summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2014-06-07 18:17:31 -0700
committerAndrew Godwin <andrew@aeracode.org>2014-06-07 18:18:02 -0700
commit4ce7a6bc84c68406e39f48550434faeef3277eba (patch)
treed8a6b7ebd627ce3b8a0f9815768aea013f81b942 /django
parent1e84d261d618c11c939028ba37db09bae81b1eef (diff)
Fixed #22750, #22248: Model renaming now also alters field FKs
Diffstat (limited to 'django')
-rw-r--r--django/db/migrations/autodetector.py16
-rw-r--r--django/db/migrations/operations/models.py46
-rw-r--r--django/db/migrations/state.py10
3 files changed, 56 insertions, 16 deletions
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index 469fd1199f..3e3dc242d2 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -580,11 +580,21 @@ class MigrationAutodetector(object):
for app_label, model_name, field_name in sorted(self.old_field_keys.intersection(self.new_field_keys)):
# Did the field change?
old_model_name = self.renamed_models.get((app_label, model_name), model_name)
- old_model_state = self.from_state.models[app_label, old_model_name]
new_model_state = self.to_state.models[app_label, model_name]
old_field_name = self.renamed_fields.get((app_label, model_name, field_name), field_name)
- old_field_dec = self.deep_deconstruct(old_model_state.get_field_by_name(old_field_name))
- new_field_dec = self.deep_deconstruct(new_model_state.get_field_by_name(field_name))
+ old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field_by_name(old_field_name)[0]
+ new_field = self.new_apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
+ # Implement any model renames on relations; these are handled by RenameModel
+ # so we need to exclude them from the comparison
+ if hasattr(new_field, "rel") and getattr(new_field.rel, "to", None):
+ rename_key = (
+ new_field.rel.to._meta.app_label,
+ new_field.rel.to._meta.object_name.lower(),
+ )
+ if rename_key in self.renamed_models:
+ new_field.rel.to = old_field.rel.to
+ old_field_dec = self.deep_deconstruct(old_field)
+ new_field_dec = self.deep_deconstruct(new_field)
if old_field_dec != new_field_dec:
self.add_operation(
app_label,
diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py
index 06ad280be6..abd7ecd8f1 100644
--- a/django/db/migrations/operations/models.py
+++ b/django/db/migrations/operations/models.py
@@ -113,9 +113,28 @@ class RenameModel(Operation):
self.new_name = new_name
def state_forwards(self, app_label, state):
+ # Get all of the related objects we need to repoint
+ apps = state.render(skip_cache=True)
+ model = apps.get_model(app_label, self.old_name)
+ related_objects = model._meta.get_all_related_objects()
+ related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
+ # Rename the model
state.models[app_label, self.new_name.lower()] = state.models[app_label, self.old_name.lower()]
state.models[app_label, self.new_name.lower()].name = self.new_name
del state.models[app_label, self.old_name.lower()]
+ # Repoint the FKs and M2Ms pointing to us
+ for related_object in (related_objects + related_m2m_objects):
+ related_key = (
+ related_object.model._meta.app_label,
+ related_object.model._meta.object_name.lower(),
+ )
+ new_fields = []
+ for name, field in state.models[related_key].fields:
+ if name == related_object.field.name:
+ field = field.clone()
+ field.rel.to = "%s.%s" % (app_label, self.new_name)
+ new_fields.append((name, field))
+ state.models[related_key].fields = new_fields
def database_forwards(self, app_label, schema_editor, from_state, to_state):
old_apps = from_state.render()
@@ -123,23 +142,30 @@ class RenameModel(Operation):
old_model = old_apps.get_model(app_label, self.old_name)
new_model = new_apps.get_model(app_label, self.new_name)
if router.allow_migrate(schema_editor.connection.alias, new_model):
+ # Move the main table
schema_editor.alter_db_table(
new_model,
old_model._meta.db_table,
new_model._meta.db_table,
)
+ # Alter the fields pointing to us
+ related_objects = old_model._meta.get_all_related_objects()
+ related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
+ for related_object in (related_objects + related_m2m_objects):
+ to_field = new_apps.get_model(
+ related_object.model._meta.app_label,
+ related_object.model._meta.object_name.lower(),
+ )._meta.get_field_by_name(related_object.field.name)[0]
+ schema_editor.alter_field(
+ related_object.model,
+ related_object.field,
+ to_field,
+ )
def database_backwards(self, app_label, schema_editor, from_state, to_state):
- old_apps = from_state.render()
- new_apps = to_state.render()
- old_model = old_apps.get_model(app_label, self.new_name)
- new_model = new_apps.get_model(app_label, self.old_name)
- if router.allow_migrate(schema_editor.connection.alias, new_model):
- schema_editor.alter_db_table(
- new_model,
- old_model._meta.db_table,
- new_model._meta.db_table,
- )
+ self.new_name, self.old_name = self.old_name, self.new_name
+ self.database_forwards(app_label, schema_editor, from_state, to_state)
+ self.new_name, self.old_name = self.old_name, self.new_name
def references_model(self, name, app_label=None):
return (
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index 43454fd99c..40bb279cfc 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -38,9 +38,9 @@ class ProjectState(object):
real_apps=self.real_apps,
)
- def render(self, include_real=None, ignore_swappable=False):
+ def render(self, include_real=None, ignore_swappable=False, skip_cache=False):
"Turns the project state into actual models in a new Apps"
- if self.apps is None:
+ if self.apps is None or skip_cache:
# Any apps in self.real_apps should have all their models included
# in the render. We don't use the original model instances as there
# are some variables that refer to the Apps object.
@@ -87,7 +87,11 @@ class ProjectState(object):
))
else:
do_pending_lookups(model)
- return self.apps
+ try:
+ return self.apps
+ finally:
+ if skip_cache:
+ self.apps = None
@classmethod
def from_apps(cls, apps):