#29897: Initial migration fails when referencing a custom user model from a
sequence of at least two concrete ancestor models
-------------------------------------+-------------------------------------
     Reporter:  stevenganz           |                    Owner:  nobody
         Type:  Bug                  |                   Status:  new
    Component:  Migrations           |                  Version:  2.1
     Severity:  Normal               |               Resolution:
     Keywords:  "custom user model"  |             Triage Stage:  Accepted
  "abstract model" "foreign key"     |
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------

Old description:

> The following model fails on initial migration.
> {{{
> from django.db import models
> from django.contrib.auth.models import (AbstractBaseUser)
>
> class A(models.Model):
>     createdByUser = models.ForeignKey('User', related_name='creations+',
> verbose_name='creatorUser', blank=True, null=True, editable=False,
> on_delete=models.PROTECT)
>
> class B(A):
>     pass
>
> class User(AbstractBaseUser, B):
>     email = models.EmailField('email address', max_length=256,
> unique=True, db_index=True)
>     USERNAME_FIELD = 'email'
>
>     @property
>     def username(self):
>          return self.email
> }}}
>
> settings.py contains:
> {{{
> AUTH_USER_MODEL = 'core.User'
> }}}
>
> {{{
>   Applying core.0001_initial...Traceback (most recent call last):
>   File "./manage.py", line 13, in <module>
>     execute_from_command_line(sys.argv)
>   File ".../lib/python3.6/site-
> packages/django/core/management/__init__.py", line 364, in
> execute_from_command_line
>     utility.execute()
>   File ".../lib/python3.6/site-
> packages/django/core/management/__init__.py", line 356, in execute
>     self.fetch_command(subcommand).run_from_argv(self.argv)
>   File ".../lib/python3.6/site-packages/django/core/management/base.py",
> line 283, in run_from_argv
>     self.execute(*args, **cmd_options)
>   File ".../lib/python3.6/site-packages/django/core/management/base.py",
> line 330, in execute
>     output = self.handle(*args, **options)
>   File ".../lib/python3.6/site-
> packages/django/core/management/commands/migrate.py", line 204, in handle
>     fake_initial=fake_initial,
>   File ".../lib/python3.6/site-
> packages/django/db/migrations/executor.py", line 115, in migrate
>     state = self._migrate_all_forwards(state, plan, full_plan, fake=fake,
> fake_initial=fake_initial)
>   File ".../lib/python3.6/site-
> packages/django/db/migrations/executor.py", line 145, in
> _migrate_all_forwards
>     state = self.apply_migration(state, migration, fake=fake,
> fake_initial=fake_initial)
>   File ".../lib/python3.6/site-
> packages/django/db/migrations/executor.py", line 244, in apply_migration
>     state = migration.apply(state, schema_editor)
>   File ".../lib/python3.6/site-
> packages/django/db/migrations/migration.py", line 129, in apply
>     operation.database_forwards(self.app_label, schema_editor, old_state,
> project_state)
>   File ".../lib/python3.6/site-
> packages/django/db/migrations/operations/fields.py", line 88, in
> database_forwards
>     field,
>   File ".../lib/python3.6/site-
> packages/django/db/backends/base/schema.py", line 431, in add_field
>     definition, params = self.column_sql(model, field,
> include_default=True)
>   File ".../lib/python3.6/site-
> packages/django/db/backends/base/schema.py", line 160, in column_sql
>     db_params = field.db_parameters(connection=self.connection)
>   File ".../lib/python3.6/site-
> packages/django/db/models/fields/related.py", line 994, in db_parameters
>     return {"type": self.db_type(connection), "check":
> self.db_check(connection)}
>   File ".../lib/python3.6/site-
> packages/django/db/models/fields/related.py", line 991, in db_type
>     return self.target_field.rel_db_type(connection=connection)
>   File ".../lib/python3.6/site-
> packages/django/db/models/fields/related.py", line 909, in target_field
>     return self.foreign_related_fields[0]
>   File ".../lib/python3.6/site-
> packages/django/db/models/fields/related.py", line 653, in
> foreign_related_fields
>     return tuple(rhs_field for lhs_field, rhs_field in
> self.related_fields if rhs_field)
>   File ".../lib/python3.6/site-
> packages/django/db/models/fields/related.py", line 640, in related_fields
>     self._related_fields = self.resolve_related_fields()
>   File ".../lib/python3.6/site-
> packages/django/db/models/fields/related.py", line 625, in
> resolve_related_fields
>     raise ValueError('Related model %r cannot be resolved' %
> self.remote_field.model)
> ValueError: Related model 'core.User' cannot be resolved
> }}}
>
> Notably, this works fine with any of the following modifications:
> - model B is removed, closing the chain
> - createdByUser is moved to B or removed altogether
> - either A or B is made abstract
> - the referenced model is not the custom user model
>
> This is not just a matter of preference for concrete classes, as only a
> concrete class can be referenced through a foreign key.

