diff options
| author | Simon Charette <charette.s@gmail.com> | 2016-05-13 11:58:54 -0400 |
|---|---|---|
| committer | Simon Charette <charette.s@gmail.com> | 2016-05-15 19:51:15 -0400 |
| commit | f937c9ec975ebd719f0c22e5d1d5f5fb87ff1edd (patch) | |
| tree | c3af8fe0e962e6ec07ba01cac9cd76019230c41e /django | |
| parent | e475e849703d937e158e75e7a6d9cb99090857f6 (diff) | |
Fixed #24100 -- Made the migration signals dispatch its plan and apps.
Thanks Markus for your contribution and Tim for your review.
Diffstat (limited to 'django')
| -rw-r--r-- | django/core/management/commands/migrate.py | 30 | ||||
| -rw-r--r-- | django/core/management/sql.py | 12 | ||||
| -rw-r--r-- | django/db/migrations/executor.py | 52 | ||||
| -rw-r--r-- | django/db/models/signals.py | 4 |
4 files changed, 81 insertions, 17 deletions
diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index a8e9831cda..a1eb190eb1 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -15,7 +15,7 @@ from django.db import DEFAULT_DB_ALIAS, connections, router, transaction from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.executor import MigrationExecutor from django.db.migrations.loader import AmbiguityError -from django.db.migrations.state import ProjectState +from django.db.migrations.state import ModelState, ProjectState from django.utils.module_loading import module_has_submodule @@ -160,7 +160,10 @@ class Command(BaseCommand): % (targets[0][1], targets[0][0]) ) - emit_pre_migrate_signal(self.verbosity, self.interactive, connection.alias) + pre_migrate_apps = executor._create_project_state().apps + emit_pre_migrate_signal( + self.verbosity, self.interactive, connection.alias, apps=pre_migrate_apps, plan=plan, + ) # Run the syncdb phase. if run_syncdb: @@ -191,14 +194,33 @@ class Command(BaseCommand): "migrations, and then re-run 'manage.py migrate' to " "apply them." )) + post_migrate_apps = pre_migrate_apps else: fake = options['fake'] fake_initial = options['fake_initial'] - executor.migrate(targets, plan, fake=fake, fake_initial=fake_initial) + post_migrate_project_state = executor.migrate( + targets, plan, fake=fake, fake_initial=fake_initial + ) + post_migrate_apps = post_migrate_project_state.apps + + # Re-render models of real apps to include relationships now that + # we've got a final state. This wouldn't be necessary if real apps + # models were rendered with relationships in the first place. + with post_migrate_apps.bulk_update(): + model_keys = [] + for model_state in post_migrate_apps.real_models: + model_key = model_state.app_label, model_state.name_lower + model_keys.append(model_key) + post_migrate_apps.unregister_model(*model_key) + post_migrate_apps.render_multiple([ + ModelState.from_model(apps.get_model(*model_key)) for model_key in model_keys + ]) # Send the post_migrate signal, so individual apps can do whatever they need # to do at this point. - emit_post_migrate_signal(self.verbosity, self.interactive, connection.alias) + emit_post_migrate_signal( + self.verbosity, self.interactive, connection.alias, apps=post_migrate_apps, plan=plan, + ) def migration_progress_callback(self, action, migration=None, fake=False): if self.verbosity >= 1: diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 0c86a944ac..c72519f30a 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -20,7 +20,7 @@ def sql_flush(style, connection, only_django=False, reset_sequences=True, allow_ return statements -def emit_pre_migrate_signal(verbosity, interactive, db): +def emit_pre_migrate_signal(verbosity, interactive, db, **kwargs): # Emit the pre_migrate signal for every application. for app_config in apps.get_app_configs(): if app_config.models_module is None: @@ -32,10 +32,12 @@ def emit_pre_migrate_signal(verbosity, interactive, db): app_config=app_config, verbosity=verbosity, interactive=interactive, - using=db) + using=db, + **kwargs + ) -def emit_post_migrate_signal(verbosity, interactive, db): +def emit_post_migrate_signal(verbosity, interactive, db, **kwargs): # Emit the post_migrate signal for every application. for app_config in apps.get_app_configs(): if app_config.models_module is None: @@ -47,4 +49,6 @@ def emit_post_migrate_signal(verbosity, interactive, db): app_config=app_config, verbosity=verbosity, interactive=interactive, - using=db) + using=db, + **kwargs + ) diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py index fb1b833cfa..4487543f16 100644 --- a/django/db/migrations/executor.py +++ b/django/db/migrations/executor.py @@ -63,6 +63,9 @@ class MigrationExecutor(object): applied.add(migration) return plan + def _create_project_state(self): + return ProjectState(real_apps=list(self.loader.unmigrated_apps)) + def migrate(self, targets, plan=None, fake=False, fake_initial=False): """ Migrates the database up to the given targets. @@ -79,7 +82,9 @@ class MigrationExecutor(object): all_backwards = all(backwards for mig, backwards in plan) if not plan: - pass # Nothing to do for an empty plan + # Nothing to do for an empty plan, except for building the post + # migrate project state + state = self._create_project_state() elif all_forwards == all_backwards: # This should only happen if there's a mixed plan raise InvalidMigrationPlan( @@ -89,21 +94,27 @@ class MigrationExecutor(object): plan ) elif all_forwards: - self._migrate_all_forwards(plan, full_plan, fake=fake, fake_initial=fake_initial) + state = self._migrate_all_forwards(plan, full_plan, fake=fake, fake_initial=fake_initial) else: # No need to check for `elif all_backwards` here, as that condition # would always evaluate to true. - self._migrate_all_backwards(plan, full_plan, fake=fake) + state = self._migrate_all_backwards(plan, full_plan, fake=fake) self.check_replacements() + return state + def _migrate_all_forwards(self, plan, full_plan, fake, fake_initial): """ Take a list of 2-tuples of the form (migration instance, False) and apply them in the order they occur in the full_plan. """ migrations_to_run = {m[0] for m in plan} - state = ProjectState(real_apps=list(self.loader.unmigrated_apps)) + state = self._create_project_state() + applied_migrations = { + self.loader.graph.nodes[key] for key in self.loader.applied_migrations + if key in self.loader.graph.nodes + } for migration, _ in full_plan: if not migrations_to_run: # We remove every migration that we applied from this set so @@ -120,9 +131,14 @@ class MigrationExecutor(object): self.progress_callback("render_success") state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial) migrations_to_run.remove(migration) - else: + elif migration in applied_migrations: + # Only mutate the state if the migration is actually applied + # to make sure the resulting state doesn't include changes + # from unrelated migrations. migration.mutate_state(state, preserve=False) + return state + def _migrate_all_backwards(self, plan, full_plan, fake): """ Take a list of 2-tuples of the form (migration instance, True) and @@ -136,7 +152,11 @@ class MigrationExecutor(object): migrations_to_run = {m[0] for m in plan} # Holds all migration states prior to the migrations being unapplied states = {} - state = ProjectState(real_apps=list(self.loader.unmigrated_apps)) + state = self._create_project_state() + applied_migrations = { + self.loader.graph.nodes[key] for key in self.loader.applied_migrations + if key in self.loader.graph.nodes + } if self.progress_callback: self.progress_callback("render_start") for migration, _ in full_plan: @@ -154,13 +174,31 @@ class MigrationExecutor(object): # The old state keeps as-is, we continue with the new state state = migration.mutate_state(state, preserve=True) migrations_to_run.remove(migration) - else: + elif migration in applied_migrations: + # Only mutate the state if the migration is actually applied + # to make sure the resulting state doesn't include changes + # from unrelated migrations. migration.mutate_state(state, preserve=False) if self.progress_callback: self.progress_callback("render_success") for migration, _ in plan: self.unapply_migration(states[migration], migration, fake=fake) + applied_migrations.remove(migration) + + # Generate the post migration state by starting from the state before + # the last migration is unapplied and mutating it to include all the + # remaining applied migrations. + last_unapplied_migration = plan[-1][0] + state = states[last_unapplied_migration] + for index, (migration, _) in enumerate(full_plan): + if migration == last_unapplied_migration: + for migration, _ in full_plan[index:]: + if migration in applied_migrations: + migration.mutate_state(state, preserve=False) + break + + return state def collect_sql(self, plan): """ diff --git a/django/db/models/signals.py b/django/db/models/signals.py index a87d473f94..b69a2812a6 100644 --- a/django/db/models/signals.py +++ b/django/db/models/signals.py @@ -65,5 +65,5 @@ m2m_changed = ModelSignal( use_caching=True, ) -pre_migrate = Signal(providing_args=["app_config", "verbosity", "interactive", "using"]) -post_migrate = Signal(providing_args=["app_config", "verbosity", "interactive", "using"]) +pre_migrate = Signal(providing_args=["app_config", "verbosity", "interactive", "using", "apps", "plan"]) +post_migrate = Signal(providing_args=["app_config", "verbosity", "interactive", "using", "apps", "plan"]) |
