Opened 10 months ago

Closed 10 months ago

Last modified 10 months ago

#22783 closed Bug (fixed)

Auto-created migrations place custom user model *after* things that depend on it.

Reported by: melinath Owned by: andrewgodwin
Component: Migrations Version: 1.7-beta-2
Severity: Release blocker Keywords:
Cc: melinath Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Ran into this when I tried to pull the latest stable/1.7.x (currently @ c8c1883962ebb0668663134e023ffbe1c753a450) and re-generate migrations for django-brambling (@ d479f0666c7fe4d5d9f11554737909dc8504ce43).

The error I'm getting is:

ValueError: Lookup failed for model referenced by field brambling.Attendee.event_person: brambling.EventPerson

The reason I'm getting this error AFAICT is that brambling.Person is a swappable user model, and is being placed *after* things that depend on it.

I had to revert to 52b7e772e4da846efb2d75b22ec05c7105091b76 to get things working.

Change History (8)

comment:1 Changed 10 months ago by timo

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

The commit you referenced after "I had to revert..." is a doc patch.

comment:2 Changed 10 months ago by valberg

  • Triage Stage changed from Unreviewed to Accepted

I can confirm this in master (after applying patch from #22848 that is).

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.conf import settings

class Article(models.Model):
    body = models.TextField()
    user = models.ForeignKey(settings.AUTH_USER_MODEL)


class Profile(AbstractUser):
    about = models.TextField()

Results in this migration:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import django.utils.timezone
import django.core.validators
from django.conf import settings


class Migration(migrations.Migration):

    dependencies = [
        ('auth', '__latest__'),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='Article',
            fields=[
                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
                ('body', models.TextField()),
                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, to_field='id')),
            ],
            options={
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='Profile',
            fields=[
                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
                ('password', models.CharField(verbose_name='password', max_length=128)),
                ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
                ('is_superuser', models.BooleanField(default=False, verbose_name='superuser status', help_text='Designates that this user has all permissions without explicitly assigning them.')),
                ('username', models.CharField(unique=True, verbose_name='username', max_length=30, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
                ('first_name', models.CharField(verbose_name='first name', blank=True, max_length=30)),
                ('last_name', models.CharField(verbose_name='last name', blank=True, max_length=30)),
                ('email', models.EmailField(verbose_name='email address', blank=True, max_length=75)),
                ('is_staff', models.BooleanField(default=False, verbose_name='staff status', help_text='Designates whether the user can log into this admin site.')),
                ('is_active', models.BooleanField(default=True, verbose_name='active', help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.')),
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
                ('about', models.TextField()),
                ('groups', models.ManyToManyField(verbose_name='groups', blank=True, to='auth.Group')),
                ('user_permissions', models.ManyToManyField(verbose_name='user permissions', blank=True, to='auth.Permission')),
            ],
            options={
                'verbose_name': 'user',
                'abstract': False,
                'verbose_name_plural': 'users',
            },
            bases=(models.Model,),
        ),
    ]

and this error when trying to migrate:

$ ./manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: auth, contenttypes, sessions
  Apply all migrations: testapp, admin
Synchronizing apps without migrations:
  Creating tables...
  Installing custom SQL...
  Installing indexes...
Running migrations:
  Applying testapp.0001_initial...Traceback (most recent call last):
  File "/Users/valberg/Code/forks/django/django/apps/config.py", line 152, in get_model
    return self.models[model_name.lower()]
KeyError: 'profile'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/valberg/Code/forks/django/django/db/migrations/state.py", line 79, in render
    model = self.apps.get_model(lookup_model[0], lookup_model[1])
  File "/Users/valberg/Code/forks/django/django/apps/registry.py", line 190, in get_model
    return self.get_app_config(app_label).get_model(model_name.lower())
  File "/Users/valberg/Code/forks/django/django/apps/config.py", line 155, in get_model
    "App '%s' doesn't have a '%s' model." % (self.label, model_name))
LookupError: App 'testapp' doesn't have a 'profile' model.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/Users/valberg/Code/forks/django/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/Users/valberg/Code/forks/django/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/valberg/Code/forks/django/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/Users/valberg/Code/forks/django/django/core/management/base.py", line 337, in execute
    output = self.handle(*args, **options)
  File "/Users/valberg/Code/forks/django/django/core/management/commands/migrate.py", line 146, in handle
    executor.migrate(targets, plan, fake=options.get("fake", False))
  File "/Users/valberg/Code/forks/django/django/db/migrations/executor.py", line 62, in migrate
    self.apply_migration(migration, fake=fake)
  File "/Users/valberg/Code/forks/django/django/db/migrations/executor.py", line 96, in apply_migration
    migration.apply(project_state, schema_editor)
  File "/Users/valberg/Code/forks/django/django/db/migrations/migration.py", line 107, in apply
    operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
  File "/Users/valberg/Code/forks/django/django/db/migrations/operations/models.py", line 33, in database_forwards
    apps = to_state.render()
  File "/Users/valberg/Code/forks/django/django/db/migrations/state.py", line 89, in render
    model=lookup_model,
ValueError: Lookup failed for model referenced by field testapp.Article.user: testapp.Profile

comment:3 Changed 10 months ago by valberg

  • Severity changed from Normal to Release blocker

comment:4 Changed 10 months ago by valberg

Apparently the order of the operations list is not the issue. Just tried with these models:

class AProfile(AbstractUser):
    about = models.TextField()


class ZArticle(models.Model):
    body = models.TextField()
    user = models.ForeignKey(settings.AUTH_USER_MODEL)

which puts the AProfile model first in the operations list in the migration.

comment:5 Changed 10 months ago by andrewgodwin

  • Owner changed from nobody to andrewgodwin
  • Status changed from new to assigned

comment:6 Changed 10 months ago by Andrew Godwin <andrew@…>

  • Resolution set to fixed
  • Status changed from assigned to closed

In 067b9668fb3552a9b40e299095be08c1642d84c4:

Fixed #22783: Make sure swappable models come first in creation

comment:7 Changed 10 months ago by Andrew Godwin <andrew@…>

In d9a7663b11269ca0adda26ad16e47350b680bab5:

[1.7.x] Fixed #22783: Make sure swappable models come first in creation

comment:8 Changed 10 months ago by andrewgodwin

I tried to replicate with the models the other way around (in operations) and it worked correctly, as I suspected - the ordering is the issue here. The fix will try and order swappable models first on a best-effort basis.

This problem is actually unsolvable in the general case; Django provides no way of telling if a model is a potential candidate for being swapped in in the future, so if you're doing your own swappable settings you may run into this (the fix just deals with AUTH_USER_MODEL). Manually re-ordering the operations should fix your problem.

Note: See TracTickets for help on using tickets.
Back to Top