mirror of
https://github.com/django/django.git
synced 2025-04-07 23:16:46 +00:00
Fixed #36146 -- Recorded applied/unapplied migrations recursively.
This commit is contained in:
parent
54a902c6e8
commit
0ee842bb45
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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()),
|
||||
],
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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)),
|
||||
],
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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)),
|
||||
],
|
||||
),
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user