summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorgi Yanchev <georgi-yanchev@users.noreply.github.com>2025-02-11 15:14:53 -0500
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-02-13 12:52:06 +0100
commit0ee842bb456cb3854d5546106e1259db83714d34 (patch)
tree8b34da74cb17c4014fe54aa70b9bf54a9077e1c0
parent54a902c6e81214462388f79a7c42f1c88c863dfd (diff)
Fixed #36146 -- Recorded applied/unapplied migrations recursively.
-rw-r--r--django/db/migrations/executor.py23
-rw-r--r--tests/migrations/test_commands.py44
-rw-r--r--tests/migrations/test_migrations_squashed_double/0001_initial.py21
-rw-r--r--tests/migrations/test_migrations_squashed_double/0002_auto.py12
-rw-r--r--tests/migrations/test_migrations_squashed_double/0003_squashed_0001_and_0002.py22
-rw-r--r--tests/migrations/test_migrations_squashed_double/0004_auto.py12
-rw-r--r--tests/migrations/test_migrations_squashed_double/0005_squashed_0003_and_0004.py25
-rw-r--r--tests/migrations/test_migrations_squashed_double/__init__.py0
8 files changed, 147 insertions, 12 deletions
diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py
index 13afa5988f..ebfe8572fe 100644
--- a/django/db/migrations/executor.py
+++ b/django/db/migrations/executor.py
@@ -254,22 +254,25 @@ class MigrationExecutor:
) as schema_editor:
state = migration.apply(state, schema_editor)
if not schema_editor.deferred_sql:
- self.record_migration(migration)
+ self.record_migration(migration.app_label, migration.name)
migration_recorded = True
if not migration_recorded:
- self.record_migration(migration)
+ self.record_migration(migration.app_label, migration.name)
# Report progress
if self.progress_callback:
self.progress_callback("apply_success", migration, fake)
return state
- def record_migration(self, migration):
+ def record_migration(self, app_label, name, forward=True):
+ migration = self.loader.disk_migrations.get((app_label, name))
# For replacement migrations, record individual statuses
- if migration.replaces:
- for app_label, name in migration.replaces:
- self.recorder.record_applied(app_label, name)
+ if migration and migration.replaces:
+ for replaced_app_label, replaced_name in migration.replaces:
+ self.record_migration(replaced_app_label, replaced_name, forward)
+ if forward:
+ self.recorder.record_applied(app_label, name)
else:
- self.recorder.record_applied(migration.app_label, migration.name)
+ self.recorder.record_unapplied(app_label, name)
def unapply_migration(self, state, migration, fake=False):
"""Run a migration backwards."""
@@ -280,11 +283,7 @@ class MigrationExecutor:
atomic=migration.atomic
) as schema_editor:
state = migration.unapply(state, schema_editor)
- # For replacement migrations, also record individual statuses.
- if migration.replaces:
- for app_label, name in migration.replaces:
- self.recorder.record_unapplied(app_label, name)
- self.recorder.record_unapplied(migration.app_label, migration.name)
+ self.record_migration(migration.app_label, migration.name, forward=False)
# Report progress
if self.progress_callback:
self.progress_callback("unapply_success", migration, fake)
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index dd54e4fe50..3b4da2bbb7 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -3073,6 +3073,50 @@ class SquashMigrationsTests(MigrationTestBase):
],
)
+ def test_double_replaced_migrations_are_recorded(self):
+ """
+ All recursively replaced migrations should be recorded/unrecorded, when
+ migrating an app with double squashed migrations.
+ """
+ out = io.StringIO()
+ with self.temporary_migration_module(
+ module="migrations.test_migrations_squashed_double"
+ ):
+ recorder = MigrationRecorder(connection)
+ applied_app_labels = [
+ app_label for app_label, _ in recorder.applied_migrations()
+ ]
+ self.assertNotIn("migrations", applied_app_labels)
+
+ call_command(
+ "migrate", "migrations", "--plan", interactive=False, stdout=out
+ )
+ migration_plan = re.findall("migrations.(.+)\n", out.getvalue())
+ # Only the top-level replacement migration should be applied.
+ self.assertEqual(migration_plan, ["0005_squashed_0003_and_0004"])
+
+ call_command("migrate", "migrations", interactive=False, verbosity=0)
+ applied_migrations = recorder.applied_migrations()
+ # Make sure all replaced migrations are recorded.
+ self.assertIn(("migrations", "0001_initial"), applied_migrations)
+ self.assertIn(("migrations", "0002_auto"), applied_migrations)
+ self.assertIn(
+ ("migrations", "0003_squashed_0001_and_0002"), applied_migrations
+ )
+ self.assertIn(("migrations", "0004_auto"), applied_migrations)
+ self.assertIn(
+ ("migrations", "0005_squashed_0003_and_0004"), applied_migrations
+ )
+
+ # Unapply all migrations from this app.
+ call_command(
+ "migrate", "migrations", "zero", interactive=False, verbosity=0
+ )
+ applied_app_labels = [
+ app_label for app_label, _ in recorder.applied_migrations()
+ ]
+ self.assertNotIn("migrations", applied_app_labels)
+
def test_squashmigrations_initial_attribute(self):
with self.temporary_migration_module(
module="migrations.test_migrations"
diff --git a/tests/migrations/test_migrations_squashed_double/0001_initial.py b/tests/migrations/test_migrations_squashed_double/0001_initial.py
new file mode 100644
index 0000000000..03b4ed839d
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_double/0001_initial.py
@@ -0,0 +1,21 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ operations = [
+ migrations.CreateModel(
+ name="A",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("foo", models.BooleanField()),
+ ],
+ ),
+ ]
diff --git a/tests/migrations/test_migrations_squashed_double/0002_auto.py b/tests/migrations/test_migrations_squashed_double/0002_auto.py
new file mode 100644
index 0000000000..c04820c6a4
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_double/0002_auto.py
@@ -0,0 +1,12 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [("migrations", "0001_initial")]
+ operations = [
+ migrations.AlterField(
+ model_name="a",
+ name="foo",
+ field=models.BooleanField(default=True),
+ ),
+ ]
diff --git a/tests/migrations/test_migrations_squashed_double/0003_squashed_0001_and_0002.py b/tests/migrations/test_migrations_squashed_double/0003_squashed_0001_and_0002.py
new file mode 100644
index 0000000000..708f73a105
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_double/0003_squashed_0001_and_0002.py
@@ -0,0 +1,22 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ replaces = [("migrations", "0001_initial"), ("migrations", "0002_auto")]
+ operations = [
+ migrations.CreateModel(
+ name="A",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("foo", models.BooleanField(default=True)),
+ ],
+ ),
+ ]
diff --git a/tests/migrations/test_migrations_squashed_double/0004_auto.py b/tests/migrations/test_migrations_squashed_double/0004_auto.py
new file mode 100644
index 0000000000..d3a9769c9a
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_double/0004_auto.py
@@ -0,0 +1,12 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [("migrations", "0002_auto")]
+ operations = [
+ migrations.AlterField(
+ model_name="a",
+ name="foo",
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/tests/migrations/test_migrations_squashed_double/0005_squashed_0003_and_0004.py b/tests/migrations/test_migrations_squashed_double/0005_squashed_0003_and_0004.py
new file mode 100644
index 0000000000..246d68272d
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_double/0005_squashed_0003_and_0004.py
@@ -0,0 +1,25 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ replaces = [
+ ("migrations", "0003_squashed_0001_and_0002"),
+ ("migrations", "0004_auto"),
+ ]
+ operations = [
+ migrations.CreateModel(
+ name="A",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("foo", models.BooleanField(default=False)),
+ ],
+ ),
+ ]
diff --git a/tests/migrations/test_migrations_squashed_double/__init__.py b/tests/migrations/test_migrations_squashed_double/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_double/__init__.py