Loading django/db/migrations/executor.py +11 −0 Original line number Diff line number Diff line Loading @@ -110,6 +110,7 @@ class MigrationExecutor(object): self.apply_migration(states[migration], migration, fake=fake, fake_initial=fake_initial) else: self.unapply_migration(states[migration], migration, fake=fake) self.check_replacements() def collect_sql(self, plan): """ Loading Loading @@ -176,6 +177,16 @@ class MigrationExecutor(object): self.progress_callback("unapply_success", migration, fake) return state def check_replacements(self): """ Mark replacement migrations applied if their replaced set all are. """ applied = self.recorder.applied_migrations() for key, migration in self.loader.replacements.items(): all_applied = all(m in applied for m in migration.replaces) if all_applied and key not in applied: self.recorder.record_applied(*key) def detect_soft_applied(self, project_state, migration): """ Tests whether a migration has been implicitly applied - that the Loading django/db/migrations/loader.py +2 −0 Original line number Diff line number Diff line Loading @@ -249,6 +249,8 @@ class MigrationLoader(object): # Mark the replacement as applied if all its replaced ones are if all(applied_statuses): self.applied_migrations.add(key) # Store the replacement migrations for later checks self.replacements = replacing # Finally, make a graph and load everything into it self.graph = MigrationGraph() for key, migration in normal.items(): Loading docs/releases/1.8.3.txt +3 −0 Original line number Diff line number Diff line Loading @@ -47,3 +47,6 @@ Bugfixes * Fixed a crash when loading squashed migrations from two apps with a dependency between them, where the dependent app's replaced migrations are partially applied (:ticket:`24895`). * Fixed recording of applied status for squashed (replacement) migrations (:ticket:`24628`). tests/migrations/test_executor.py +26 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ from django.apps.registry import apps as global_apps from django.db import connection from django.db.migrations.executor import MigrationExecutor from django.db.migrations.graph import MigrationGraph from django.db.migrations.recorder import MigrationRecorder from django.db.utils import DatabaseError from django.test import TestCase, modify_settings, override_settings Loading Loading @@ -411,6 +412,31 @@ class ExecutorTests(MigrationTestBase): self.assertTableNotExists("author_app_author") self.assertTableNotExists("book_app_book") @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed"}) def test_apply_all_replaced_marks_replacement_as_applied(self): """ Applying all replaced migrations marks the replacement as applied. Ticket #24628. """ recorder = MigrationRecorder(connection) # Place the database in a state where the replaced migrations are # partially applied: 0001 is applied, 0002 is not. recorder.record_applied("migrations", "0001_initial") executor = MigrationExecutor(connection) # Use fake because we don't actually have the first migration # applied, so the second will fail. And there's no need to actually # create/modify tables here, we're just testing the # MigrationRecord, which works the same with or without fake. executor.migrate([("migrations", "0002_second")], fake=True) # Because we've now applied 0001 and 0002 both, their squashed # replacement should be marked as applied. self.assertIn( ("migrations", "0001_squashed_0002"), recorder.applied_migrations(), ) class FakeLoader(object): def __init__(self, graph, applied): Loading Loading
django/db/migrations/executor.py +11 −0 Original line number Diff line number Diff line Loading @@ -110,6 +110,7 @@ class MigrationExecutor(object): self.apply_migration(states[migration], migration, fake=fake, fake_initial=fake_initial) else: self.unapply_migration(states[migration], migration, fake=fake) self.check_replacements() def collect_sql(self, plan): """ Loading Loading @@ -176,6 +177,16 @@ class MigrationExecutor(object): self.progress_callback("unapply_success", migration, fake) return state def check_replacements(self): """ Mark replacement migrations applied if their replaced set all are. """ applied = self.recorder.applied_migrations() for key, migration in self.loader.replacements.items(): all_applied = all(m in applied for m in migration.replaces) if all_applied and key not in applied: self.recorder.record_applied(*key) def detect_soft_applied(self, project_state, migration): """ Tests whether a migration has been implicitly applied - that the Loading
django/db/migrations/loader.py +2 −0 Original line number Diff line number Diff line Loading @@ -249,6 +249,8 @@ class MigrationLoader(object): # Mark the replacement as applied if all its replaced ones are if all(applied_statuses): self.applied_migrations.add(key) # Store the replacement migrations for later checks self.replacements = replacing # Finally, make a graph and load everything into it self.graph = MigrationGraph() for key, migration in normal.items(): Loading
docs/releases/1.8.3.txt +3 −0 Original line number Diff line number Diff line Loading @@ -47,3 +47,6 @@ Bugfixes * Fixed a crash when loading squashed migrations from two apps with a dependency between them, where the dependent app's replaced migrations are partially applied (:ticket:`24895`). * Fixed recording of applied status for squashed (replacement) migrations (:ticket:`24628`).
tests/migrations/test_executor.py +26 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ from django.apps.registry import apps as global_apps from django.db import connection from django.db.migrations.executor import MigrationExecutor from django.db.migrations.graph import MigrationGraph from django.db.migrations.recorder import MigrationRecorder from django.db.utils import DatabaseError from django.test import TestCase, modify_settings, override_settings Loading Loading @@ -411,6 +412,31 @@ class ExecutorTests(MigrationTestBase): self.assertTableNotExists("author_app_author") self.assertTableNotExists("book_app_book") @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed"}) def test_apply_all_replaced_marks_replacement_as_applied(self): """ Applying all replaced migrations marks the replacement as applied. Ticket #24628. """ recorder = MigrationRecorder(connection) # Place the database in a state where the replaced migrations are # partially applied: 0001 is applied, 0002 is not. recorder.record_applied("migrations", "0001_initial") executor = MigrationExecutor(connection) # Use fake because we don't actually have the first migration # applied, so the second will fail. And there's no need to actually # create/modify tables here, we're just testing the # MigrationRecord, which works the same with or without fake. executor.migrate([("migrations", "0002_second")], fake=True) # Because we've now applied 0001 and 0002 both, their squashed # replacement should be marked as applied. self.assertIn( ("migrations", "0001_squashed_0002"), recorder.applied_migrations(), ) class FakeLoader(object): def __init__(self, graph, applied): Loading