#35410: Can't Set a Default Value for ForeignKey Field in Custom User Model
-------------------------------------+-------------------------------------
     Reporter:  Ebram Shehata        |                    Owner:  nobody
         Type:  Bug                  |                   Status:  closed
    Component:  contrib.auth         |                  Version:  5.0
     Severity:  Normal               |               Resolution:  invalid
     Keywords:  migrations,          |             Triage Stage:
  foreignkey, user, models           |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------

Old description:

> Hello,
>
> So, I'm trying to add a `ForeignKey` field with a default value in a
> custom user model.
> The use case is that each user should be assigned to a department. But
> all new users
> should have a default department with name 'UNASSIGNED'.
>
> - How to reproduce:
> 1. Create a blank Django project.
> 2. Create a new 'profiles' app.
> 3. Register the app in settings.py and point `AUTH_USER_MODEL` to
> `"profiles.UserProfile"`.
>
> {{{
> INSTALLED_APPS = [
>     'django.contrib.admin',
>     'django.contrib.auth',
>     'django.contrib.contenttypes',
>     'django.contrib.sessions',
>     'django.contrib.messages',
>     'django.contrib.staticfiles',
>     "profiles"
> ]
> AUTH_USER_MODEL = "profiles.UserProfile"
> }}}
>
> 4. Add the following models to profiles/models.py:
>
> {{{
> from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
>
> from django.db import models
>

> class Department(models.Model):
>     name = models.CharField(max_length=256, unique=True)
>

> def unassigned_department():
>     return Department.objects.get_or_create(name="UNASSIGNED")[0].pk
>

> class UserProfile(AbstractBaseUser, PermissionsMixin):
>     department = models.ForeignKey(
>         Department,
>         on_delete=models.CASCADE,
>         default=unassigned_department,
>         related_name="user_profiles",
>     )
>     username = models.CharField(max_length=256, unique=True)
>
>     is_active = models.BooleanField(default=True, null=False)
>     is_superuser = models.BooleanField(default=False, null=False)
>     is_staff = models.BooleanField(default=False, null=False)
>
>     USERNAME_FIELD = "username"
> }}}
>
> 5. Run `python manage.py makemigrations`.
>
> You'll get the following error:
> `django.db.utils.OperationalError: no such table: profiles_department`
>
> Django versions I tried: 4.2.7 and 5.0.4.
>
> I also noticed that if I inherit from `django.db.models.Model` in
> `UserProfile` de-register it
> from `AUTH_USER_MODEL` setting, I can create migrations successfully and
> migrate the
> database too! I also could create instances that have the default
> department as expected.

