mirror of
https://github.com/django/django.git
synced 2025-08-21 17:29:13 +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:
|
) as schema_editor:
|
||||||
state = migration.apply(state, schema_editor)
|
state = migration.apply(state, schema_editor)
|
||||||
if not schema_editor.deferred_sql:
|
if not schema_editor.deferred_sql:
|
||||||
self.record_migration(migration)
|
self.record_migration(migration.app_label, migration.name)
|
||||||
migration_recorded = True
|
migration_recorded = True
|
||||||
if not migration_recorded:
|
if not migration_recorded:
|
||||||
self.record_migration(migration)
|
self.record_migration(migration.app_label, migration.name)
|
||||||
# Report progress
|
# Report progress
|
||||||
if self.progress_callback:
|
if self.progress_callback:
|
||||||
self.progress_callback("apply_success", migration, fake)
|
self.progress_callback("apply_success", migration, fake)
|
||||||
return state
|
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
|
# For replacement migrations, record individual statuses
|
||||||
if migration.replaces:
|
if migration and migration.replaces:
|
||||||
for app_label, name in migration.replaces:
|
for replaced_app_label, replaced_name in migration.replaces:
|
||||||
self.recorder.record_applied(app_label, name)
|
self.record_migration(replaced_app_label, replaced_name, forward)
|
||||||
|
if forward:
|
||||||
|
self.recorder.record_applied(app_label, name)
|
||||||
else:
|
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):
|
def unapply_migration(self, state, migration, fake=False):
|
||||||
"""Run a migration backwards."""
|
"""Run a migration backwards."""
|
||||||
@ -280,11 +283,7 @@ class MigrationExecutor:
|
|||||||
atomic=migration.atomic
|
atomic=migration.atomic
|
||||||
) as schema_editor:
|
) as schema_editor:
|
||||||
state = migration.unapply(state, schema_editor)
|
state = migration.unapply(state, schema_editor)
|
||||||
# For replacement migrations, also record individual statuses.
|
self.record_migration(migration.app_label, migration.name, forward=False)
|
||||||
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)
|
|
||||||
# Report progress
|
# Report progress
|
||||||
if self.progress_callback:
|
if self.progress_callback:
|
||||||
self.progress_callback("unapply_success", migration, fake)
|
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):
|
def test_squashmigrations_initial_attribute(self):
|
||||||
with self.temporary_migration_module(
|
with self.temporary_migration_module(
|
||||||
module="migrations.test_migrations"
|
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