Opened 5 months ago

Last modified 3 days ago

#29000 assigned Bug

RenameModel does not rename M2M column when run after AlterField/RenameField

Reported by: David Nelson Adamec Owned by: Jeff
Component: Migrations Version: 2.0
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by David Nelson Adamec)

Encountered this on a project at work. I have some models like so:

class ModelA(models.Model):
    name_renamed = models.CharField(max_length=10)

class ModelBRenamed(models.Model):
    a = models.ForeignKey(ModelA, null=True)

class ModelC(models.Model):
    b_set = models.ManyToManyField(ModelBRenamed)

I have a migration 0002_renamefield to rename a field on ModelA:

class Migration(migrations.Migration):

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

    operations = [
        migrations.RenameField(
            model_name='modela',
            old_name='name',
            new_name='name_renamed',
        ),
    ]

Then a migration 0003_renamemodel to rename ModelB:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_renamefield'),
    ]

    operations = [
        migrations.RenameModel(
            old_name='ModelB',
            new_name='ModelBRenamed',
        ),
    ]

If the two migrations are run together, then the modelb_id column in myapp_modelc_b_set will not be renamed to modelbrenamed_id. However, if I run the migrations one at a time, the column will be renamed as expected.

I think this is related to #27737. When ModelA is reloaded in the RenameField migration, ModelB is reloaded due to its foreign key but is missing its relationship to ModelC. When the RenameModel migration is run, ModelC is not included in ModelB's related_objects, so the through table's column is not renamed.

I've reproduced this using Django 1.11 on either MySQL or SQLite.

Change History (10)

comment:1 Changed 5 months ago by Tim Graham

Component: UncategorizedMigrations
Type: UncategorizedBug

Please test with Django 2.0 as there are changes there which may resolve this.

comment:2 in reply to:  1 Changed 5 months ago by David Nelson Adamec

Sorry, should have just done that from the start. Tested in Django 2.0.1, and the issue still occurs.

comment:3 Changed 5 months ago by David Nelson Adamec

Description: modified (diff)
Version: 1.112.0

comment:4 Changed 5 months ago by Tim Graham

Triage Stage: UnreviewedAccepted

Thanks, good bug report. I can also reproduce on PostgreSQL with Django master (e2908ecb3e20a629be801ddb826c474f7e7023ea).

comment:5 Changed 4 months ago by shangdahao

Owner: changed from nobody to shangdahao
Status: newassigned

comment:6 Changed 4 months ago by David Nelson Adamec

After doing a little more digging, it appears that the reload_model with delay=True in the RenameField migration is the culprit. Because of the delay flag, only the original model and its directly related models (ModelA and ModelB in the example) are reloaded, so ModelC is untouched and its b_set field continues to point at the old version of ModelB that's no longer registered. Since ModelC isn't pointing at the new ModelB, it's not included in ModelB's related_objects.

I'm not sure what the performance implication would be if delayed loading were removed from RenameField/AlterField, but I'm sure it's not good. Perhaps there's another clever solution out there, but I feel a bit out of my depth. Here's the workaround I'm using for the time being.

class RenameModelWorkaround(migrations.RenameModel):
    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        from_state.reload_model(app_label, self.old_name_lower)
        super(RenameModelWorkaround, self).database_forwards(
            app_label, schema_editor, from_state, to_state)

comment:7 Changed 4 months ago by shangdahao

Owner: shangdahao deleted
Status: assignednew

comment:8 Changed 10 days ago by Jeff

Owner: set to Jeff
Status: newassigned

Unsurprisingly, this bug also breaks backwards migrations, as the names in the bridge table don't match expected values.

For the models/migrations in the tickets description, backwards migrations produces the expected result:

django.db.utils.OperationalError: (1054, "Unknown column 'modelbrenamed_id' in 'temp_modelc_b_set'")

David Nelson Adamec was also correct, removing 'delay=delay' from the state.reload_model() call in state_forwards() did fix this particular issue, but I am unsure as to what downsides are associated with removing the delay. I am looking into other solutions now.

Last edited 10 days ago by Jeff (previous) (diff)

comment:10 Changed 3 days ago by Jeff

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