Opened 3 years ago

Closed 2 years ago

#33566 closed Bug (needsinfo)

Issue with migrations after upgrading Django 2 to Django 3.2.

Reported by: Ismael Jerez Owned by: nobody
Component: Migrations Version: 3.2
Severity: Normal Keywords: orm, migrate, makemigrations, database, migrations, migration
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Hi,

I recently upgraded from Django 2 to Django 3.2.

Everything works fine but when I execute “migrate” as a manage.py command, it always says “Your models in app(s): ‘admin’, ‘auth’, ‘base’, ‘contenttypes’, ‘sessions’ have changes that are not yet reflected in a migration, and so won’t be applied.
Run ‘manage.py makemigrations’ to make new migrations, and then re-run ‘manage.py migrate’ to apply them.”

If I execute “makemigrations” commands I always get “No changes detected”.

I downgrade to Django2, the message is gone.

I have every mentioned app on my INSTALLED_APPS variable in settings. As you can see only ‘base’ app is one of mine, the rest are default Django apps.

I am using Oracle 19 database that can not be recreated.

I hope you can help me or give me some ideas of what is happening and why am I receiving that message from “migrate” command.

Thank you in advance,
Ismael.

Change History (5)

comment:1 by Mariusz Felisiak, 3 years ago

Resolution: needsinfo
Status: newclosed

Hi, I don't think you've explained the issue in enough detail to confirm a bug in Django. Can you provide a sample project showing a fault in Django?

comment:2 by Ismael Jerez, 2 years ago

Resolution: needsinfo
Status: closednew

Hi again:

Sorry, it's been a while. I cannot share my code due to confidentiality, but I think I found a solution.

I was debugging migrate command to see what changes are being detected. Then, I have called write_migration_files(changes) from makemigrations command using the changes detected before. After this, I have new migrations created for the mentioned apps. Most of the changes are only verbose_name related (Spanish translations) and minor updates, so I think it should be safe to migrate.

The question is: why did migrate command detect these changes (see below) but makemigrations did not? I think both changes autodetector should work the same way to avoid problems like this one.

These are the changes detected by migrate command (but not by makemigrations) related to Django default apps:
admin app:

# Generated by Django 3.2.13 on 2022-06-08 07:39

from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

    dependencies = [
        ('base', '0012_auto_20220608_0939'),
        ('contenttypes', '0003_auto_20220608_0939'),
        ('admin', '0003_logentry_add_action_flag_choices'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='logentry',
            options={'ordering': ['-action_time'], 'verbose_name': 'entrada de registro', 'verbose_name_plural': 'entradas de registro'},
        ),
        migrations.AlterField(
            model_name='logentry',
            name='action_flag',
            field=models.PositiveSmallIntegerField(choices=[(1, 'Añadido'), (2, 'Modificar'), (3, 'Borrado')], verbose_name='marca de acción'),
        ),
        migrations.AlterField(
            model_name='logentry',
            name='action_time',
            field=models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='hora de la acción'),
        ),
        migrations.AlterField(
            model_name='logentry',
            name='change_message',
            field=models.TextField(blank=True, verbose_name='mensaje de cambio'),
        ),
        migrations.AlterField(
            model_name='logentry',
            name='content_type',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.contenttype', verbose_name='tipo de contenido'),
        ),
        migrations.AlterField(
            model_name='logentry',
            name='object_id',
            field=models.TextField(blank=True, null=True, verbose_name='id del objeto'),
        ),
        migrations.AlterField(
            model_name='logentry',
            name='object_repr',
            field=models.CharField(max_length=200, verbose_name='repr del objeto'),
        ),
        migrations.AlterField(
            model_name='logentry',
            name='user',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.ucauser', verbose_name='usuario'),
        ),
    ]

auth app:

# Generated by Django 3.2.13 on 2022-06-08 07:39

