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.
This commit is contained in:
parent
b616908ce1
commit
241deed259
|
@ -28,12 +28,16 @@ def _is_relevant_relation(relation, altered_field):
|
||||||
return altered_field.name in field.to_fields
|
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):
|
def _related_non_m2m_objects(old_field, new_field):
|
||||||
# Filter out m2m objects from reverse relations.
|
# Filter out m2m objects from reverse relations.
|
||||||
# Return (old_relation, new_relation) tuples.
|
# Return (old_relation, new_relation) tuples.
|
||||||
return zip(
|
return zip(
|
||||||
(obj for obj in old_field.model._meta.related_objects if _is_relevant_relation(obj, old_field)),
|
(obj for obj in _all_related_fields(old_field.model) 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(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
|
# Type alteration on primary key? Then we need to alter the column
|
||||||
# referring to us.
|
# referring to us.
|
||||||
rels_to_update = []
|
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))
|
rels_to_update.extend(_related_non_m2m_objects(old_field, new_field))
|
||||||
# Changed to become primary key?
|
# Changed to become primary key?
|
||||||
if self._field_became_primary_key(old_field, new_field):
|
if self._field_became_primary_key(old_field, new_field):
|
||||||
|
|
|
@ -1308,6 +1308,51 @@ class OperationTests(OperationTestBase):
|
||||||
operation.database_backwards("test_alflpkfk", editor, new_state, project_state)
|
operation.database_backwards("test_alflpkfk", editor, new_state, project_state)
|
||||||
assertIdTypeEqualsFkType()
|
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):
|
def test_alter_field_reloads_state_on_fk_target_changes(self):
|
||||||
"""
|
"""
|
||||||
If AlterField doesn't reload state appropriately, the second AlterField
|
If AlterField doesn't reload state appropriately, the second AlterField
|
||||||
|
|
Loading…
Reference in New Issue