Fixed #27310 -- Stopped rendering apps in RenameModel.state_forwards.
Thanks Tim for the review.
This commit is contained in:
parent
8e3a72f4fb
commit
ecd625e830
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.migrations.operations.base import Operation
|
from django.db.migrations.operations.base import Operation
|
||||||
from django.db.migrations.state import ModelState
|
from django.db.migrations.state import ModelState
|
||||||
|
from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
|
||||||
from django.db.models.options import normalize_together
|
from django.db.models.options import normalize_together
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
@ -277,70 +278,50 @@ class RenameModel(ModelOperation):
|
||||||
kwargs
|
kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_model_tuple(self, remote_model, app_label, model_name):
|
||||||
|
if remote_model == RECURSIVE_RELATIONSHIP_CONSTANT:
|
||||||
|
return app_label, model_name.lower()
|
||||||
|
elif '.' in remote_model:
|
||||||
|
return tuple(remote_model.lower().split('.'))
|
||||||
|
else:
|
||||||
|
return app_label, remote_model.lower()
|
||||||
|
|
||||||
def state_forwards(self, app_label, state):
|
def state_forwards(self, app_label, state):
|
||||||
# In cases where state doesn't have rendered apps, prevent subsequent
|
# Add a new model.
|
||||||
# reload_model() calls from rendering models for performance
|
renamed_model = state.models[app_label, self.old_name_lower].clone()
|
||||||
# reasons. This method should be refactored to avoid relying on
|
renamed_model.name = self.new_name
|
||||||
# state.apps (#27310).
|
state.models[app_label, self.new_name_lower] = renamed_model
|
||||||
reset_apps = 'apps' not in state.__dict__
|
# Repoint all fields pointing to the old model to the new one.
|
||||||
apps = state.apps
|
old_model_tuple = app_label, self.old_name_lower
|
||||||
model = apps.get_model(app_label, self.old_name)
|
new_remote_model = '%s.%s' % (app_label, self.new_name)
|
||||||
model._meta.apps = apps
|
for (model_app_label, model_name), model_state in state.models.items():
|
||||||
# Get all of the related objects we need to repoint
|
model_changed = False
|
||||||
all_related_objects = (
|
for index, (name, field) in enumerate(model_state.fields):
|
||||||
f for f in model._meta.get_fields(include_hidden=True)
|
changed_field = None
|
||||||
if f.auto_created and not f.concrete and (not f.hidden or f.many_to_many)
|
remote_field = field.remote_field
|
||||||
|
if remote_field:
|
||||||
|
remote_model_tuple = self._get_model_tuple(
|
||||||
|
remote_field.model, model_app_label, model_name
|
||||||
)
|
)
|
||||||
if reset_apps:
|
if remote_model_tuple == old_model_tuple:
|
||||||
del state.__dict__['apps']
|
changed_field = field.clone()
|
||||||
# Rename the model
|
changed_field.remote_field.model = new_remote_model
|
||||||
state.models[app_label, self.new_name_lower] = state.models[app_label, self.old_name_lower]
|
through_model = getattr(remote_field, 'through', None)
|
||||||
state.models[app_label, self.new_name_lower].name = self.new_name
|
if through_model:
|
||||||
|
through_model_tuple = self._get_model_tuple(
|
||||||
|
through_model, model_app_label, model_name
|
||||||
|
)
|
||||||
|
if through_model_tuple == old_model_tuple:
|
||||||
|
if changed_field is None:
|
||||||
|
changed_field = field.clone()
|
||||||
|
changed_field.remote_field.through = new_remote_model
|
||||||
|
if changed_field:
|
||||||
|
model_state.fields[index] = name, changed_field
|
||||||
|
model_changed = True
|
||||||
|
if model_changed:
|
||||||
|
state.reload_model(model_app_label, model_name)
|
||||||
|
# Remove the old model.
|
||||||
state.remove_model(app_label, self.old_name_lower)
|
state.remove_model(app_label, self.old_name_lower)
|
||||||
# Repoint the FKs and M2Ms pointing to us
|
|
||||||
for related_object in all_related_objects:
|
|
||||||
if related_object.model is not model:
|
|
||||||
# The model being renamed does not participate in this relation
|
|
||||||
# directly. Rather, a superclass does.
|
|
||||||
continue
|
|
||||||
# Use the new related key for self referential related objects.
|
|
||||||
if related_object.related_model == model:
|
|
||||||
related_key = (app_label, self.new_name_lower)
|
|
||||||
else:
|
|
||||||
related_key = (
|
|
||||||
related_object.related_model._meta.app_label,
|
|
||||||
related_object.related_model._meta.model_name,
|
|
||||||
)
|
|
||||||
new_fields = []
|
|
||||||
for name, field in state.models[related_key].fields:
|
|
||||||
if name == related_object.field.name:
|
|
||||||
field = field.clone()
|
|
||||||
field.remote_field.model = "%s.%s" % (app_label, self.new_name)
|
|
||||||
new_fields.append((name, field))
|
|
||||||
state.models[related_key].fields = new_fields
|
|
||||||
state.reload_model(*related_key)
|
|
||||||
# Repoint M2Ms with through pointing to us
|
|
||||||
related_models = {
|
|
||||||
f.remote_field.model for f in model._meta.fields
|
|
||||||
if getattr(f.remote_field, 'model', None)
|
|
||||||
}
|
|
||||||
model_name = '%s.%s' % (app_label, self.old_name)
|
|
||||||
for related_model in related_models:
|
|
||||||
if related_model == model:
|
|
||||||
related_key = (app_label, self.new_name_lower)
|
|
||||||
else:
|
|
||||||
related_key = (related_model._meta.app_label, related_model._meta.model_name)
|
|
||||||
new_fields = []
|
|
||||||
changed = False
|
|
||||||
for name, field in state.models[related_key].fields:
|
|
||||||
if field.is_relation and field.many_to_many and field.remote_field.through == model_name:
|
|
||||||
field = field.clone()
|
|
||||||
field.remote_field.through = '%s.%s' % (app_label, self.new_name)
|
|
||||||
changed = True
|
|
||||||
new_fields.append((name, field))
|
|
||||||
if changed:
|
|
||||||
state.models[related_key].fields = new_fields
|
|
||||||
state.reload_model(*related_key)
|
|
||||||
state.reload_model(app_label, self.new_name_lower)
|
state.reload_model(app_label, self.new_name_lower)
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
|
|
|
@ -624,9 +624,11 @@ class OperationTests(OperationTestBase):
|
||||||
self.assertIn(("test_rmwsrf", "horserider"), new_state.models)
|
self.assertIn(("test_rmwsrf", "horserider"), new_state.models)
|
||||||
# Remember, RenameModel also repoints all incoming FKs and M2Ms
|
# Remember, RenameModel also repoints all incoming FKs and M2Ms
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"test_rmwsrf.HorseRider",
|
'self',
|
||||||
new_state.models["test_rmwsrf", "horserider"].fields[2][1].remote_field.model
|
new_state.models["test_rmwsrf", "horserider"].fields[2][1].remote_field.model
|
||||||
)
|
)
|
||||||
|
HorseRider = new_state.apps.get_model('test_rmwsrf', 'horserider')
|
||||||
|
self.assertIs(HorseRider._meta.get_field('horserider').remote_field.model, HorseRider)
|
||||||
# Test the database alteration
|
# Test the database alteration
|
||||||
self.assertTableExists("test_rmwsrf_rider")
|
self.assertTableExists("test_rmwsrf_rider")
|
||||||
self.assertTableNotExists("test_rmwsrf_horserider")
|
self.assertTableNotExists("test_rmwsrf_horserider")
|
||||||
|
|
Loading…
Reference in New Issue