import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

    dependencies = [
        ('contenttypes', '0003_auto_20220608_0939'),
        ('auth', '0012_alter_user_first_name_max_length'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='group',
            options={'verbose_name': 'grupo', 'verbose_name_plural': 'grupos'},
        ),
        migrations.AlterModelOptions(
            name='permission',
            options={'ordering': ['content_type__app_label', 'content_type__model', 'codename'], 'verbose_name': 'permiso', 'verbose_name_plural': 'permisos'},
        ),
        migrations.AlterModelOptions(
            name='user',
            options={'verbose_name': 'usuario', 'verbose_name_plural': 'usuarios'},
        ),
        migrations.AlterField(
            model_name='group',
            name='name',
            field=models.CharField(max_length=150, unique=True, verbose_name='nombre'),
        ),
        migrations.AlterField(
            model_name='group',
            name='permissions',
            field=models.ManyToManyField(blank=True, to='auth.Permission', verbose_name='permisos'),
        ),
        migrations.AlterField(
            model_name='permission',
            name='codename',
            field=models.CharField(max_length=100, verbose_name='nombre en código'),
        ),
        migrations.AlterField(
            model_name='permission',
            name='content_type',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype', verbose_name='tipo de contenido'),
        ),
        migrations.AlterField(
            model_name='permission',
            name='name',
            field=models.CharField(max_length=255, verbose_name='nombre'),
        ),
        migrations.AlterField(
            model_name='user',
            name='date_joined',
            field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='fecha de alta'),
        ),
        migrations.AlterField(
            model_name='user',
            name='email',
            field=models.EmailField(blank=True, max_length=254, verbose_name='dirección de correo electrónico'),
        ),
        migrations.AlterField(
            model_name='user',
            name='first_name',
            field=models.CharField(blank=True, max_length=150, verbose_name='nombre'),
        ),
        migrations.AlterField(
            model_name='user',
            name='groups',
            field=models.ManyToManyField(blank=True, help_text='Los grupos a los que pertenece este usuario. Un usuario tendrá todos los permisos asignados a cada uno de sus grupos.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='grupos'),
        ),
        migrations.AlterField(
            model_name='user',
            name='is_active',
            field=models.BooleanField(default=True, help_text='Indica si el usuario debe ser tratado como activo. Desmarque esta opción en lugar de borrar la cuenta.', verbose_name='activo'),
        ),
        migrations.AlterField(
            model_name='user',
            name='is_staff',
            field=models.BooleanField(default=False, help_text='Indica si el usuario puede entrar en este sitio de administración.', verbose_name='es staff'),
        ),
        migrations.AlterField(
            model_name='user',
            name='is_superuser',
            field=models.BooleanField(default=False, help_text='Indica que este usuario tiene todos los permisos sin asignárselos explícitamente.', verbose_name='estado de superusuario'),
        ),
        migrations.AlterField(
            model_name='user',
            name='last_login',
            field=models.DateTimeField(blank=True, null=True, verbose_name='último inicio de sesión'),
        ),
        migrations.AlterField(
            model_name='user',
            name='last_name',
            field=models.CharField(blank=True, max_length=150, verbose_name='apellidos'),
        ),
        migrations.AlterField(
            model_name='user',
            name='password',
            field=models.CharField(max_length=128, verbose_name='contraseña'),
        ),
        migrations.AlterField(
            model_name='user',
            name='user_permissions',
            field=models.ManyToManyField(blank=True, help_text='Permisos específicos para este usuario.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='permisos de usuario'),
        ),
        migrations.AlterField(
            model_name='user',
            name='username',
            field=models.CharField(error_messages={'unique': 'Ya existe un usuario con este nombre.'}, help_text='Requerido. 150 carácteres como máximo. Únicamente letras, dígitos y @/./+/-/_ ', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='nombre de usuario'),
        ),
    ]

contenttypes app:

# Generated by Django 3.2.13 on 2022-06-08 07:39

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('contenttypes', '0002_remove_content_type_name'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='contenttype',
            options={'verbose_name': 'tipo de contenido', 'verbose_name_plural': 'tipos de contenido'},
        ),
        migrations.AlterField(
            model_name='contenttype',
            name='model',
            field=models.CharField(max_length=100, verbose_name='nombre de la clase modelo de python'),
        ),
    ]

sessions app:

# Generated by Django 3.2.13 on 2022-06-08 07:39

from django.db import migrations, models


