diff options
| author | Tim Graham <timograham@gmail.com> | 2017-06-30 14:51:08 -0400 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-12-01 19:09:36 -0500 |
| commit | b8a2f3c2d66aa15af4be745a576609b958a853c0 (patch) | |
| tree | 02409c44ede8a96005a985406a76daeca782077b /django | |
| parent | f319e7abad145ddcb1017293b5cdb7e09a92ee85 (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.py | 14 | ||||
| -rw-r--r-- | django/db/migrations/operations/fields.py | 17 | ||||
| -rw-r--r-- | django/db/migrations/operations/utils.py | 9 |
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 |
