Loading django/db/migrations/executor.py +9 −4 Original line number Diff line number Diff line Loading @@ -33,7 +33,12 @@ 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]: 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) Loading tests/migrations/test_executor.py +33 −1 Original line number Diff line number Diff line Loading @@ -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): Loading @@ -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() tests/migrations/test_migrations_2/0001_initial.py 0 → 100644 +21 −0 Original line number Diff line number Diff line 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)), ], ), ] tests/migrations/test_migrations_2/__init__.py 0 → 100644 +0 −0 Empty file added. Loading
django/db/migrations/executor.py +9 −4 Original line number Diff line number Diff line Loading @@ -33,7 +33,12 @@ 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]: 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) Loading
tests/migrations/test_executor.py +33 −1 Original line number Diff line number Diff line Loading @@ -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): Loading @@ -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()
tests/migrations/test_migrations_2/0001_initial.py 0 → 100644 +21 −0 Original line number Diff line number Diff line 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)), ], ), ]