Fixed #27100 -- Included already applied migration changes in the pre-migrate state.
Refs #24100. Thanks Tim for the review.
This commit is contained in:
parent
d1757d8df4
commit
d5c4ea5246
|
@ -160,7 +160,8 @@ class Command(BaseCommand):
|
||||||
% (targets[0][1], targets[0][0])
|
% (targets[0][1], targets[0][0])
|
||||||
)
|
)
|
||||||
|
|
||||||
pre_migrate_apps = executor._create_project_state().apps
|
pre_migrate_state = executor._create_project_state(with_applied_migrations=True)
|
||||||
|
pre_migrate_apps = pre_migrate_state.apps
|
||||||
emit_pre_migrate_signal(
|
emit_pre_migrate_signal(
|
||||||
self.verbosity, self.interactive, connection.alias, apps=pre_migrate_apps, plan=plan,
|
self.verbosity, self.interactive, connection.alias, apps=pre_migrate_apps, plan=plan,
|
||||||
)
|
)
|
||||||
|
@ -198,10 +199,11 @@ class Command(BaseCommand):
|
||||||
else:
|
else:
|
||||||
fake = options['fake']
|
fake = options['fake']
|
||||||
fake_initial = options['fake_initial']
|
fake_initial = options['fake_initial']
|
||||||
post_migrate_project_state = executor.migrate(
|
post_migrate_state = executor.migrate(
|
||||||
targets, plan, fake=fake, fake_initial=fake_initial
|
targets, plan=plan, state=pre_migrate_state.clone(), fake=fake,
|
||||||
|
fake_initial=fake_initial,
|
||||||
)
|
)
|
||||||
post_migrate_apps = post_migrate_project_state.apps
|
post_migrate_apps = post_migrate_state.apps
|
||||||
|
|
||||||
# Re-render models of real apps to include relationships now that
|
# 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
|
# we've got a final state. This wouldn't be necessary if real apps
|
||||||
|
|
|
@ -63,10 +63,25 @@ class MigrationExecutor(object):
|
||||||
applied.add(migration)
|
applied.add(migration)
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
def _create_project_state(self):
|
def _create_project_state(self, with_applied_migrations=False):
|
||||||
return ProjectState(real_apps=list(self.loader.unmigrated_apps))
|
"""
|
||||||
|
Create a project state including all the applications without
|
||||||
|
migrations and applied migrations if with_applied_migrations=True.
|
||||||
|
"""
|
||||||
|
state = ProjectState(real_apps=list(self.loader.unmigrated_apps))
|
||||||
|
if with_applied_migrations:
|
||||||
|
# Create the forwards plan Django would follow on an empty database
|
||||||
|
full_plan = self.migration_plan(self.loader.graph.leaf_nodes(), clean_start=True)
|
||||||
|
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 migration in applied_migrations:
|
||||||
|
migration.mutate_state(state, preserve=False)
|
||||||
|
return state
|
||||||
|
|
||||||
def migrate(self, targets, plan=None, fake=False, fake_initial=False):
|
def migrate(self, targets, plan=None, state=None, fake=False, fake_initial=False):
|
||||||
"""
|
"""
|
||||||
Migrates the database up to the given targets.
|
Migrates the database up to the given targets.
|
||||||
|
|
||||||
|
@ -82,15 +97,9 @@ class MigrationExecutor(object):
|
||||||
all_backwards = all(backwards for mig, backwards in plan)
|
all_backwards = all(backwards for mig, backwards in plan)
|
||||||
|
|
||||||
if not plan:
|
if not plan:
|
||||||
# The resulting state should include applied migrations.
|
if state is None:
|
||||||
state = self._create_project_state()
|
# The resulting state should include applied migrations.
|
||||||
applied_migrations = {
|
state = self._create_project_state(with_applied_migrations=True)
|
||||||
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 migration in applied_migrations:
|
|
||||||
migration.mutate_state(state, preserve=False)
|
|
||||||
elif all_forwards == all_backwards:
|
elif all_forwards == all_backwards:
|
||||||
# This should only happen if there's a mixed plan
|
# This should only happen if there's a mixed plan
|
||||||
raise InvalidMigrationPlan(
|
raise InvalidMigrationPlan(
|
||||||
|
@ -100,7 +109,10 @@ class MigrationExecutor(object):
|
||||||
plan
|
plan
|
||||||
)
|
)
|
||||||
elif all_forwards:
|
elif all_forwards:
|
||||||
state = self._migrate_all_forwards(plan, full_plan, fake=fake, fake_initial=fake_initial)
|
if state is None:
|
||||||
|
# The resulting state should still include applied migrations.
|
||||||
|
state = self._create_project_state(with_applied_migrations=True)
|
||||||
|
state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
|
||||||
else:
|
else:
|
||||||
# No need to check for `elif all_backwards` here, as that condition
|
# No need to check for `elif all_backwards` here, as that condition
|
||||||
# would always evaluate to true.
|
# would always evaluate to true.
|
||||||
|
@ -110,19 +122,14 @@ class MigrationExecutor(object):
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def _migrate_all_forwards(self, plan, full_plan, fake, fake_initial):
|
def _migrate_all_forwards(self, state, plan, full_plan, fake, fake_initial):
|
||||||
"""
|
"""
|
||||||
Take a list of 2-tuples of the form (migration instance, False) and
|
Take a list of 2-tuples of the form (migration instance, False) and
|
||||||
apply them in the order they occur in the full_plan.
|
apply them in the order they occur in the full_plan.
|
||||||
"""
|
"""
|
||||||
migrations_to_run = {m[0] for m in plan}
|
migrations_to_run = {m[0] for m in plan}
|
||||||
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:
|
for migration, _ in full_plan:
|
||||||
if not migrations_to_run and not applied_migrations:
|
if not migrations_to_run:
|
||||||
# We remove every migration that we applied from these sets so
|
# We remove every migration that we applied from these sets so
|
||||||
# that we can bail out once the last migration has been applied
|
# that we can bail out once the last migration has been applied
|
||||||
# and don't always run until the very end of the migration
|
# and don't always run until the very end of the migration
|
||||||
|
@ -137,12 +144,6 @@ class MigrationExecutor(object):
|
||||||
self.progress_callback("render_success")
|
self.progress_callback("render_success")
|
||||||
state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
|
state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
|
||||||
migrations_to_run.remove(migration)
|
migrations_to_run.remove(migration)
|
||||||
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)
|
|
||||||
applied_migrations.remove(migration)
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
|
@ -72,3 +72,7 @@ Bugfixes
|
||||||
* Fixed the creation of ``ContentType`` and ``Permission`` objects for models
|
* Fixed the creation of ``ContentType`` and ``Permission`` objects for models
|
||||||
of applications without migrations when calling the ``migrate`` command with
|
of applications without migrations when calling the ``migrate`` command with
|
||||||
no migrations to apply (:ticket:`27044`).
|
no migrations to apply (:ticket:`27044`).
|
||||||
|
|
||||||
|
* Included the already applied migration state changes in the ``Apps`` instance
|
||||||
|
provided to the ``pre_migrate`` signal receivers to allow ``ContentType``
|
||||||
|
renaming to be performed on model rename (:ticket:`27100`).
|
||||||
|
|
|
@ -114,11 +114,16 @@ class MigrateSignalTests(TransactionTestCase):
|
||||||
['migrate_signals.Signal']
|
['migrate_signals.Signal']
|
||||||
)
|
)
|
||||||
# Migrating with an empty plan.
|
# Migrating with an empty plan.
|
||||||
|
pre_migrate_receiver = Receiver(signals.pre_migrate)
|
||||||
post_migrate_receiver = Receiver(signals.post_migrate)
|
post_migrate_receiver = Receiver(signals.post_migrate)
|
||||||
management.call_command(
|
management.call_command(
|
||||||
'migrate', database=MIGRATE_DATABASE, verbosity=MIGRATE_VERBOSITY,
|
'migrate', database=MIGRATE_DATABASE, verbosity=MIGRATE_VERBOSITY,
|
||||||
interactive=MIGRATE_INTERACTIVE, stdout=stdout,
|
interactive=MIGRATE_INTERACTIVE, stdout=stdout,
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
[model._meta.label for model in pre_migrate_receiver.call_args['apps'].get_models()],
|
||||||
|
['migrate_signals.Signal']
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[model._meta.label for model in post_migrate_receiver.call_args['apps'].get_models()],
|
[model._meta.label for model in post_migrate_receiver.call_args['apps'].get_models()],
|
||||||
['migrate_signals.Signal']
|
['migrate_signals.Signal']
|
||||||
|
|
Loading…
Reference in New Issue