summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2017-06-30 14:51:08 -0400
committerTim Graham <timograham@gmail.com>2017-12-01 19:09:36 -0500
commitb8a2f3c2d66aa15af4be745a576609b958a853c0 (patch)
tree02409c44ede8a96005a985406a76daeca782077b /django
parentf319e7abad145ddcb1017293b5cdb7e09a92ee85 (diff)
[1.11.x] Fixed #28305 -- Fixed "Cannot change column 'x': used in a foreign key constraint" crash on MySQL with a sequence of AlterField or RenameField operations.
Regression in 45ded053b1f4320284aa5dac63052f6d1baefea9. Backport of c3e0adcad8d8ba94b33cabd137056166ed36dae0 from master
Diffstat (limited to 'django')
-rw-r--r--django/db/backends/base/schema.py14
-rw-r--r--django/db/migrations/operations/fields.py17
-rw-r--r--django/db/migrations/operations/utils.py9
3 files changed, 32 insertions, 8 deletions
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index 56102ff27b..2c9e016d11 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -543,9 +543,15 @@ class BaseDatabaseSchemaEditor(object):
))
for constraint_name in constraint_names:
self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name))
- # Drop incoming FK constraints if we're a primary key and things are going
- # to change.
- if old_field.primary_key and new_field.primary_key and old_type != new_type:
+ # Drop incoming FK constraints if the field is a primary key or unique,
+ # which might be a to_field target, and things are going to change.
+ drop_foreign_keys = (
+ (
+ (old_field.primary_key and new_field.primary_key) or
+ (old_field.unique and new_field.unique)
+ ) and old_type != new_type
+ )
+ if drop_foreign_keys:
# '_meta.related_field' also contains M2M reverse fields, these
# will be filtered out
for _old_rel, new_rel in _related_non_m2m_objects(old_field, new_field):
@@ -772,7 +778,7 @@ class BaseDatabaseSchemaEditor(object):
new_field.db_constraint):
self.execute(self._create_fk_sql(model, new_field, "_fk_%(to_table)s_%(to_column)s"))
# Rebuild FKs that pointed to us if we previously had to drop them
- if old_field.primary_key and new_field.primary_key and old_type != new_type:
+ if drop_foreign_keys:
for rel in new_field.model._meta.related_objects:
if not rel.many_to_many and rel.field.db_constraint:
self.execute(self._create_fk_sql(rel.related_model, rel.field, "_fk"))
diff --git a/django/db/migrations/operations/fields.py b/django/db/migrations/operations/fields.py
index a458397fd4..0d5e690472 100644
--- a/django/db/migrations/operations/fields.py
+++ b/django/db/migrations/operations/fields.py
@@ -5,6 +5,7 @@ from django.db.models.fields import NOT_PROVIDED
from django.utils.functional import cached_property
from .base import Operation
+from .utils import is_referenced_by_foreign_key
class FieldOperation(Operation):
@@ -201,8 +202,12 @@ class AlterField(FieldOperation):
]
# TODO: investigate if old relational fields must be reloaded or if it's
# sufficient if the new field is (#27737).
- # Delay rendering of relationships if it's not a relational field
- delay = not field.is_relation
+ # Delay rendering of relationships if it's not a relational field and
+ # not referenced by a foreign key.
+ delay = (
+ not field.is_relation and
+ not is_referenced_by_foreign_key(state, self.model_name_lower, self.field, self.name)
+ )
state.reload_model(app_label, self.model_name_lower, delay=delay)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
@@ -275,8 +280,12 @@ class RenameField(FieldOperation):
for index, (name, field) in enumerate(fields):
if name == self.old_name:
fields[index] = (self.new_name, field)
- # Delay rendering of relationships if it's not a relational field.
- delay = not field.is_relation
+ # Delay rendering of relationships if it's not a relational
+ # field and not referenced by a foreign key.
+ delay = (
+ not field.is_relation and
+ not is_referenced_by_foreign_key(state, self.model_name_lower, field, self.name)
+ )
break
else:
raise FieldDoesNotExist(
diff --git a/django/db/migrations/operations/utils.py b/django/db/migrations/operations/utils.py
new file mode 100644
index 0000000000..af23ea9563
--- /dev/null
+++ b/django/db/migrations/operations/utils.py
@@ -0,0 +1,9 @@
+def is_referenced_by_foreign_key(state, model_name_lower, field, field_name):
+ for state_app_label, state_model in state.models:
+ for _, f in state.models[state_app_label, state_model].fields:
+ if (f.related_model and
+ '%s.%s' % (state_app_label, model_name_lower) == f.related_model.lower() and
+ hasattr(f, 'to_fields')):
+ if (f.to_fields[0] is None and field.primary_key) or field_name in f.to_fields:
+ return True
+ return False