Skip to content

Commit

Permalink
Fixed #24628 -- Fixed applied status for squashed migrations.
Browse files Browse the repository at this point in the history
  • Loading branch information
carljm committed Jun 2, 2015
1 parent 335fc44 commit 492537a
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 0 deletions.
11 changes: 11 additions & 0 deletions django/db/migrations/executor.py
Expand Up @@ -110,6 +110,7 @@ def migrate(self, targets, plan=None, fake=False, fake_initial=False):
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):
"""
Expand Down Expand Up @@ -176,6 +177,16 @@ def unapply_migration(self, state, migration, fake=False):
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
Expand Down
2 changes: 2 additions & 0 deletions django/db/migrations/loader.py
Expand Up @@ -251,6 +251,8 @@ def build_graph(self):
# 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():
Expand Down
3 changes: 3 additions & 0 deletions docs/releases/1.8.3.txt
Expand Up @@ -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`).
26 changes: 26 additions & 0 deletions tests/migrations/test_executor.py
Expand Up @@ -2,6 +2,7 @@
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

Expand Down Expand Up @@ -411,6 +412,31 @@ def test_alter_id_type_with_fk(self):
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):
Expand Down

0 comments on commit 492537a

Please sign in to comment.