Browse Source

Fixed #33462 -- Fixed migration crash when altering type of primary key with MTI and foreign key.

This prevents duplicated operations when altering type of primary key
with MTI and foreign key. Previously, a foreign key to the base model
was added twice, once directly and once by the inheritance model.

Thanks bcail for the report.

Regression in 325d7710ce9f6155bb55610ad6b4580d31263557.
Mariusz Felisiak 3 years ago
parent
commit
e972620ada
3 changed files with 74 additions and 1 deletions
  1. 3 1
      django/db/backends/base/schema.py
  2. 4 0
      docs/releases/4.0.2.txt
  3. 67 0
      tests/migrations/test_operations.py

+ 3 - 1
django/db/backends/base/schema.py

@@ -30,7 +30,9 @@ def _is_relevant_relation(relation, altered_field):
 
 
 def _all_related_fields(model):
-    return model._meta._get_fields(forward=False, reverse=True, include_hidden=True)
+    return model._meta._get_fields(
+        forward=False, reverse=True, include_hidden=True, include_parents=False,
+    )
 
 
 def _related_non_m2m_objects(old_field, new_field):

+ 4 - 0
docs/releases/4.0.2.txt

@@ -28,3 +28,7 @@ Bugfixes
 
 * Fixed a regression in Django 4.0 that caused incorrect
   :attr:`.ModelAdmin.radio_fields` layout in the admin (:ticket:`33407`).
+
+* Fixed a duplicate operation regression in Django 4.0 that caused a migration
+  crash when altering a primary key type for a concrete parent model referenced
+  by a foreign key (:ticket:`33462`).

+ 67 - 0
tests/migrations/test_operations.py

@@ -1622,6 +1622,73 @@ class OperationTests(OperationTestBase):
                 (f'{app_label}_shetlandpony', 'pony_ptr_id'),
             )
 
+    def test_alter_field_pk_mti_and_fk_to_base(self):
+        app_label = 'test_alflpkmtiftb'
+        project_state = self.set_up_test_model(
+            app_label, mti_model=True, related_model=True,
+        )
+        operation = migrations.AlterField(
+            'Pony',
+            'id',
+            models.BigAutoField(primary_key=True),
+        )
+        new_state = project_state.clone()
+        operation.state_forwards(app_label, new_state)
+        self.assertIsInstance(
+            new_state.models[app_label, 'pony'].fields['id'],
+            models.BigAutoField,
+        )
+
+        def _get_column_id_type(cursor, table, column):
+            return [
+                c.type_code
+                for c in connection.introspection.get_table_description(
+                    cursor,
+                    f'{app_label}_{table}',
+                )
+                if c.name == column
+            ][0]
+
+        def assertIdTypeEqualsMTIFkType():
+            with connection.cursor() as cursor:
+                parent_id_type = _get_column_id_type(cursor, 'pony', 'id')
+                fk_id_type = _get_column_id_type(cursor, 'rider', 'pony_id')
+                child_id_type = _get_column_id_type(cursor, 'shetlandpony', 'pony_ptr_id')
+            self.assertEqual(parent_id_type, child_id_type)
+            self.assertEqual(parent_id_type, fk_id_type)
+
+        assertIdTypeEqualsMTIFkType()
+        # Alter primary key.
+        with connection.schema_editor() as editor:
+            operation.database_forwards(app_label, editor, project_state, new_state)
+        assertIdTypeEqualsMTIFkType()
+        if connection.features.supports_foreign_keys:
+            self.assertFKExists(
+                f'{app_label}_shetlandpony',
+                ['pony_ptr_id'],
+                (f'{app_label}_pony', 'id'),
+            )
+            self.assertFKExists(
+                f'{app_label}_rider',
+                ['pony_id'],
+                (f'{app_label}_pony', 'id'),
+            )
+        # Reversal.
+        with connection.schema_editor() as editor:
+            operation.database_backwards(app_label, editor, new_state, project_state)
+        assertIdTypeEqualsMTIFkType()
+        if connection.features.supports_foreign_keys:
+            self.assertFKExists(
+                f'{app_label}_shetlandpony',
+                ['pony_ptr_id'],
+                (f'{app_label}_pony', 'id'),
+            )
+            self.assertFKExists(
+                f'{app_label}_rider',
+                ['pony_id'],
+                (f'{app_label}_pony', 'id'),
+            )
+
     @skipUnlessDBFeature('supports_foreign_keys')
     def test_alter_field_reloads_state_on_fk_with_to_field_target_type_change(self):
         app_label = 'test_alflrsfkwtflttc'