Loading django/core/management/commands/migrate.py +26 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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: Loading django/core/management/sql.py +8 −4 Original line number Diff line number Diff line Loading @@ -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: Loading @@ -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: Loading @@ -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 ) django/db/migrations/executor.py +45 −7 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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( Loading @@ -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 Loading @@ -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 Loading @@ -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: Loading @@ -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): """ Loading django/db/models/signals.py +2 −2 Original line number Diff line number Diff line Loading @@ -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"]) docs/ref/signals.txt +34 −0 Original line number Diff line number Diff line Loading @@ -406,6 +406,23 @@ Arguments sent with this signal: ``using`` The alias of database on which a command will operate. ``plan`` .. versionadded:: 1.10 The migration plan that is going to be used for the migration run. While the plan is not public API, this allows for the rare cases when it is necessary to know the plan. A plan is a list of two-tuples with the first item being the instance of a migration class and the second item showing if the migration was rolled back (``True``) or applied (``False``). ``apps`` .. versionadded:: 1.10 An instance of :data:`Apps <django.apps>` containing the state of the project before the migration run. It should be used instead of the global :attr:`apps <django.apps.apps>` registry to retrieve the models you want to perform operations on. ``post_migrate`` ---------------- Loading Loading @@ -448,6 +465,23 @@ Arguments sent with this signal: The database alias used for synchronization. Defaults to the ``default`` database. ``plan`` .. versionadded:: 1.10 The migration plan that was used for the migration run. While the plan is not public API, this allows for the rare cases when it is necessary to know the plan. A plan is a list of two-tuples with the first item being the instance of a migration class and the second item showing if the migration was rolled back (``True``) or applied (``False``). ``apps`` .. versionadded:: 1.10 An instance of :data:`Apps <django.apps.apps>` containing the state of the project after the migration run. It should be used instead of the global :attr:`apps <django.apps.apps>` registry to retrieve the models you want to perform operations on. For example, you could register a callback in an :class:`~django.apps.AppConfig` like this:: Loading Loading
django/core/management/commands/migrate.py +26 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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: Loading
django/core/management/sql.py +8 −4 Original line number Diff line number Diff line Loading @@ -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: Loading @@ -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: Loading @@ -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 )
django/db/migrations/executor.py +45 −7 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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( Loading @@ -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 Loading @@ -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 Loading @@ -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: Loading @@ -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): """ Loading
django/db/models/signals.py +2 −2 Original line number Diff line number Diff line Loading @@ -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"])
docs/ref/signals.txt +34 −0 Original line number Diff line number Diff line Loading @@ -406,6 +406,23 @@ Arguments sent with this signal: ``using`` The alias of database on which a command will operate. ``plan`` .. versionadded:: 1.10 The migration plan that is going to be used for the migration run. While the plan is not public API, this allows for the rare cases when it is necessary to know the plan. A plan is a list of two-tuples with the first item being the instance of a migration class and the second item showing if the migration was rolled back (``True``) or applied (``False``). ``apps`` .. versionadded:: 1.10 An instance of :data:`Apps <django.apps>` containing the state of the project before the migration run. It should be used instead of the global :attr:`apps <django.apps.apps>` registry to retrieve the models you want to perform operations on. ``post_migrate`` ---------------- Loading Loading @@ -448,6 +465,23 @@ Arguments sent with this signal: The database alias used for synchronization. Defaults to the ``default`` database. ``plan`` .. versionadded:: 1.10 The migration plan that was used for the migration run. While the plan is not public API, this allows for the rare cases when it is necessary to know the plan. A plan is a list of two-tuples with the first item being the instance of a migration class and the second item showing if the migration was rolled back (``True``) or applied (``False``). ``apps`` .. versionadded:: 1.10 An instance of :data:`Apps <django.apps.apps>` containing the state of the project after the migration run. It should be used instead of the global :attr:`apps <django.apps.apps>` registry to retrieve the models you want to perform operations on. For example, you could register a callback in an :class:`~django.apps.AppConfig` like this:: Loading