diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 896ea3d517..da3ec1879d 100644 --- a/django/db/backends/base/schema.py +++ b/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): diff --git a/docs/releases/4.0.2.txt b/docs/releases/4.0.2.txt index 8227d98622..af0f728c54 100644 --- a/docs/releases/4.0.2.txt +++ b/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`). diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 11961a1f40..5e2edc5297 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1600,6 +1600,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'