New description:

 The following model fails on initial migration.
 {{{
 from django.db import models
 from django.contrib.auth.models import (AbstractBaseUser)

 class A(models.Model):
     createdByUser = models.ForeignKey('User', related_name='creations+',
 verbose_name='creatorUser', blank=True, null=True, editable=False,
 on_delete=models.PROTECT)

 class B(A):
     pass

 class User(AbstractBaseUser, B):
     email = models.EmailField('email address', max_length=256,
 unique=True, db_index=True)
     USERNAME_FIELD = 'email'

     @property
     def username(self):
          return self.email
 }}}

 settings.py contains:
 {{{
 AUTH_USER_MODEL = 'core.User'
 }}}

 Output of running the migrations:
 {{{
   Applying core.0001_initial...Traceback (most recent call last):
   File "./manage.py", line 13, in <module>
     execute_from_command_line(sys.argv)
   File ".../lib/python3.6/site-
 packages/django/core/management/__init__.py", line 364, in
 execute_from_command_line
     utility.execute()
   File ".../lib/python3.6/site-
 packages/django/core/management/__init__.py", line 356, in execute
     self.fetch_command(subcommand).run_from_argv(self.argv)
   File ".../lib/python3.6/site-packages/django/core/management/base.py",
 line 283, in run_from_argv
     self.execute(*args, **cmd_options)
   File ".../lib/python3.6/site-packages/django/core/management/base.py",
 line 330, in execute
     output = self.handle(*args, **options)
   File ".../lib/python3.6/site-
 packages/django/core/management/commands/migrate.py", line 204, in handle
     fake_initial=fake_initial,
   File ".../lib/python3.6/site-packages/django/db/migrations/executor.py",
 line 115, in migrate
     state = self._migrate_all_forwards(state, plan, full_plan, fake=fake,
 fake_initial=fake_initial)
   File ".../lib/python3.6/site-packages/django/db/migrations/executor.py",
 line 145, in _migrate_all_forwards
     state = self.apply_migration(state, migration, fake=fake,
 fake_initial=fake_initial)
   File ".../lib/python3.6/site-packages/django/db/migrations/executor.py",
 line 244, in apply_migration
     state = migration.apply(state, schema_editor)
   File ".../lib/python3.6/site-
 packages/django/db/migrations/migration.py", line 129, in apply
     operation.database_forwards(self.app_label, schema_editor, old_state,
 project_state)
   File ".../lib/python3.6/site-
 packages/django/db/migrations/operations/fields.py", line 88, in
 database_forwards
     field,
   File ".../lib/python3.6/site-
 packages/django/db/backends/base/schema.py", line 431, in add_field
     definition, params = self.column_sql(model, field,
 include_default=True)
   File ".../lib/python3.6/site-
 packages/django/db/backends/base/schema.py", line 160, in column_sql
     db_params = field.db_parameters(connection=self.connection)
   File ".../lib/python3.6/site-
 packages/django/db/models/fields/related.py", line 994, in db_parameters
     return {"type": self.db_type(connection), "check":
 self.db_check(connection)}
   File ".../lib/python3.6/site-
 packages/django/db/models/fields/related.py", line 991, in db_type
     return self.target_field.rel_db_type(connection=connection)
   File ".../lib/python3.6/site-
 packages/django/db/models/fields/related.py", line 909, in target_field
     return self.foreign_related_fields[0]
   File ".../lib/python3.6/site-
 packages/django/db/models/fields/related.py", line 653, in
 foreign_related_fields
     return tuple(rhs_field for lhs_field, rhs_field in self.related_fields
 if rhs_field)
   File ".../lib/python3.6/site-
 packages/django/db/models/fields/related.py", line 640, in related_fields
     self._related_fields = self.resolve_related_fields()
   File ".../lib/python3.6/site-
 packages/django/db/models/fields/related.py", line 625, in
 resolve_related_fields
     raise ValueError('Related model %r cannot be resolved' %
 self.remote_field.model)
 ValueError: Related model 'core.User' cannot be resolved
 }}}

 Notably, this works fine with any of the following modifications:
 - model B is removed, closing the chain
 - createdByUser is moved to B or removed altogether
 - either A or B is made abstract
 - the referenced model is not the custom user model

 This is not just a matter of preference for concrete classes, as only a
 concrete class can be referenced through a foreign key.

--

Comment (by stevenganz):

 I accidentally reset your changes to several fields -- please check that
 I've restored them appropriately.

 This was for an initial migration.  Like you, I reproduced it on 1.11 &
 2.1.

 Your first work-around didn't work for me (perhaps because my actual
 models are much more complex), but the second (temporarily updating the
 custom user model) does.  Thanks, and let me know if you need any more
 information.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/29897#comment:3>
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 post to this group, send email to django-updates@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/068.9e2402b421cf583957e6fde22bf94643%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to