New description:

 Hello,

 So, I'm trying to add a `ForeignKey` field with a default value in a
 custom user model.
 The use case is that each user should be assigned to a department. But all
 new users
 should have a default department with name 'UNASSIGNED'.

 - How to reproduce:
 1. Create a blank Django project.
 2. Create a new 'profiles' app.
 3. Register the app in settings.py and point `AUTH_USER_MODEL` to
 `"profiles.UserProfile"`.

 {{{
 INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     "profiles"
 ]
 AUTH_USER_MODEL = "profiles.UserProfile"
 }}}

 4. Add the following models to profiles/models.py:

 {{{
 from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin

 from django.db import models


 class Department(models.Model):
     name = models.CharField(max_length=256, unique=True)


 def unassigned_department():
     return Department.objects.get_or_create(name="UNASSIGNED")[0].pk


 class UserProfile(AbstractBaseUser, PermissionsMixin):
     department = models.ForeignKey(
         Department,
         on_delete=models.CASCADE,
         default=unassigned_department,
         related_name="user_profiles",
     )
     username = models.CharField(max_length=256, unique=True)

     is_active = models.BooleanField(default=True, null=False)
     is_superuser = models.BooleanField(default=False, null=False)
     is_staff = models.BooleanField(default=False, null=False)

     USERNAME_FIELD = "username"
 }}}

 5. Run `python manage.py makemigrations`.

 You'll get the following error:
 `django.db.utils.OperationalError: no such table: profiles_department`

 Django versions I tried: 4.2.7 and 5.0.4.

 I also noticed that if I inherit from `django.db.models.Model` in
 `UserProfile` de-register it
 from `AUTH_USER_MODEL` setting, I can create migrations successfully and
 migrate the
 database too! I also could create instances that have the default
 department as expected.


 Here's the full traceback:

 {{{
 Traceback (most recent call last):
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/backends/utils.py", line 105, in _execute
     return self.cursor.execute(sql, params)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/backends/sqlite3/base.py", line 329, in execute
     return super().execute(query, params)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 sqlite3.OperationalError: no such table: profiles_department

 The above exception was the direct cause of the following exception:

 Traceback (most recent call last):
   File "/SOME-DIR/blank_django/src/blanked/manage.py", line 22, in
 <module>
     main()
   File "/SOME-DIR/blank_django/src/blanked/manage.py", line 18, in main
     execute_from_command_line(sys.argv)
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/__init__.py", line 442, in
 execute_from_command_line
     utility.execute()
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/__init__.py", line 436, in execute
     self.fetch_command(subcommand).run_from_argv(self.argv)
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/base.py", line 413, in run_from_argv
     self.execute(*args, **cmd_options)
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/base.py", line 454, in execute
     self.check()
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/base.py", line 486, in check
     all_issues = checks.run_checks(
                  ^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/checks/registry.py", line 88, in run_checks
     new_errors = check(app_configs=app_configs, databases=databases)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/contrib/auth/checks.py", line 84, in check_user_model
     if isinstance(cls().is_anonymous, MethodType):
                   ^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/base.py", line 535, in __init__
     val = field.get_default()
           ^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/fields/related.py", line 1134, in get_default
     field_default = super().get_default()
                     ^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/fields/__init__.py", line 1021, in get_default
     return self._get_default()
            ^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/blanked/profiles/models.py", line 11,
 in unassigned_department
     return Department.objects.get_or_create(name="UNASSIGNED")[0].pk
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/manager.py", line 87, in manager_method
     return getattr(self.get_queryset(), name)(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/query.py", line 948, in get_or_create
     return self.get(**kwargs), False
            ^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/query.py", line 645, in get
     num = len(clone)
           ^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/query.py", line 382, in __len__
     self._fetch_all()
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/query.py", line 1928, in _fetch_all
     self._result_cache = list(self._iterable_class(self))
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/query.py", line 91, in __iter__
     results = compiler.execute_sql(
               ^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/models/sql/compiler.py", line 1562, in execute_sql
     cursor.execute(sql, params)
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/backends/utils.py", line 122, in execute
     return super().execute(sql, params)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/backends/utils.py", line 79, in execute
     return self._execute_with_wrappers(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/backends/utils.py", line 92, in _execute_with_wrappers
     return executor(sql, params, many, context)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/backends/utils.py", line 100, in _execute
     with self.db.wrap_database_errors:
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/utils.py", line 91, in __exit__
     raise dj_exc_value.with_traceback(traceback) from exc_value
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/backends/utils.py", line 105, in _execute
     return self.cursor.execute(sql, params)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/backends/sqlite3/base.py", line 329, in execute
     return super().execute(query, params)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 django.db.utils.OperationalError: no such table: profiles_department

 }}}

--
Comment (by Ebram Shehata):

 Replying to [comment:1 Tim Graham]:
 > You didn't provide the full traceback but I suspect the error comes from
 `Department.objects.get_or_create()`. I don't think Django is at fault.
 You could make the migrations in two steps, adding the `default` only
 after the `Department` table is created.

 Oh, I just added the traceback.

 - I think that is not the expected behavior when someone uses a callable
 as `default` parameter. I expect it to be used when the value was not
 provided and I'm trying to create an instance and of course not when I
 make migrations and that is working as expected when the target model is
 not a custom user model (inheriting from `models.Model`).

 - I also think the work around of creating a data migration for creating
 the default instance is not actually convenient. Because I think the
 scenario could be:
 Develop stuff.
 Create migration for the project and the a special migration for that
 default instance.
 After that we'll have to actually migrate the database so far to create
 that instance.
 Now develop the custom user profile and make migrations.
 Now we should be able to migrate database but no. Here's a problem that
 appeared with this scenario after adding the custom user model in this
 case:

 {{{
 Operations to perform:
   Apply all migrations: admin, auth, contenttypes, profiles, sessions
 Traceback (most recent call last):
   File "/SOME-DIR/blank_django/src/blanked/manage.py", line 22, in
 <module>
     main()
   File "/SOME-DIR/blank_django/src/blanked/manage.py", line 18, in main
     execute_from_command_line(sys.argv)
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/__init__.py", line 442, in
 execute_from_command_line
     utility.execute()
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/__init__.py", line 436, in execute
     self.fetch_command(subcommand).run_from_argv(self.argv)
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/base.py", line 413, in run_from_argv
     self.execute(*args, **cmd_options)
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/base.py", line 459, in execute
     output = self.handle(*args, **options)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/base.py", line 107, in wrapper
     res = handle_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/core/management/commands/migrate.py", line 302, in handle
     pre_migrate_apps = pre_migrate_state.apps
                        ^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/utils/functional.py", line 47, in __get__
     res = instance.__dict__[self.name] = self.func(instance)
                                          ^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/migrations/state.py", line 566, in apps
     return StateApps(self.real_apps, self.models)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/SOME-DIR/blank_django/src/venv/lib/python3.11/site-
 packages/django/db/migrations/state.py", line 637, in __init__
     raise ValueError("\n".join(error.msg for error in errors))
 ValueError: The field admin.LogEntry.user was declared with a lazy
 reference to 'profiles.userprofile', but app 'profiles' doesn't provide
 model 'userprofile'.
 }}}


 Overall about the scenario of having to create a data migration for that
 default instance, I think it's not convenient and it is not what's
 expected from a callable in `default` parameter. I think it should be
 behaving the same as with regular models, not having a special behavior if
 we're creating a custom user model.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35410#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/0107018f1bc8b109-0cc431ac-025e-4fcc-98bd-37958907b03d-000000%40eu-central-1.amazonses.com.

Reply via email to