diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py index 57042a8690..a8a189f7d9 100644 --- a/django/db/migrations/executor.py +++ b/django/db/migrations/executor.py @@ -250,12 +250,11 @@ class MigrationExecutor: if not fake: with self.connection.schema_editor(atomic=migration.atomic) as schema_editor: state = migration.unapply(state, schema_editor) - # For replacement migrations, record individual statuses + # For replacement migrations, also record individual statuses. if migration.replaces: for app_label, name in migration.replaces: self.recorder.record_unapplied(app_label, name) - else: - self.recorder.record_unapplied(migration.app_label, migration.name) + self.recorder.record_unapplied(migration.app_label, migration.name) # Report progress if self.progress_callback: self.progress_callback("unapply_success", migration, fake) diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py index e61d8f1276..fc80c950fa 100644 --- a/tests/migrations/test_executor.py +++ b/tests/migrations/test_executor.py @@ -653,6 +653,23 @@ class ExecutorTests(MigrationTestBase): recorder.applied_migrations(), ) + @override_settings(MIGRATION_MODULES={'migrations': 'migrations.test_migrations_squashed'}) + def test_migrate_marks_replacement_unapplied(self): + executor = MigrationExecutor(connection) + executor.migrate([('migrations', '0001_squashed_0002')]) + try: + self.assertIn( + ('migrations', '0001_squashed_0002'), + executor.recorder.applied_migrations(), + ) + finally: + executor.loader.build_graph() + executor.migrate([('migrations', None)]) + self.assertNotIn( + ('migrations', '0001_squashed_0002'), + executor.recorder.applied_migrations(), + ) + # When the feature is False, the operation and the record won't be # performed in a transaction and the test will systematically pass. @skipUnlessDBFeature('can_rollback_ddl')