diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py index 9d99c90c11..fe0ac6b061 100644 --- a/django/db/migrations/executor.py +++ b/django/db/migrations/executor.py @@ -33,10 +33,15 @@ class MigrationExecutor(object): # If the migration is already applied, do backwards mode, # otherwise do forwards mode. elif target in applied: - for migration in self.loader.graph.backwards_plan(target)[:-1]: - if migration in applied: - plan.append((self.loader.graph.nodes[migration], True)) - applied.remove(migration) + backwards_plan = self.loader.graph.backwards_plan(target)[:-1] + # We only do this if the migration is not the most recent one + # in its app - that is, another migration with the same app + # label is in the backwards plan + if any(node[0] == target[0] for node in backwards_plan): + for migration in backwards_plan: + if migration in applied: + plan.append((self.loader.graph.nodes[migration], True)) + applied.remove(migration) else: for migration in self.loader.graph.forwards_plan(target): if migration not in applied: diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py index ddbfa78db7..5167f428d1 100644 --- a/tests/migrations/test_executor.py +++ b/tests/migrations/test_executor.py @@ -12,7 +12,7 @@ class ExecutorTests(TransactionTestCase): test failures first, as they may be propagating into here. """ - available_apps = ["migrations"] + available_apps = ["migrations", "django.contrib.sessions"] @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) def test_run(self): @@ -38,3 +38,35 @@ class ExecutorTests(TransactionTestCase): # Are the tables there now? self.assertIn("migrations_author", connection.introspection.get_table_list(connection.cursor())) self.assertIn("migrations_book", connection.introspection.get_table_list(connection.cursor())) + + @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations", "sessions": "migrations.test_migrations_2"}) + def test_empty_plan(self): + """ + Tests that re-planning a full migration of a fully-migrated set doesn't + perform spurious unmigrations and remigrations. + + There was previously a bug where the executor just always performed the + backwards plan for applied migrations - which even for the most recent + migration in an app, might include other, dependent apps, and these + were being unmigrated. + """ + # Make the initial plan, check it + # We use 'sessions' here as the second app as it's always present + # in INSTALLED_APPS, so we can happily assign it test migrations. + executor = MigrationExecutor(connection) + plan = executor.migration_plan([("migrations", "0002_second"), ("sessions", "0001_initial")]) + self.assertEqual( + plan, + [ + (executor.loader.graph.nodes["migrations", "0001_initial"], False), + (executor.loader.graph.nodes["migrations", "0002_second"], False), + (executor.loader.graph.nodes["sessions", "0001_initial"], False), + ], + ) + # Fake-apply all migrations + executor.migrate([("migrations", "0002_second"), ("sessions", "0001_initial")], fake=True) + # Now plan a second time and make sure it's empty + plan = executor.migration_plan([("migrations", "0002_second"), ("sessions", "0001_initial")]) + self.assertEqual(plan, []) + # Erase all the fake records + executor.recorder.flush() diff --git a/tests/migrations/test_migrations_2/0001_initial.py b/tests/migrations/test_migrations_2/0001_initial.py new file mode 100644 index 0000000000..94c4bc0746 --- /dev/null +++ b/tests/migrations/test_migrations_2/0001_initial.py @@ -0,0 +1,21 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [("migrations", "0002_second")] + + operations = [ + + migrations.CreateModel( + "OtherAuthor", + [ + ("id", models.AutoField(primary_key=True)), + ("name", models.CharField(max_length=255)), + ("slug", models.SlugField(null=True)), + ("age", models.IntegerField(default=0)), + ("silly_field", models.BooleanField(default=False)), + ], + ), + + ] diff --git a/tests/migrations/test_migrations_2/__init__.py b/tests/migrations/test_migrations_2/__init__.py new file mode 100644 index 0000000000..e69de29bb2