[4.0.x] 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 325d7710ce.
Backport of e972620ada from main
This commit is contained in:
Mariusz Felisiak 2022-01-27 18:51:39 +01:00
parent f4de87038e
commit 7c2d4d943b
3 changed files with 74 additions and 1 deletions

View File

@ -30,7 +30,9 @@ def _is_relevant_relation(relation, altered_field):
def _all_related_fields(model): 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): def _related_non_m2m_objects(old_field, new_field):

View File

@ -28,3 +28,7 @@ Bugfixes
* Fixed a regression in Django 4.0 that caused incorrect * Fixed a regression in Django 4.0 that caused incorrect
:attr:`.ModelAdmin.radio_fields` layout in the admin (:ticket:`33407`). :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`).

View File

@ -1600,6 +1600,73 @@ class OperationTests(OperationTestBase):
(f'{app_label}_shetlandpony', 'pony_ptr_id'), (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') @skipUnlessDBFeature('supports_foreign_keys')
def test_alter_field_reloads_state_on_fk_with_to_field_target_type_change(self): def test_alter_field_reloads_state_on_fk_with_to_field_target_type_change(self):
app_label = 'test_alflrsfkwtflttc' app_label = 'test_alflrsfkwtflttc'