Search code examples
djangomigrationmodels

Django: Trying to make Users migration with a self-referential default value


I'm trying to set a default value on a field I added to my User model, but I'm having to do a lot of acrobativs to make it work.

I want to add an 'identifier' SlugField with a default value defined in a function, like so:

def create_identifier():
    while True:
        identifier = ''.join(random.SystemRandom().choice('23456789BCDFGHJKMNPQRSTVWXYZ') for _ in range(15))
        if not User.objects.filter(identifier=identifier).exists():
            return identifier

class User(AbstractUser):
    identifier = models.SlugField(default=create_identifier, max_length=16)

    def __str__(self):
        return self.username

This exact scenario works fine on a different (not User) nodel class, but when I try to run migrations with this code for User, I get:

Traceback (most recent call last):
  File "manage.py", line 29, in <module>
    execute_from_command_line(sys.argv)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/core/management/__init__.py", line 371, in execute_from_command_line
    utility.execute()
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/core/management/__init__.py", line 365, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/core/management/base.py", line 332, in execute
    self.check()
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/core/management/base.py", line 364, in check
    include_deployment_checks=include_deployment_checks,
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/core/management/commands/migrate.py", line 58, in _run_checks
    issues.extend(super()._run_checks(**kwargs))
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/core/management/base.py", line 351, in _run_checks
    return checks.run_checks(**kwargs)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/core/checks/registry.py", line 73, in run_checks
    new_errors = check(app_configs=app_configs)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/contrib/auth/checks.py", line 74, in check_user_model
    if isinstance(cls().is_anonymous, MethodType):
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/models/base.py", line 469, in __init__
    val = field.get_default()
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/models/fields/__init__.py", line 775, in get_default
    return self._get_default()
  File "/home/myprojectname/myprojectname/myprojectname/users/models.py", line 34, in create_identifier
    if not User.objects.filter(identifier=identifier).exists():
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/models/query.py", line 715, in exists
    return self.query.has_results(using=self.db)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/models/sql/query.py", line 509, in has_results
    return compiler.has_results()
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1037, in has_results
    return bool(self.execute_sql(SINGLE))
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1068, in execute_sql
    cursor.execute(sql, params)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/backends/utils.py", line 100, in execute
    return super().execute(sql, params)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/backends/utils.py", line 68, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/backends/utils.py", line 77, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/backends/utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/myprojectname/.local/share/virtualenvs/myprojectname-Uef4Bstr/lib/python3.6/site-packages/django/db/backends/utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "users_user" does not exist
LINE 1: SELECT (1) AS "a" FROM "users_user" WHERE "users_user"."iden...

I have to manually modify the migrations as such:

migrations.CreateModel(
    name='User',
    fields=[
        ...

        ('identifier', models.SlugField(default='', max_length=16)),

        ...
),

migrations.AlterField(
    model_name='user',
    name='identifier',
    field=models.SlugField(default=myprojectname.users.models.create_identifier, max_length=16),
),

And then if I initially set default='' in the models.py and run migrations it works, and I can change back to default=create_identifier after the fact.

class User(AbstractUser):
    identifier = models.SlugField(default='', max_length=16)

    def __str__(self):
        return self.username

Is there any way I can make this work without having to go through these measures?

Edit: Would I be better off just leaving blank=True, null=True and overriding the model's save() method to change the value if the field is blank or null?


Solution

  • I seem to have solved the issue by modifying create_identifier to the following:

    def create_identifier():
        while True:
            identifier = ''.join(random.SystemRandom().choice('23456789BCDFGHJKMNPQRSTVWXYZ') for _ in range(15))
            try:
                present = User.objects.first()
            except:
                present = None
    
            if present:
                if not User.objects.filter(identifier=identifier).exists():
                    return identifier
            else:
                return identifier