Fixed #24899 -- Split migrations autodetector._detect_changes() method
This commit is contained in:
parent
2dc93bb10a
commit
ed63df3fc2
|
@ -155,35 +155,9 @@ class MigrationAutodetector(object):
|
|||
# Renames have to come first
|
||||
self.generate_renamed_models()
|
||||
|
||||
# Prepare field lists, and prepare a list of the fields that used
|
||||
# through models in the old state so we can make dependencies
|
||||
# from the through model deletion to the field that uses it.
|
||||
self.kept_model_keys = set(self.old_model_keys).intersection(self.new_model_keys)
|
||||
self.kept_proxy_keys = set(self.old_proxy_keys).intersection(self.new_proxy_keys)
|
||||
self.kept_unmanaged_keys = set(self.old_unmanaged_keys).intersection(self.new_unmanaged_keys)
|
||||
self.through_users = {}
|
||||
self.old_field_keys = set()
|
||||
self.new_field_keys = set()
|
||||
for app_label, model_name in sorted(self.kept_model_keys):
|
||||
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]
|
||||
self.old_field_keys.update((app_label, model_name, x) for x, y in old_model_state.fields)
|
||||
self.new_field_keys.update((app_label, model_name, x) for x, y in new_model_state.fields)
|
||||
|
||||
# Through model map generation
|
||||
for app_label, model_name in sorted(self.old_model_keys):
|
||||
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]
|
||||
for field_name, field in old_model_state.fields:
|
||||
old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field(field_name)
|
||||
if (hasattr(old_field, "remote_field") and getattr(old_field.remote_field, "through", None)
|
||||
and not old_field.remote_field.through._meta.auto_created):
|
||||
through_key = (
|
||||
old_field.remote_field.through._meta.app_label,
|
||||
old_field.remote_field.through._meta.model_name,
|
||||
)
|
||||
self.through_users[through_key] = (app_label, old_model_name, field_name)
|
||||
# Prepare lists of fields and generate through model map
|
||||
self._prepare_field_lists()
|
||||
self._generate_through_model_map()
|
||||
|
||||
# Generate non-rename model operations
|
||||
self.generate_deleted_models()
|
||||
|
@ -203,33 +177,59 @@ class MigrationAutodetector(object):
|
|||
self.generate_altered_db_table()
|
||||
self.generate_altered_order_with_respect_to()
|
||||
|
||||
# Now, reordering to make things possible. The order we have already
|
||||
# isn't bad, but we need to pull a few things around so FKs work nicely
|
||||
# inside the same app
|
||||
for app_label, ops in sorted(self.generated_operations.items()):
|
||||
self._sort_migrations()
|
||||
self._build_migration_list(graph)
|
||||
self._optimize_migrations()
|
||||
|
||||
# construct a dependency graph for intra-app dependencies
|
||||
dependency_graph = {op: set() for op in ops}
|
||||
for op in ops:
|
||||
for dep in op._auto_deps:
|
||||
if dep[0] == app_label:
|
||||
for op2 in ops:
|
||||
if self.check_dependency(op2, dep):
|
||||
dependency_graph[op].add(op2)
|
||||
return self.migrations
|
||||
|
||||
# we use a stable sort for deterministic tests & general behavior
|
||||
self.generated_operations[app_label] = stable_topological_sort(ops, dependency_graph)
|
||||
def _prepare_field_lists(self):
|
||||
"""
|
||||
Prepare field lists, and prepare a list of the fields that used
|
||||
through models in the old state so we can make dependencies
|
||||
from the through model deletion to the field that uses it.
|
||||
"""
|
||||
self.kept_model_keys = set(self.old_model_keys).intersection(self.new_model_keys)
|
||||
self.kept_proxy_keys = set(self.old_proxy_keys).intersection(self.new_proxy_keys)
|
||||
self.kept_unmanaged_keys = set(self.old_unmanaged_keys).intersection(self.new_unmanaged_keys)
|
||||
self.through_users = {}
|
||||
self.old_field_keys = set()
|
||||
self.new_field_keys = set()
|
||||
for app_label, model_name in sorted(self.kept_model_keys):
|
||||
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]
|
||||
self.old_field_keys.update((app_label, model_name, x) for x, y in old_model_state.fields)
|
||||
self.new_field_keys.update((app_label, model_name, x) for x, y in new_model_state.fields)
|
||||
|
||||
# Now, we need to chop the lists of operations up into migrations with
|
||||
# dependencies on each other.
|
||||
# We do this by stepping up an app's list of operations until we
|
||||
# find one that has an outgoing dependency that isn't in another app's
|
||||
# migration yet (hasn't been chopped off its list). We then chop off the
|
||||
# operations before it into a migration and move onto the next app.
|
||||
# If we loop back around without doing anything, there's a circular
|
||||
# dependency (which _should_ be impossible as the operations are all
|
||||
# split at this point so they can't depend and be depended on)
|
||||
def _generate_through_model_map(self):
|
||||
"""
|
||||
Through model map generation
|
||||
"""
|
||||
for app_label, model_name in sorted(self.old_model_keys):
|
||||
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]
|
||||
for field_name, field in old_model_state.fields:
|
||||
old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field(field_name)
|
||||
if (hasattr(old_field, "remote_field") and getattr(old_field.remote_field, "through", None)
|
||||
and not old_field.remote_field.through._meta.auto_created):
|
||||
through_key = (
|
||||
old_field.remote_field.through._meta.app_label,
|
||||
old_field.remote_field.through._meta.model_name,
|
||||
)
|
||||
self.through_users[through_key] = (app_label, old_model_name, field_name)
|
||||
|
||||
def _build_migration_list(self, graph=None):
|
||||
"""
|
||||
We need to chop the lists of operations up into migrations with
|
||||
dependencies on each other. We do this by stepping up an app's list of
|
||||
operations until we find one that has an outgoing dependency that isn't
|
||||
in another app's migration yet (hasn't been chopped off its list). We
|
||||
then chop off the operations before it into a migration and move onto
|
||||
the next app. If we loop back around without doing anything, there's a
|
||||
circular dependency (which _should_ be impossible as the operations are
|
||||
all split at this point so they can't depend and be depended on).
|
||||
"""
|
||||
self.migrations = {}
|
||||
num_ops = sum(len(x) for x in self.generated_operations.values())
|
||||
chop_mode = False
|
||||
|
@ -309,7 +309,27 @@ class MigrationAutodetector(object):
|
|||
raise ValueError("Cannot resolve operation dependencies: %r" % self.generated_operations)
|
||||
num_ops = new_num_ops
|
||||
|
||||
# OK, add in internal dependencies among the migrations
|
||||
def _sort_migrations(self):
|
||||
"""
|
||||
Reorder to make things possible. The order we have already isn't bad,
|
||||
but we need to pull a few things around so FKs work nicely inside the
|
||||
same app
|
||||
"""
|
||||
for app_label, ops in sorted(self.generated_operations.items()):
|
||||
# construct a dependency graph for intra-app dependencies
|
||||
dependency_graph = {op: set() for op in ops}
|
||||
for op in ops:
|
||||
for dep in op._auto_deps:
|
||||
if dep[0] == app_label:
|
||||
for op2 in ops:
|
||||
if self.check_dependency(op2, dep):
|
||||
dependency_graph[op].add(op2)
|
||||
|
||||
# we use a stable sort for deterministic tests & general behavior
|
||||
self.generated_operations[app_label] = stable_topological_sort(ops, dependency_graph)
|
||||
|
||||
def _optimize_migrations(self):
|
||||
# Add in internal dependencies among the migrations
|
||||
for app_label, migrations in self.migrations.items():
|
||||
for m1, m2 in zip(migrations, migrations[1:]):
|
||||
m2.dependencies.append((app_label, m1.name))
|
||||
|
@ -324,8 +344,6 @@ class MigrationAutodetector(object):
|
|||
for migration in migrations:
|
||||
migration.operations = MigrationOptimizer().optimize(migration.operations, app_label=app_label)
|
||||
|
||||
return self.migrations
|
||||
|
||||
def check_dependency(self, operation, dependency):
|
||||
"""
|
||||
Returns ``True`` if the given operation depends on the given dependency,
|
||||
|
|
Loading…
Reference in New Issue