summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdnan Umer <u.adnan@outlook.com>2019-08-22 14:46:17 +0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-09-11 11:21:08 +0200
commit241deed2590bcb1d8c45271d44c86eaedfb57119 (patch)
treed3fb4a1bead27aa5ed2c936d7d1f3f656a039937
parentb616908ce1ad46457c4b49fe098320bb27e09581 (diff)
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.
-rw-r--r--django/db/backends/base/schema.py10
-rw-r--r--tests/migrations/test_operations.py45
2 files changed, 52 insertions, 3 deletions
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index 3a0dc39238..98afbcc05a 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -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):
diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py
index 33b76984d4..64e29194ca 100644
--- a/tests/migrations/test_operations.py
+++ b/tests/migrations/test_operations.py
@@ -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