summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Meyer <carl@oddbird.net>2015-06-02 14:23:07 -0600
committerCarl Meyer <carl@oddbird.net>2015-06-02 16:21:58 -0600
commitefdcd13c340cee944531747c24be890af1e46710 (patch)
tree9d7c30c37c64377069a54a9e0f39ffbcee2e7368
parentc37c6dc410cab2d66a651647015d919feaecc4f7 (diff)
[1.8.x] Fixed #24628 -- Fixed applied status for squashed migrations.
Backport of 492537ac18df56c36f8a2335d773aa0fa01a61a3 from master.
-rw-r--r--django/db/migrations/executor.py11
-rw-r--r--django/db/migrations/loader.py2
-rw-r--r--docs/releases/1.8.3.txt3
-rw-r--r--tests/migrations/test_executor.py26
4 files changed, 42 insertions, 0 deletions
diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py
index 7dc0350927..af2207c9ab 100644
--- a/django/db/migrations/executor.py
+++ b/django/db/migrations/executor.py
@@ -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):
"""
@@ -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
diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index b10b84f6f8..e824728f2b 100644
--- a/django/db/migrations/loader.py
+++ b/django/db/migrations/loader.py
@@ -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():
diff --git a/docs/releases/1.8.3.txt b/docs/releases/1.8.3.txt
index 2940623e55..4f98027f7e 100644
--- a/docs/releases/1.8.3.txt
+++ b/docs/releases/1.8.3.txt
@@ -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`).
diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py
index b05eb04514..9946dd4503 100644
--- a/tests/migrations/test_executor.py
+++ b/tests/migrations/test_executor.py
@@ -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
@@ -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):