Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions django/db/migrations/autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,8 @@ def generate_renamed_models(self):

removed_models = self.old_model_keys - self.new_model_keys
for rem_app_label, rem_model_name in removed_models:
if rem_app_label == app_label:
is_moving = not bool(rem_app_label == app_label)
if True or rem_app_label == app_label: # TODO remove if and reduce indent
rem_model_state = self.from_state.models[rem_app_label, rem_model_name]
rem_model_fields_def = self.only_relation_agnostic_fields(rem_model_state.fields)
if model_fields_def == rem_model_fields_def:
Expand All @@ -487,11 +488,27 @@ def generate_renamed_models(self):
for field in model_opts.get_fields():
if field.is_relation:
dependencies.extend(self._get_dependencies_for_foreign_key(field))
if is_moving:
self.add_operation(
app_label,
operations.MoveModelPlaceholder(
old_name=rem_model_state.name,
new_name=model_state.name,
new_app_label=app_label if is_moving else None,
),
dependencies=[
(rem_app_label, rem_model_name, None, True),
],
)
self.from_state.add_model(model_state)
dependencies.append((app_label, model_name, None, True))

self.add_operation(
app_label,
rem_app_label,
operations.RenameModel(
old_name=rem_model_state.name,
new_name=model_state.name,
new_app_label=app_label if is_moving else None,
),
dependencies=dependencies,
)
Expand Down
72 changes: 64 additions & 8 deletions django/db/migrations/operations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,12 @@ def describe(self):


class RenameModel(ModelOperation):
"""Rename a model."""
"""Rename a model. ( with move model to new_app capability )"""

def __init__(self, old_name, new_name):
def __init__(self, old_name, new_name, new_app_label=None):
self.old_name = old_name
self.new_name = new_name
self.new_app_label = new_app_label
super().__init__(old_name)

@cached_property
Expand All @@ -295,20 +296,24 @@ def deconstruct(self):
'old_name': self.old_name,
'new_name': self.new_name,
}
if self.new_app_label:
kwargs['new_app_label'] = self.new_app_label
return (
self.__class__.__qualname__,
[],
kwargs
)

def state_forwards(self, app_label, state):
new_app_label = self.new_app_label or app_label
# Add a new model.
renamed_model = state.models[app_label, self.old_name_lower].clone()
renamed_model.name = self.new_name
state.models[app_label, self.new_name_lower] = renamed_model
renamed_model.app_label = new_app_label
state.models[new_app_label, self.new_name_lower] = renamed_model
# Repoint all fields pointing to the old model to the new one.
old_model_tuple = ModelTuple(app_label, self.old_name_lower)
new_remote_model = '%s.%s' % (app_label, self.new_name)
new_remote_model = '%s.%s' % (new_app_label, self.new_name)
to_reload = []
for (model_app_label, model_name), model_state in state.models.items():
model_changed = False
Expand Down Expand Up @@ -340,10 +345,11 @@ def state_forwards(self, app_label, state):
state.reload_models(to_reload, delay=True)
# Remove the old model.
state.remove_model(app_label, self.old_name_lower)
state.reload_model(app_label, self.new_name_lower, delay=True)
state.reload_model(new_app_label, self.new_name_lower, delay=True)

def database_forwards(self, app_label, schema_editor, from_state, to_state):
new_model = to_state.apps.get_model(app_label, self.new_name)
new_app_label = self.new_app_label or app_label
new_model = to_state.apps.get_model(new_app_label, self.new_name)
if self.allow_migrate_model(schema_editor.connection.alias, new_model):
old_model = from_state.apps.get_model(app_label, self.old_name)
# Move the main table
Expand All @@ -356,7 +362,7 @@ def database_forwards(self, app_label, schema_editor, from_state, to_state):
for related_object in old_model._meta.related_objects:
if related_object.related_model == old_model:
model = new_model
related_key = (app_label, self.new_name_lower)
related_key = (new_app_label, self.new_name_lower)
else:
model = related_object.related_model
related_key = (
Expand Down Expand Up @@ -394,24 +400,37 @@ def database_forwards(self, app_label, schema_editor, from_state, to_state):
)

def database_backwards(self, app_label, schema_editor, from_state, to_state):
is_moving = bool(self.new_app_label)
self.new_name_lower, self.old_name_lower = self.old_name_lower, self.new_name_lower
self.new_name, self.old_name = self.old_name, self.new_name
if is_moving:
self.new_app_label, app_label = app_label, self.new_app_label

self.database_forwards(app_label, schema_editor, from_state, to_state)

self.new_name_lower, self.old_name_lower = self.old_name_lower, self.new_name_lower
self.new_name, self.old_name = self.old_name, self.new_name
if is_moving:
self.new_app_label, app_label = app_label, self.new_app_label

def references_model(self, name, app_label=None):
# TODO: update references_model for moving
return (
name.lower() == self.old_name_lower or
name.lower() == self.new_name_lower
)

def describe(self):
return "Rename model %s to %s" % (self.old_name, self.new_name)
if self.new_app_label:
if self.old_name == self.new_name:
return "Move model %s to %s.%s" % (self.old_name, self.new_app_label, self.new_name)
else:
return "Rename and Move model %s to %s.%s" % (self.old_name, self.new_app_label, self.new_name)
else:
return "Rename model %s to %s" % (self.old_name, self.new_name)

def reduce(self, operation, app_label=None):
# TODO: update reduce for moving
if (isinstance(operation, RenameModel) and
self.new_name_lower == operation.old_name_lower):
return [
Expand All @@ -428,6 +447,43 @@ def reduce(self, operation, app_label=None):
)


class MoveModelPlaceholder(Operation):
_comment = 'this is a required noop operation to construct dependencies for moving model'

reduces_to_sql = False

def __init__(self, old_name, new_name, new_app_label=None, comment=None, *args, **kwargs):
self.old_name = old_name
self.new_name = new_name
self.new_app_label = new_app_label
super().__init__(*args, **kwargs)

def deconstruct(self):
kwargs = {
'old_name': self.old_name,
'new_name': self.new_name,
'new_app_label': self.new_app_label,
'comment': self._comment # tricky way to put comment in generated migration file
}
return (
self.__class__.__qualname__,
[],
kwargs
)

def state_forwards(self, app_label, state):
pass

def database_forwards(self, app_label, schema_editor, from_state, to_state):
pass

def database_backwards(self, app_label, schema_editor, from_state, to_state):
pass

def describe(self):
return "Move model %s to %s.%s (placeholder)" % (self.old_name, self.new_app_label, self.new_name)


class ModelOptionOperation(ModelOperation):
def reduce(self, operation, app_label=None):
if isinstance(operation, (self.__class__, DeleteModel)) and self.name_lower == operation.name_lower:
Expand Down