[3.0.x] Fixed #30591 -- Fixed recreation of foreign key constraints on MySQL when altering type of referenced unique field.

Thanks Mariusz Felisiak for tests and Matthijs Kooijman for
investigation and initial patch.

Backport of 241deed259 from master
This commit is contained in:
Adnan Umer 2019-08-22 14:46:17 +05:00 committed by Mariusz Felisiak
parent 1520df3f57
commit acb11725f8
2 changed files with 52 additions and 3 deletions

View File

@ -28,12 +28,16 @@ def _is_relevant_relation(relation, altered_field):
return altered_field.name in field.to_fields
def _all_related_fields(model):
return model._meta._get_fields(forward=False, reverse=True, include_hidden=True)
def _related_non_m2m_objects(old_field, new_field):
# Filter out m2m objects from reverse relations.
# Return (old_relation, new_relation) tuples.
return zip(
(obj for obj in old_field.model._meta.related_objects if _is_relevant_relation(obj, old_field)),
(obj for obj in new_field.model._meta.related_objects if _is_relevant_relation(obj, new_field))
(obj for obj in _all_related_fields(old_field.model) if _is_relevant_relation(obj, old_field)),
(obj for obj in _all_related_fields(new_field.model) if _is_relevant_relation(obj, new_field)),
)
@ -753,7 +757,7 @@ class BaseDatabaseSchemaEditor:
# Type alteration on primary key? Then we need to alter the column
# referring to us.
rels_to_update = []
if old_field.primary_key and new_field.primary_key and old_type != new_type:
if drop_foreign_keys:
rels_to_update.extend(_related_non_m2m_objects(old_field, new_field))
# Changed to become primary key?
if self._field_became_primary_key(old_field, new_field):

View File

@ -1308,6 +1308,51 @@ class OperationTests(OperationTestBase):
operation.database_backwards("test_alflpkfk", editor, new_state, project_state)
assertIdTypeEqualsFkType()
@skipUnlessDBFeature('supports_foreign_keys')
def test_alter_field_reloads_state_on_fk_with_to_field_target_type_change(self):
app_label = 'alter_field_reloads_state_on_fk_with_to_field_target_type_change'
project_state = self.apply_operations(app_label, ProjectState(), operations=[
migrations.CreateModel('Rider', fields=[
('id', models.AutoField(primary_key=True)),
('code', models.PositiveIntegerField(unique=True)),
]),
migrations.CreateModel('Pony', fields=[
('id', models.AutoField(primary_key=True)),
('rider', models.ForeignKey('%s.Rider' % app_label, models.CASCADE, to_field='code')),
]),
])
operation = migrations.AlterField(
'Rider',
'code',
models.CharField(max_length=100, unique=True),
)
self.apply_operations(app_label, project_state, operations=[operation])
@skipUnlessDBFeature('supports_foreign_keys')
def test_alter_field_reloads_state_on_fk_with_to_field_related_name_target_type_change(self):
app_label = 'alter_field_reloads_state_on_fk_with_to_field_rn_target_type_change'
project_state = self.apply_operations(app_label, ProjectState(), operations=[
migrations.CreateModel('Rider', fields=[
('id', models.AutoField(primary_key=True)),
('code', models.PositiveIntegerField(unique=True)),
]),
migrations.CreateModel('Pony', fields=[
('id', models.AutoField(primary_key=True)),
('rider', models.ForeignKey(
'%s.Rider' % app_label,
models.CASCADE,
to_field='code',
related_name='+',
)),
]),
])
operation = migrations.AlterField(
'Rider',
'code',
models.CharField(max_length=100, unique=True),
)
self.apply_operations(app_label, project_state, operations=[operation])
def test_alter_field_reloads_state_on_fk_target_changes(self):
"""
If AlterField doesn't reload state appropriately, the second AlterField