class Migration(migrations.Migration):

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

    operations = [
        migrations.AlterModelOptions(
            name='session',
            options={'verbose_name': 'sesión', 'verbose_name_plural': 'sesiones'},
        ),
        migrations.AlterField(
            model_name='session',
            name='expire_date',
            field=models.DateTimeField(db_index=True, verbose_name='fecha de caducidad'),
        ),
        migrations.AlterField(
            model_name='session',
            name='session_data',
            field=models.TextField(verbose_name='datos de sesión'),
        ),
        migrations.AlterField(
            model_name='session',
            name='session_key',
            field=models.CharField(max_length=40, primary_key=True, serialize=False, verbose_name='clave de sesión'),
        ),
    ]

comment:3 by Carlton Gibson, 2 years ago

Resolution: needsinfo
Status: newclosed

Thanks for the follow-up.

How are you setting language for your project? Do you have any customisations there at boot-up?

I'm struggling to get the options translated into Spanish at all (in migrate or makemigrations):

            options={'verbose_name': 'grupo', 'verbose_name_plural': 'grupos'},

This is because both migrate and makemigrations use the private @no_translations decorator (src).

The only way I've managed to reproduce your error is by disabling that and then adding LANGUAGE_CODE = "es" to settings.
However if I do that, the change applies equally to makemigrations.

I was able to reproduce that with a stock fresh project created with startproject, so you should be able to do the same, but it looks like you must have some customisation in play that you've not specified here. If you can include it in a minimal sample project, happy to have a look.

comment:4 by Ismael Jerez, 2 years ago

Resolution: needsinfo
Status: closednew

Hi:

Thanks you for your reply. I don't have any customisation. About internationalisation this is my setting:

LANGUAGE_CODE = 'es-es'

TIME_ZONE = 'Europe/Madrid'

USE_I18N = True

USE_L10N = True

USE_TZ = True

I test it removing these options so makemigrations generates new migrations like reverting verbose_name to English but anyway when I run migrate after that I still getting the message about changes not reflected in a migration...

I don't think the rest of my variables could take effect on migrations.

My last try:
I edited makemigrations command commenting some lines just as migrate command:

autodetector = MigrationAutodetector(
            loader.project_state(),
            ProjectState.from_apps(apps),
            # questioner,
        )


changes = autodetector.changes(
            graph=loader.graph,
            # trim_to_apps=app_labels or None,
            # convert_apps=app_labels or None,
            # migration_name=self.migration_name,
        )

After this and removing internationalisation settings, it generates all these migrations:

Migrations for 'contenttypes':
  venv2\lib\site-packages\django\contrib\contenttypes\migrations\0004_auto_20220610_0826.py
    - Change Meta options on contenttype
    - Alter field model on contenttype
Migrations for 'sessions':
  venv2\lib\site-packages\django\contrib\sessions\migrations\0003_auto_20220610_0826.py
    - Change Meta options on session
    - Alter field expire_date on session
    - Alter field session_data on session
    - Alter field session_key on session
Migrations for 'auth':
  venv2\lib\site-packages\django\contrib\auth\migrations\0014_auto_20220610_0826.py
    - Change Meta options on group
    - Change Meta options on permission
    - Change Meta options on user
    - Alter field name on group
    - Alter field permissions on group
    - Alter field codename on permission
    - Alter field content_type on permission
    - Alter field name on permission
    - Alter field date_joined on user
    - Alter field email on user
    - Alter field first_name on user
    - Alter field groups on user
    - Alter field is_active on user
    - Alter field is_staff on user
    - Alter field is_superuser on user
    - Alter field last_login on user
    - Alter field last_name on user
    - Alter field password on user
    - Alter field user_permissions on user
    - Alter field username on user
Migrations for 'admin':
  venv2\lib\site-packages\django\contrib\admin\migrations\0005_auto_20220610_0826.py
    - Change Meta options on logentry
    - Alter field action_flag on logentry
    - Alter field action_time on logentry
    - Alter field change_message on logentry
    - Alter field content_type on logentry
    - Alter field object_id on logentry
    - Alter field object_repr on logentry
    - Alter field user on logentry

These are the last three migrations generated on auth app:

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('auth', '0009_alter_user_last_name_max_length'),
    ]

    operations = [
        migrations.AlterField(
            model_name='group',
            name='name',
            field=models.CharField(max_length=150, unique=True, verbose_name='name'),
        ),
    ]

