3

Is there any way to check if a value exists in a model when assigning it as a default value? If the assigning value exists (it's not unique), then generate another value. (Read comments):

def unique_rand():
    return ... # Generate a random string with 8 characters length
               # It shouldn't exists among other persons
               # If exists then generate another random string


class Person(models.Model):
    code = models.CharField(max_length=8, unique=True, default=unique_rand())

Maybe the workaround cannot be inside unique_rand but somewhere inside Person class. I don't want override save method because I need the code before save. Also, I don't want to use uuid randoms.

masoud
  • 55,379
  • 16
  • 141
  • 208

1 Answers1

4

Just test the existence of the generated code in the loop.

from django.contrib.auth.models import User

def unique_rand():
    while True:
        code = password = User.objects.make_random_password(length=8)
        if not Person.objects.filter(code=code).exists():
            return code

class Person(models.Model):
    code = models.CharField(max_length=8, unique=True, default=unique_rand)

Note that there is no round brackets in the default=unique_rand argument.

If you want to limit the number of attempts then change the loop from while to for:

def unique_rand():
    for _ in range(5):
        ...
    raise ValueError('Too many attempts to generate the code')
catavaran
  • 44,703
  • 8
  • 98
  • 85
  • Note that @catavaran has changed `default=unique_rand()` to `default=unique_rand`. It was made so the `unique_rand()` function would be called every time you create a `Person` object. Otherwise it would be called just once - on model's initialization. – Igor Hatarist Feb 09 '15 at 21:27
  • There must be a race problem! Imagine `p1 = Person(); p2 = Person()` then `p1.code` could be as same as `p2.code` because they've not saved yet. What about this situation ?! – masoud Feb 09 '15 at 21:38
  • Then `IntegrityError` exception will be raised. You can't avoid this anyway. – catavaran Feb 09 '15 at 21:42
  • @M M: that's an impossibility in your requirements, it seems to me. – RemcoGerlich Feb 09 '15 at 21:45
  • Yes, I think it is impossible. At least as a straightforward solution. The generated codes should be stored somewhere else. BTW, thanks for your help. – masoud Feb 09 '15 at 21:46
  • You can create another model with the single unique field to store generated codes. Then in the `unique_rand()` you can try to save the code in this model and catch `IntegrityError` to retry with the new code. But then you have to take care of the synchronization of these two tables. – catavaran Feb 10 '15 at 00:54