#21638 closed Bug (fixed)

Infinite migrations when using AUTH_USER_MODEL

Reported by: patrick@… Owned by: Andrew Godwin <andrew@…>
Component: Migrations Version: master
Severity: Normal Keywords:
Cc: jess@…, wengole Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

When using a custom class with AUTH_USER_MODEL, syncdb thinks that the "username" field in that class has changed. Every time you run

python manage.py makemigrations

it generates a new migration file, even though that model has no changes. If you run that migration, and call makemigrations again, it still thinks the field has changed and will make even more migration files.

For example, my models.py:

from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.

class Person(AbstractUser):
    favorite_color = models.CharField(max_length=64)
    

0001_initial.py created by "python manage.py makemigrations myapp"

# encoding: utf8
from django.db import models, migrations
import django.utils.timezone
import django.core.validators


class Migration(migrations.Migration):
    
    dependencies = []

    operations = [
        migrations.CreateModel(
            fields = [('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True),), ('password', models.CharField(max_length=128, verbose_name='password'),), ('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(max_length=30, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')]),), ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True),), ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True),), ('email', models.EmailField(max_length=75, verbose_name='email address', blank=True),), ('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'),), ('favorite_color', models.CharField(max_length=64),), ('groups', models.ManyToManyField(to='auth.Group', verbose_name='groups', blank=True),), ('user_permissions', models.ManyToManyField(to='auth.Permission', verbose_name='user permissions', blank=True),)],
            options = {'abstract': False, 'verbose_name': 'user', 'verbose_name_plural': 'users'},
            bases = (models.Model,),
            name = 'Person',
        ),
    ]

I'm suspicious of the "bases = (models.Model,)" instead of "bases=(models.AbstractUser,)" but I don't know if that has any significance.


And every time I run makemigrations after this, it creates a new migration like the following (the field order changes, but the content is always the same). If you run makemigrations ten times without changing your model, you get ten new scripts that all look like this:

# encoding: utf8
from django.db import models, migrations
import django.core.validators


class Migration(migrations.Migration):
    
    dependencies = [('myapp', '0001_initial')]

    operations = [
        migrations.AlterField(
            name = 'username',
            model_name = 'person',
            field = models.CharField(validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username', max_length=30, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True),
        ),
    ]

Discovered using Python 3.3.3, and Django 1.7 (alpha) commit 23d9f517dc3ca31816bb8596f5a59f1ae44304ee

Note: I double checked this occurred with a fresh database after reading the warning here, and confirmed that AUTH_USER_MODEL was set before any tables were made.

Change History (8)

comment:1 Changed 19 months ago by jess@…

  • Cc jess@… added
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

comment:2 Changed 19 months ago by timo

  • Triage Stage changed from Unreviewed to Accepted
  • Version set to master

comment:3 Changed 18 months ago by patrick@…

In django/db/migrations/autodetector.py line 241, the old version of the username field is compared against the current username field. Both the old and new username fields have their own separate instance of a django.core.validators.RegexValidator. The two RegexValidator objects have the exact same values, but because they are separate instances and do not have a comparison function they always compare as unequal.

Some possible solutions:

1) Ignore validators when comparing fields. My understanding is that the validators only affect data and do not influence the database schema, but I haven't fully explored their usage. I believe something similar was proposed and turned down in ticket #21275 because the validators may still be useful during the migration process.

2) Add an __eq__ function to the RegexValidator class. This is quick and easy, but the other validator classes may need this also to prevent the same error in the future.

I'd be willing to contribute a patch, but I don't want to prescribe a solution without feedback from maintainers who understand the overall system better than I do.

comment:4 Changed 18 months ago by Andrew Godwin <andrew@…>

  • Owner set to Andrew Godwin <andrew@…>
  • Resolution set to fixed
  • Status changed from new to closed

In a68f32579145dfbd51d87e14f9b5e04c770f9081:

Fixed #21638: Validators are now comparable, stops infinite user mig'ns

comment:5 Changed 15 months ago by anonymous

  • Resolution fixed deleted
  • Status changed from closed to new

I am still encountering this issue on 1.7b1

$ git log
commit dda6224459ed843dfd63e85729613fcc60ce925f
Author: James Bennett <james@b-list.org>
Date:   Thu Mar 20 19:42:11 2014 -0500

    [1.7.x] Bump version numbers for 1.7 beta 1.

User Model

class User(AbstractUser):

    def __unicode__(self):
        return self.username

Running ./manage makemigrations

./manage.py makemigrations
Migrations for 'users':
  0002_auto_20140415_1115.py:
    - Alter field username on user

Results in

class Migration(migrations.Migration):

    dependencies = [
        ('users', '0001_initial'),
    ]

    operations = [
        migrations.AlterField(
            model_name='user',
            name='username',
            field=models.CharField(help_text=u'Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name=u'username', validators=[django.core.validators.RegexValidator(u'^[\\w.@+-]+$', u'Enter a valid username.', u'invalid')]),
        ),
    ]

And this keeps happening over and over

comment:6 Changed 15 months ago by wengole

  • Cc wengole added

comment:7 Changed 14 months ago by timo

I cannot reproduce with the provided information due to ValueError: Lookup failed for model referenced by field polls.User.groups: auth.Group when running makemigrations a second time, which I believe is #22485. I will leave this ticket open so we can check this after that is fixed.

comment:8 Changed 14 months ago by timo

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

With the latest master and stable/1.7.x I cannot reproduce infinite migrations given the above code.

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