|
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|
|
from django.apps.registry import apps as global_apps
|
|
|
from django.db import migrations
|
|
|
|
|
|
+from .exceptions import InvalidMigrationPlan
|
|
|
from .loader import MigrationLoader
|
|
|
from .recorder import MigrationRecorder
|
|
|
from .state import ProjectState
|
|
@@ -71,46 +72,95 @@ class MigrationExecutor(object):
|
|
|
"""
|
|
|
if plan is None:
|
|
|
plan = self.migration_plan(targets)
|
|
|
- migrations_to_run = {m[0] for m in plan}
|
|
|
# Create the forwards plan Django would follow on an empty database
|
|
|
full_plan = self.migration_plan(self.loader.graph.leaf_nodes(), clean_start=True)
|
|
|
- # Holds all states right before a migration is applied
|
|
|
- # if the migration is being run.
|
|
|
+
|
|
|
+ all_forwards = all(not backwards for mig, backwards in plan)
|
|
|
+ all_backwards = all(backwards for mig, backwards in plan)
|
|
|
+
|
|
|
+ if not plan:
|
|
|
+ pass # Nothing to do for an empty plan
|
|
|
+ elif all_forwards == all_backwards:
|
|
|
+ # This should only happen if there's a mixed plan
|
|
|
+ raise InvalidMigrationPlan(
|
|
|
+ "Migration plans with both forwards and backwards migrations "
|
|
|
+ "are not supported. Please split your migration process into "
|
|
|
+ "separate plans of only forwards OR backwards migrations.",
|
|
|
+ plan
|
|
|
+ )
|
|
|
+ elif all_forwards:
|
|
|
+ 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)
|
|
|
+
|
|
|
+ self.check_replacements()
|
|
|
+
|
|
|
+ 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))
|
|
|
+ for migration, _ in full_plan:
|
|
|
+ if not migrations_to_run:
|
|
|
+ # We remove every migration that we applied from this set so
|
|
|
+ # that we can bail out once the last migration has been applied
|
|
|
+ # and don't always run until the very end of the migration
|
|
|
+ # process.
|
|
|
+ break
|
|
|
+ if migration in migrations_to_run:
|
|
|
+ if 'apps' not in state.__dict__:
|
|
|
+ if self.progress_callback:
|
|
|
+ self.progress_callback("render_start")
|
|
|
+ state.apps # Render all -- performance critical
|
|
|
+ if self.progress_callback:
|
|
|
+ self.progress_callback("render_success")
|
|
|
+ state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
|
|
|
+ migrations_to_run.remove(migration)
|
|
|
+ else:
|
|
|
+ migration.mutate_state(state, preserve=False)
|
|
|
+
|
|
|
+ def _migrate_all_backwards(self, plan, full_plan, fake):
|
|
|
+ """
|
|
|
+ Take a list of 2-tuples of the form (migration instance, True) and
|
|
|
+ unapply them in reverse order they occur in the full_plan.
|
|
|
+
|
|
|
+ Since unapplying a migration requires the project state prior to that
|
|
|
+ migration, Django will compute the migration states before each of them
|
|
|
+ in a first run over the plan and then unapply them in a second run over
|
|
|
+ the plan.
|
|
|
+ """
|
|
|
+ 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))
|
|
|
if self.progress_callback:
|
|
|
self.progress_callback("render_start")
|
|
|
- # Phase 1 -- Store all project states of migrations right before they
|
|
|
- # are applied. The first migration that will be applied in phase 2 will
|
|
|
- # trigger the rendering of the initial project state. From this time on
|
|
|
- # models will be recursively reloaded as explained in
|
|
|
- # `django.db.migrations.state.get_related_models_recursive()`.
|
|
|
for migration, _ in full_plan:
|
|
|
if not migrations_to_run:
|
|
|
- # We remove every migration whose state was already computed
|
|
|
- # from the set below (`migrations_to_run.remove(migration)`).
|
|
|
- # If no states for migrations must be computed, we can exit
|
|
|
- # this loop. Migrations that occur after the latest migration
|
|
|
- # that is about to be applied would only trigger unneeded
|
|
|
- # mutate_state() calls.
|
|
|
+ # We remove every migration that we applied from this set so
|
|
|
+ # that we can bail out once the last migration has been applied
|
|
|
+ # and don't always run until the very end of the migration
|
|
|
+ # process.
|
|
|
break
|
|
|
- do_run = migration in migrations_to_run
|
|
|
- if do_run:
|
|
|
+ if migration in migrations_to_run:
|
|
|
if 'apps' not in state.__dict__:
|
|
|
- state.apps # Render all real_apps -- performance critical
|
|
|
- states[migration] = state.clone()
|
|
|
+ state.apps # Render all -- performance critical
|
|
|
+ # The state before this migration
|
|
|
+ states[migration] = state
|
|
|
+ # The old state keeps as-is, we continue with the new state
|
|
|
+ state = migration.mutate_state(state, preserve=True)
|
|
|
migrations_to_run.remove(migration)
|
|
|
- # Only preserve the state if the migration is being run later
|
|
|
- state = migration.mutate_state(state, preserve=do_run)
|
|
|
+ else:
|
|
|
+ migration.mutate_state(state, preserve=False)
|
|
|
if self.progress_callback:
|
|
|
self.progress_callback("render_success")
|
|
|
- # Phase 2 -- Run the migrations
|
|
|
- for migration, backwards in plan:
|
|
|
- if not backwards:
|
|
|
- self.apply_migration(states[migration], migration, fake=fake, fake_initial=fake_initial)
|
|
|
- else:
|
|
|
- self.unapply_migration(states[migration], migration, fake=fake)
|
|
|
- self.check_replacements()
|
|
|
+
|
|
|
+ for migration, _ in plan:
|
|
|
+ self.unapply_migration(states[migration], migration, fake=fake)
|
|
|
|
|
|
def collect_sql(self, plan):
|
|
|
"""
|