Opened 49 minutes ago
#37495 new Bug
makemigrations generates wrong dependencies when a new model has an FK to a renamed model in a different app
| Reported by: | Gavin Wahl | Owned by: | |
|---|---|---|---|
| Component: | Migrations | Version: | 6.1 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
When adding a new model with a ForeignKey to a model that is also being renamed in a different app, makemigrations generates dependencies that put the new model with a FK to renamed model's new name before renaming the referenced model and migrate fails.
- App
shophasclass T0(models.Model): pass. Runmakemigrations.Migrations for 'shop': shop/migrations/0001_initial.py + Create model T0
- Rename
T0toT0r, and add appzetawith:class T1(models.Model): t0 = models.ForeignKey("shop.T0r", models.CASCADE)
- Run
makemigrationsand answer yes to the rename.Migrations for 'zeta': zeta/migrations/0001_initial.py + Create model T1 Migrations for 'shop': shop/migrations/0002_rename_t0_t0r.py ~ Rename model T0 to T0r
- Run
migrate.
The dependencies come out backwards. shop/0002_rename_t0_t0r depends on ('zeta', '0001_initial'), and zeta/0001_initial depends only on ('shop', '0001_initial') even though its foreign key points at shop.t0r:
Applying zeta.0001_initial... ValueError: Related model 'shop.t0r' cannot be resolved
This is a regression in Django 2.0 introduced by #28493's change to generate_renamed_models(). The same steps work on 1.11 and fail on 2.0 and later. This is essentially the reverse structure of #28493: its foreign key used the old name and had to run before the rename. Here it uses the new name and has to run after.
Changing the dependency in zeta.0001_initial to shop.0002_rename_t0_t0r and removing the zeta.0001_initial dependency from shop.0002_rename_t0_t0r makes it work. That's the order generated by Django 1.11, and the only order that works migrating forwards and back.
I haven't had time to investigate further, but I played around a little bit with this patch and it appears to work:
-
django/db/migrations/autodetector.py
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 907988d..df0a9ef 100644
a b class MigrationAutodetector: 596 596 rem_model_state, model_state 597 597 ): 598 598 dependencies = [] 599 # A relation to the old name must be created 600 # before the rename. A relation added in this run 601 # refers to the new name and is created after it. 602 old_relations = self.from_state.relations.get( 603 (rem_app_label, rem_model_name), {} 604 ) 599 605 fields = list(model_state.fields.values()) + [ 600 606 field.remote_field 601 for relat ions in self.to_state.relations[607 for related_key, relations in self.to_state.relations[ 602 608 app_label, model_name 603 ].values() 604 for field in relations.values() 609 ].items() 610 for field_name, field in relations.items() 611 if field_name in old_relations.get(related_key, {}) 605 612 ] 606 613 for field in fields: 607 614 if field.is_relation:
I found this with the same fuzzer as #37493, which tests a migration extension that manages database views with Django migrations. Its random changes include renaming a model and adding a model with a foreign key to an existing one, across multiple apps. One change it produced failed to migrate. I then produced a minimal repro using Django alone.
This looks semi-related to #37477, but the proposed patch only applies to changes in a single app at a time.