From ed63df3fc2f0c228354ca3435e9bed45be9d0909 Mon Sep 17 00:00:00 2001 From: Steadman Date: Thu, 4 Jun 2015 14:47:44 +0100 Subject: [PATCH] Fixed #24899 -- Split migrations autodetector._detect_changes() method --- django/db/migrations/autodetector.py | 128 +++++++++++++++------------ 1 file changed, 73 insertions(+), 55 deletions(-) diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 9a4ba42722..ddf8e36b83 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -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,