import sys

from django.core.management.color import color_style
from django.db import IntegrityError, migrations, transaction
from django.db.models import Q

WARNING = """
    A problem arose migrating proxy model permissions for {old} to {new}.

      Permission(s) for {new} already existed.
      Codenames Q: {query}

    Ensure to audit ALL permissions for {old} and {new}.
"""


def update_proxy_model_permissions(apps, schema_editor, reverse=False):
    """
    Update the content_type of proxy model permissions to use the ContentType
    of the proxy model.
    """
    style = color_style()
    Permission = apps.get_model('auth', 'Permission')
    ContentType = apps.get_model('contenttypes', 'ContentType')
    alias = schema_editor.connection.alias
    for Model in apps.get_models():
        opts = Model._meta
        if not opts.proxy:
            continue
        proxy_default_permissions_codenames = [
            '%s_%s' % (action, opts.model_name)
            for action in opts.default_permissions
        ]
        permissions_query = Q(codename__in=proxy_default_permissions_codenames)
        for codename, name in opts.permissions:
            permissions_query = permissions_query | Q(codename=codename, name=name)
        content_type_manager = ContentType.objects.db_manager(alias)
        concrete_content_type = content_type_manager.get_for_model(Model, for_concrete_model=True)
        proxy_content_type = content_type_manager.get_for_model(Model, for_concrete_model=False)
        old_content_type = proxy_content_type if reverse else concrete_content_type
        new_content_type = concrete_content_type if reverse else proxy_content_type
        try:
            with transaction.atomic(using=alias):
                Permission.objects.using(alias).filter(
                    permissions_query,
                    content_type=old_content_type,
                ).update(content_type=new_content_type)
        except IntegrityError:
            old = '{}_{}'.format(old_content_type.app_label, old_content_type.model)
            new = '{}_{}'.format(new_content_type.app_label, new_content_type.model)
            sys.stdout.write(style.WARNING(WARNING.format(old=old, new=new, query=permissions_query)))


def revert_proxy_model_permissions(apps, schema_editor):
    """
    Update the content_type of proxy model permissions to use the ContentType
    of the concrete model.
    """
    update_proxy_model_permissions(apps, schema_editor, reverse=True)


class Migration(migrations.Migration):
    dependencies = [
        ('auth', '0010_alter_group_name_max_length'),
        ('contenttypes', '0002_remove_content_type_name'),
    ]
    operations = [
        migrations.RunPython(update_proxy_model_permissions, revert_proxy_model_permissions),
    ]
from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('auth', '0011_update_proxy_permissions'),
    ]

    operations = [
        migrations.AlterField(
            model_name='user',
            name='first_name',
            field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
        ),
    ]

I can paste you the rest of the migrations if you need them.

Just in case, remember I was migrating from Django 2 to Django 3.

These are my installed pip packages, just to be sure any of these are not affecting it:

django==3.2.13
django-debug-toolbar
cx-Oracle==7.3.0
django-braces
django-tinymce==2.8.0
suds-jurko==0.6
django-bootstrap3
django-wkhtmltopdf==3.3.0
xlwt==1.3.0
ftfy==5.7
psycopg2
selenium
django-nocaptcha-recaptcha==0.0.20
zeep

Thanks you in advance,
Ismael.

Last edited 2 years ago by Ismael Jerez (previous) (diff)

comment:5 by Carlton Gibson, 2 years ago

Resolution: needsinfo
Status: newclosed

Thanks for the reply Ismael.

I test it removing these options so makemigrations generates new migrations like reverting verbose_name to English but anyway when I run migrate after that I still getting the message about changes not reflected in a migration...

There are two things here I can't quite make sense of:

I test it removing these options so makemigrations generates new migrations like reverting verbose_name to English...

So this implies the @no_translations decorator is not working, so there must be some customisation in your project affecting that.

... anyway when I run migrate after that I still getting the message about changes not reflected in a migration...

As per my previous comment, I can get both commands to apply translations but not just one of them, as you're seeing here. (Again, there must be a customisation somewhere in your setup, one would think)

Can you provide a sample project, created using startproject and adding just enough, to recreate the issue? Otherwise it's hard to see where the error could be.

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