2018-03-16 03:32:56 +08:00
|
|
|
import compileall
|
|
|
|
import os
|
2020-12-29 06:05:18 +08:00
|
|
|
from importlib import import_module
|
2018-03-16 03:32:56 +08:00
|
|
|
|
2016-07-14 21:10:15 +08:00
|
|
|
from django.db import connection, connections
|
2016-04-02 20:46:59 +08:00
|
|
|
from django.db.migrations.exceptions import (
|
2016-09-02 04:19:29 +08:00
|
|
|
AmbiguityError, InconsistentMigrationHistory, NodeNotFoundError,
|
2016-04-02 20:46:59 +08:00
|
|
|
)
|
2015-05-02 02:46:07 +08:00
|
|
|
from django.db.migrations.loader import MigrationLoader
|
2013-05-18 19:49:56 +08:00
|
|
|
from django.db.migrations.recorder import MigrationRecorder
|
2016-09-02 04:19:29 +08:00
|
|
|
from django.test import TestCase, modify_settings, override_settings
|
2013-05-18 19:49:56 +08:00
|
|
|
|
2018-03-16 03:32:56 +08:00
|
|
|
from .test_base import MigrationTestBase
|
|
|
|
|
2013-05-18 19:49:56 +08:00
|
|
|
|
|
|
|
class RecorderTests(TestCase):
|
|
|
|
"""
|
2013-05-30 00:47:10 +08:00
|
|
|
Tests recording migrations as applied or not.
|
2013-05-18 19:49:56 +08:00
|
|
|
"""
|
2018-07-12 12:12:20 +08:00
|
|
|
databases = {'default', 'other'}
|
2013-05-18 19:49:56 +08:00
|
|
|
|
|
|
|
def test_apply(self):
|
|
|
|
"""
|
|
|
|
Tests marking migrations as applied/unapplied.
|
|
|
|
"""
|
|
|
|
recorder = MigrationRecorder(connection)
|
|
|
|
self.assertEqual(
|
2017-06-02 07:08:59 +08:00
|
|
|
{(x, y) for (x, y) in recorder.applied_migrations() if x == "myapp"},
|
2013-05-18 19:49:56 +08:00
|
|
|
set(),
|
|
|
|
)
|
|
|
|
recorder.record_applied("myapp", "0432_ponies")
|
|
|
|
self.assertEqual(
|
2017-06-02 07:08:59 +08:00
|
|
|
{(x, y) for (x, y) in recorder.applied_migrations() if x == "myapp"},
|
2014-09-26 20:31:50 +08:00
|
|
|
{("myapp", "0432_ponies")},
|
2013-05-18 19:49:56 +08:00
|
|
|
)
|
2014-04-30 22:53:20 +08:00
|
|
|
# That should not affect records of another database
|
|
|
|
recorder_other = MigrationRecorder(connections['other'])
|
|
|
|
self.assertEqual(
|
2017-06-02 07:08:59 +08:00
|
|
|
{(x, y) for (x, y) in recorder_other.applied_migrations() if x == "myapp"},
|
2014-04-30 22:53:20 +08:00
|
|
|
set(),
|
|
|
|
)
|
2013-05-18 19:49:56 +08:00
|
|
|
recorder.record_unapplied("myapp", "0432_ponies")
|
|
|
|
self.assertEqual(
|
2017-06-02 07:08:59 +08:00
|
|
|
{(x, y) for (x, y) in recorder.applied_migrations() if x == "myapp"},
|
2013-05-18 19:49:56 +08:00
|
|
|
set(),
|
|
|
|
)
|
2013-05-30 00:47:10 +08:00
|
|
|
|
|
|
|
|
2013-06-19 22:36:02 +08:00
|
|
|
class LoaderTests(TestCase):
|
2013-05-30 00:47:10 +08:00
|
|
|
"""
|
|
|
|
Tests the disk and database loader, and running through migrations
|
|
|
|
in memory.
|
|
|
|
"""
|
2020-12-31 05:55:31 +08:00
|
|
|
def setUp(self):
|
|
|
|
self.applied_records = []
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
# Unapply records on databases that don't roll back changes after each
|
|
|
|
# test method.
|
|
|
|
if not connection.features.supports_transactions:
|
|
|
|
for recorder, app, name in self.applied_records:
|
|
|
|
recorder.record_unapplied(app, name)
|
|
|
|
|
|
|
|
def record_applied(self, recorder, app, name):
|
|
|
|
recorder.record_applied(app, name)
|
|
|
|
self.applied_records.append((recorder, app, name))
|
2013-05-30 00:47:10 +08:00
|
|
|
|
2013-06-19 22:36:02 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
2014-06-17 03:59:24 +08:00
|
|
|
@modify_settings(INSTALLED_APPS={'append': 'basic'})
|
2013-05-30 00:47:10 +08:00
|
|
|
def test_load(self):
|
|
|
|
"""
|
|
|
|
Makes sure the loader can load the migrations for the test apps,
|
2013-12-24 19:25:17 +08:00
|
|
|
and then render them out to a new Apps.
|
2013-05-30 00:47:10 +08:00
|
|
|
"""
|
|
|
|
# Load and test the plan
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
self.assertEqual(
|
|
|
|
migration_loader.graph.forwards_plan(("migrations", "0002_second")),
|
2014-01-08 21:00:12 +08:00
|
|
|
[
|
|
|
|
("migrations", "0001_initial"),
|
|
|
|
("migrations", "0002_second"),
|
|
|
|
],
|
2013-05-30 00:47:10 +08:00
|
|
|
)
|
|
|
|
# Now render it out!
|
2014-05-01 03:25:12 +08:00
|
|
|
project_state = migration_loader.project_state(("migrations", "0002_second"))
|
2013-05-30 00:47:10 +08:00
|
|
|
self.assertEqual(len(project_state.models), 2)
|
|
|
|
|
|
|
|
author_state = project_state.models["migrations", "author"]
|
|
|
|
self.assertEqual(
|
2020-04-22 11:14:34 +08:00
|
|
|
list(author_state.fields),
|
2013-05-31 01:08:58 +08:00
|
|
|
["id", "name", "slug", "age", "rating"]
|
2013-05-30 00:47:10 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
book_state = project_state.models["migrations", "book"]
|
2020-04-22 11:14:34 +08:00
|
|
|
self.assertEqual(list(book_state.fields), ['id', 'author'])
|
2013-07-23 02:43:58 +08:00
|
|
|
|
2014-05-01 03:25:12 +08:00
|
|
|
# Ensure we've included unmigrated apps in there too
|
2014-06-17 03:59:24 +08:00
|
|
|
self.assertIn("basic", project_state.real_apps)
|
2014-05-01 03:25:12 +08:00
|
|
|
|
2018-03-09 15:57:47 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={
|
|
|
|
'migrations': 'migrations.test_migrations',
|
|
|
|
'migrations2': 'migrations2.test_migrations_2',
|
|
|
|
})
|
|
|
|
@modify_settings(INSTALLED_APPS={'append': 'migrations2'})
|
|
|
|
def test_plan_handles_repeated_migrations(self):
|
|
|
|
"""
|
|
|
|
_generate_plan() doesn't readd migrations already in the plan (#29180).
|
|
|
|
"""
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
nodes = [('migrations', '0002_second'), ('migrations2', '0001_initial')]
|
|
|
|
self.assertEqual(
|
|
|
|
migration_loader.graph._generate_plan(nodes, at_end=True),
|
|
|
|
[('migrations', '0001_initial'), ('migrations', '0002_second'), ('migrations2', '0001_initial')]
|
|
|
|
)
|
|
|
|
|
2014-09-06 02:06:02 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_unmigdep"})
|
2014-01-08 21:00:12 +08:00
|
|
|
def test_load_unmigrated_dependency(self):
|
|
|
|
"""
|
|
|
|
Makes sure the loader can load migrations with a dependency on an unmigrated app.
|
|
|
|
"""
|
|
|
|
# Load and test the plan
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
self.assertEqual(
|
|
|
|
migration_loader.graph.forwards_plan(("migrations", "0001_initial")),
|
|
|
|
[
|
2014-06-17 06:49:37 +08:00
|
|
|
('contenttypes', '0001_initial'),
|
|
|
|
('auth', '0001_initial'),
|
2014-01-08 21:00:12 +08:00
|
|
|
("migrations", "0001_initial"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
# Now render it out!
|
2014-05-01 03:25:12 +08:00
|
|
|
project_state = migration_loader.project_state(("migrations", "0001_initial"))
|
2014-01-09 05:20:29 +08:00
|
|
|
self.assertEqual(len([m for a, m in project_state.models if a == "migrations"]), 1)
|
2014-01-08 21:00:12 +08:00
|
|
|
|
|
|
|
book_state = project_state.models["migrations", "book"]
|
2020-04-22 11:14:34 +08:00
|
|
|
self.assertEqual(list(book_state.fields), ['id', 'user'])
|
2014-01-08 21:00:12 +08:00
|
|
|
|
2014-05-29 18:30:47 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_run_before"})
|
|
|
|
def test_run_before(self):
|
|
|
|
"""
|
|
|
|
Makes sure the loader uses Migration.run_before.
|
|
|
|
"""
|
|
|
|
# Load and test the plan
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
self.assertEqual(
|
|
|
|
migration_loader.graph.forwards_plan(("migrations", "0002_second")),
|
|
|
|
[
|
|
|
|
("migrations", "0001_initial"),
|
|
|
|
("migrations", "0003_third"),
|
|
|
|
("migrations", "0002_second"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
2014-06-18 14:27:03 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={
|
|
|
|
"migrations": "migrations.test_migrations_first",
|
|
|
|
"migrations2": "migrations2.test_migrations_2_first",
|
|
|
|
})
|
|
|
|
@modify_settings(INSTALLED_APPS={'append': 'migrations2'})
|
|
|
|
def test_first(self):
|
|
|
|
"""
|
|
|
|
Makes sure the '__first__' migrations build correctly.
|
|
|
|
"""
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
self.assertEqual(
|
|
|
|
migration_loader.graph.forwards_plan(("migrations", "second")),
|
|
|
|
[
|
|
|
|
("migrations", "thefirst"),
|
|
|
|
("migrations2", "0001_initial"),
|
|
|
|
("migrations2", "0002_second"),
|
|
|
|
("migrations", "second"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
2013-07-23 02:43:58 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
|
|
|
def test_name_match(self):
|
|
|
|
"Tests prefix name matching"
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
self.assertEqual(
|
|
|
|
migration_loader.get_migration_by_prefix("migrations", "0001").name,
|
|
|
|
"0001_initial",
|
|
|
|
)
|
|
|
|
with self.assertRaises(AmbiguityError):
|
|
|
|
migration_loader.get_migration_by_prefix("migrations", "0")
|
|
|
|
with self.assertRaises(KeyError):
|
|
|
|
migration_loader.get_migration_by_prefix("migrations", "blarg")
|
2013-09-06 04:51:11 +08:00
|
|
|
|
|
|
|
def test_load_import_error(self):
|
2015-02-04 07:02:59 +08:00
|
|
|
with override_settings(MIGRATION_MODULES={"migrations": "import_error_package"}):
|
2013-09-06 04:51:11 +08:00
|
|
|
with self.assertRaises(ImportError):
|
2013-10-24 05:56:54 +08:00
|
|
|
MigrationLoader(connection)
|
2013-09-06 04:51:31 +08:00
|
|
|
|
|
|
|
def test_load_module_file(self):
|
|
|
|
with override_settings(MIGRATION_MODULES={"migrations": "migrations.faulty_migrations.file"}):
|
2015-05-25 03:17:39 +08:00
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
self.assertIn(
|
|
|
|
"migrations", loader.unmigrated_apps,
|
|
|
|
"App with migrations module file not in unmigrated apps."
|
|
|
|
)
|
2013-09-06 04:51:31 +08:00
|
|
|
|
|
|
|
def test_load_empty_dir(self):
|
|
|
|
with override_settings(MIGRATION_MODULES={"migrations": "migrations.faulty_migrations.namespace"}):
|
2015-05-25 03:17:39 +08:00
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
self.assertIn(
|
|
|
|
"migrations", loader.unmigrated_apps,
|
|
|
|
"App missing __init__.py in migrations module not in unmigrated apps."
|
|
|
|
)
|
2013-10-24 05:56:54 +08:00
|
|
|
|
2015-09-12 08:23:10 +08:00
|
|
|
@override_settings(
|
|
|
|
INSTALLED_APPS=['migrations.migrations_test_apps.migrated_app'],
|
|
|
|
)
|
|
|
|
def test_marked_as_migrated(self):
|
|
|
|
"""
|
|
|
|
Undefined MIGRATION_MODULES implies default migration module.
|
|
|
|
"""
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
self.assertEqual(migration_loader.migrated_apps, {'migrated_app'})
|
|
|
|
self.assertEqual(migration_loader.unmigrated_apps, set())
|
|
|
|
|
|
|
|
@override_settings(
|
|
|
|
INSTALLED_APPS=['migrations.migrations_test_apps.migrated_app'],
|
|
|
|
MIGRATION_MODULES={"migrated_app": None},
|
|
|
|
)
|
|
|
|
def test_marked_as_unmigrated(self):
|
|
|
|
"""
|
|
|
|
MIGRATION_MODULES allows disabling of migrations for a particular app.
|
|
|
|
"""
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
self.assertEqual(migration_loader.migrated_apps, set())
|
|
|
|
self.assertEqual(migration_loader.unmigrated_apps, {'migrated_app'})
|
|
|
|
|
2016-08-27 05:04:12 +08:00
|
|
|
@override_settings(
|
|
|
|
INSTALLED_APPS=['migrations.migrations_test_apps.migrated_app'],
|
|
|
|
MIGRATION_MODULES={'migrated_app': 'missing-module'},
|
|
|
|
)
|
|
|
|
def test_explicit_missing_module(self):
|
|
|
|
"""
|
|
|
|
If a MIGRATION_MODULES override points to a missing module, the error
|
|
|
|
raised during the importation attempt should be propagated unless
|
|
|
|
`ignore_no_migrations=True`.
|
|
|
|
"""
|
|
|
|
with self.assertRaisesMessage(ImportError, 'missing-module'):
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
migration_loader = MigrationLoader(connection, ignore_no_migrations=True)
|
|
|
|
self.assertEqual(migration_loader.migrated_apps, set())
|
|
|
|
self.assertEqual(migration_loader.unmigrated_apps, {'migrated_app'})
|
|
|
|
|
2013-10-24 05:56:54 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed"})
|
|
|
|
def test_loading_squashed(self):
|
|
|
|
"Tests loading a squashed migration"
|
|
|
|
migration_loader = MigrationLoader(connection)
|
|
|
|
recorder = MigrationRecorder(connection)
|
2016-02-25 00:22:09 +08:00
|
|
|
self.addCleanup(recorder.flush)
|
2013-10-24 05:56:54 +08:00
|
|
|
# Loading with nothing applied should just give us the one node
|
|
|
|
self.assertEqual(
|
2014-05-30 07:43:49 +08:00
|
|
|
len([x for x in migration_loader.graph.nodes if x[0] == "migrations"]),
|
2013-10-24 05:56:54 +08:00
|
|
|
1,
|
|
|
|
)
|
|
|
|
# However, fake-apply one migration and it should now use the old two
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '0001_initial')
|
2013-10-24 05:56:54 +08:00
|
|
|
migration_loader.build_graph()
|
|
|
|
self.assertEqual(
|
2014-05-30 07:43:49 +08:00
|
|
|
len([x for x in migration_loader.graph.nodes if x[0] == "migrations"]),
|
2013-10-24 05:56:54 +08:00
|
|
|
2,
|
|
|
|
)
|
2014-09-26 06:24:17 +08:00
|
|
|
|
|
|
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed_complex"})
|
|
|
|
def test_loading_squashed_complex(self):
|
|
|
|
"Tests loading a complex set of squashed migrations"
|
|
|
|
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
recorder = MigrationRecorder(connection)
|
2016-02-25 00:22:09 +08:00
|
|
|
self.addCleanup(recorder.flush)
|
2014-09-26 06:24:17 +08:00
|
|
|
|
|
|
|
def num_nodes():
|
|
|
|
plan = set(loader.graph.forwards_plan(('migrations', '7_auto')))
|
2019-03-08 08:36:55 +08:00
|
|
|
return len(plan - loader.applied_migrations.keys())
|
2014-09-26 06:24:17 +08:00
|
|
|
|
|
|
|
# Empty database: use squashed migration
|
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 5)
|
|
|
|
|
|
|
|
# Starting at 1 or 2 should use the squashed migration too
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '1_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 4)
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '2_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 3)
|
|
|
|
|
|
|
|
# However, starting at 3 to 5 cannot use the squashed migration
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '3_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 4)
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '4_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 3)
|
|
|
|
|
|
|
|
# Starting at 5 to 7 we are passed the squashed migrations
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '5_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 2)
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '6_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 1)
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '7_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 0)
|
|
|
|
|
2014-10-30 23:55:38 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={
|
|
|
|
"app1": "migrations.test_migrations_squashed_complex_multi_apps.app1",
|
|
|
|
"app2": "migrations.test_migrations_squashed_complex_multi_apps.app2",
|
|
|
|
})
|
|
|
|
@modify_settings(INSTALLED_APPS={'append': [
|
|
|
|
"migrations.test_migrations_squashed_complex_multi_apps.app1",
|
|
|
|
"migrations.test_migrations_squashed_complex_multi_apps.app2",
|
|
|
|
]})
|
|
|
|
def test_loading_squashed_complex_multi_apps(self):
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
loader.build_graph()
|
|
|
|
|
|
|
|
plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
|
2015-06-02 07:22:10 +08:00
|
|
|
expected_plan = {
|
2016-05-08 09:01:23 +08:00
|
|
|
('app1', '1_auto'),
|
2014-10-30 23:55:38 +08:00
|
|
|
('app2', '1_squashed_2'),
|
2016-05-08 09:01:23 +08:00
|
|
|
('app1', '2_squashed_3'),
|
|
|
|
('app1', '4_auto'),
|
2015-06-02 07:22:10 +08:00
|
|
|
}
|
|
|
|
self.assertEqual(plan, expected_plan)
|
|
|
|
|
|
|
|
@override_settings(MIGRATION_MODULES={
|
|
|
|
"app1": "migrations.test_migrations_squashed_complex_multi_apps.app1",
|
|
|
|
"app2": "migrations.test_migrations_squashed_complex_multi_apps.app2",
|
|
|
|
})
|
|
|
|
@modify_settings(INSTALLED_APPS={'append': [
|
|
|
|
"migrations.test_migrations_squashed_complex_multi_apps.app1",
|
|
|
|
"migrations.test_migrations_squashed_complex_multi_apps.app2",
|
|
|
|
]})
|
|
|
|
def test_loading_squashed_complex_multi_apps_partially_applied(self):
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
recorder = MigrationRecorder(connection)
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'app1', '1_auto')
|
|
|
|
self.record_applied(recorder, 'app1', '2_auto')
|
2015-06-02 07:22:10 +08:00
|
|
|
loader.build_graph()
|
|
|
|
|
|
|
|
plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
|
2019-03-08 08:36:55 +08:00
|
|
|
plan = plan - loader.applied_migrations.keys()
|
2015-06-02 07:22:10 +08:00
|
|
|
expected_plan = {
|
|
|
|
('app2', '1_squashed_2'),
|
2016-05-08 09:01:23 +08:00
|
|
|
('app1', '3_auto'),
|
|
|
|
('app1', '4_auto'),
|
2015-06-02 07:22:10 +08:00
|
|
|
}
|
|
|
|
|
2014-10-30 23:55:38 +08:00
|
|
|
self.assertEqual(plan, expected_plan)
|
|
|
|
|
2014-09-26 06:24:17 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed_erroneous"})
|
|
|
|
def test_loading_squashed_erroneous(self):
|
|
|
|
"Tests loading a complex but erroneous set of squashed migrations"
|
|
|
|
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
recorder = MigrationRecorder(connection)
|
2016-02-25 00:22:09 +08:00
|
|
|
self.addCleanup(recorder.flush)
|
2014-09-26 06:24:17 +08:00
|
|
|
|
|
|
|
def num_nodes():
|
|
|
|
plan = set(loader.graph.forwards_plan(('migrations', '7_auto')))
|
2019-03-08 08:36:55 +08:00
|
|
|
return len(plan - loader.applied_migrations.keys())
|
2014-09-26 06:24:17 +08:00
|
|
|
|
|
|
|
# Empty database: use squashed migration
|
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 5)
|
|
|
|
|
|
|
|
# Starting at 1 or 2 should use the squashed migration too
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '1_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 4)
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '2_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 3)
|
|
|
|
|
2017-02-03 09:43:21 +08:00
|
|
|
# However, starting at 3 or 4, nonexistent migrations would be needed.
|
2014-09-26 06:24:17 +08:00
|
|
|
msg = ("Migration migrations.6_auto depends on nonexistent node ('migrations', '5_auto'). "
|
|
|
|
"Django tried to replace migration migrations.5_auto with any of "
|
|
|
|
"[migrations.3_squashed_5] but wasn't able to because some of the replaced "
|
|
|
|
"migrations are already applied.")
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '3_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
|
|
|
loader.build_graph()
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '4_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
|
|
|
loader.build_graph()
|
|
|
|
|
|
|
|
# Starting at 5 to 7 we are passed the squashed migrations
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '5_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 2)
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '6_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 1)
|
|
|
|
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '7_auto')
|
2014-09-26 06:24:17 +08:00
|
|
|
loader.build_graph()
|
|
|
|
self.assertEqual(num_nodes(), 0)
|
2016-04-02 20:46:59 +08:00
|
|
|
|
|
|
|
@override_settings(
|
|
|
|
MIGRATION_MODULES={'migrations': 'migrations.test_migrations'},
|
|
|
|
INSTALLED_APPS=['migrations'],
|
|
|
|
)
|
|
|
|
def test_check_consistent_history(self):
|
|
|
|
loader = MigrationLoader(connection=None)
|
|
|
|
loader.check_consistent_history(connection)
|
|
|
|
recorder = MigrationRecorder(connection)
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '0002_second')
|
2016-08-19 06:17:01 +08:00
|
|
|
msg = (
|
|
|
|
"Migration migrations.0002_second is applied before its dependency "
|
|
|
|
"migrations.0001_initial on database 'default'."
|
|
|
|
)
|
2016-04-02 20:46:59 +08:00
|
|
|
with self.assertRaisesMessage(InconsistentMigrationHistory, msg):
|
|
|
|
loader.check_consistent_history(connection)
|
2016-05-08 08:56:13 +08:00
|
|
|
|
2016-08-05 07:37:42 +08:00
|
|
|
@override_settings(
|
|
|
|
MIGRATION_MODULES={'migrations': 'migrations.test_migrations_squashed_extra'},
|
|
|
|
INSTALLED_APPS=['migrations'],
|
|
|
|
)
|
|
|
|
def test_check_consistent_history_squashed(self):
|
|
|
|
"""
|
|
|
|
MigrationLoader.check_consistent_history() should ignore unapplied
|
|
|
|
squashed migrations that have all of their `replaces` applied.
|
|
|
|
"""
|
|
|
|
loader = MigrationLoader(connection=None)
|
|
|
|
recorder = MigrationRecorder(connection)
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '0001_initial')
|
|
|
|
self.record_applied(recorder, 'migrations', '0002_second')
|
2016-08-05 07:37:42 +08:00
|
|
|
loader.check_consistent_history(connection)
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'migrations', '0003_third')
|
2016-08-05 07:37:42 +08:00
|
|
|
loader.check_consistent_history(connection)
|
|
|
|
|
2016-05-08 08:56:13 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={
|
|
|
|
"app1": "migrations.test_migrations_squashed_ref_squashed.app1",
|
|
|
|
"app2": "migrations.test_migrations_squashed_ref_squashed.app2",
|
|
|
|
})
|
|
|
|
@modify_settings(INSTALLED_APPS={'append': [
|
|
|
|
"migrations.test_migrations_squashed_ref_squashed.app1",
|
|
|
|
"migrations.test_migrations_squashed_ref_squashed.app2",
|
|
|
|
]})
|
|
|
|
def test_loading_squashed_ref_squashed(self):
|
|
|
|
"Tests loading a squashed migration with a new migration referencing it"
|
2016-09-17 00:15:00 +08:00
|
|
|
r"""
|
2016-09-16 23:15:20 +08:00
|
|
|
The sample migrations are structured like this:
|
2016-05-08 08:56:13 +08:00
|
|
|
|
|
|
|
app_1 1 --> 2 ---------------------*--> 3 *--> 4
|
|
|
|
\ / /
|
|
|
|
*-------------------*----/--> 2_sq_3 --*
|
|
|
|
\ / /
|
|
|
|
=============== \ ============= / == / ======================
|
|
|
|
app_2 *--> 1_sq_2 --* /
|
|
|
|
\ /
|
|
|
|
*--> 1 --> 2 --*
|
|
|
|
|
|
|
|
Where 2_sq_3 is a replacing migration for 2 and 3 in app_1,
|
|
|
|
as 1_sq_2 is a replacing migration for 1 and 2 in app_2.
|
|
|
|
"""
|
|
|
|
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
recorder = MigrationRecorder(connection)
|
|
|
|
self.addCleanup(recorder.flush)
|
|
|
|
|
|
|
|
# Load with nothing applied: both migrations squashed.
|
|
|
|
loader.build_graph()
|
|
|
|
plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
|
2019-03-08 08:36:55 +08:00
|
|
|
plan = plan - loader.applied_migrations.keys()
|
2016-05-08 08:56:13 +08:00
|
|
|
expected_plan = {
|
|
|
|
('app1', '1_auto'),
|
|
|
|
('app2', '1_squashed_2'),
|
|
|
|
('app1', '2_squashed_3'),
|
|
|
|
('app1', '4_auto'),
|
|
|
|
}
|
|
|
|
self.assertEqual(plan, expected_plan)
|
|
|
|
|
|
|
|
# Fake-apply a few from app1: unsquashes migration in app1.
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'app1', '1_auto')
|
|
|
|
self.record_applied(recorder, 'app1', '2_auto')
|
2016-05-08 08:56:13 +08:00
|
|
|
loader.build_graph()
|
|
|
|
plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
|
2019-03-08 08:36:55 +08:00
|
|
|
plan = plan - loader.applied_migrations.keys()
|
2016-05-08 08:56:13 +08:00
|
|
|
expected_plan = {
|
|
|
|
('app2', '1_squashed_2'),
|
|
|
|
('app1', '3_auto'),
|
|
|
|
('app1', '4_auto'),
|
|
|
|
}
|
|
|
|
self.assertEqual(plan, expected_plan)
|
|
|
|
|
|
|
|
# Fake-apply one from app2: unsquashes migration in app2 too.
|
2020-12-31 05:55:31 +08:00
|
|
|
self.record_applied(recorder, 'app2', '1_auto')
|
2016-05-08 08:56:13 +08:00
|
|
|
loader.build_graph()
|
|
|
|
plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
|
2019-03-08 08:36:55 +08:00
|
|
|
plan = plan - loader.applied_migrations.keys()
|
2016-05-08 08:56:13 +08:00
|
|
|
expected_plan = {
|
|
|
|
('app2', '2_auto'),
|
|
|
|
('app1', '3_auto'),
|
|
|
|
('app1', '4_auto'),
|
|
|
|
}
|
|
|
|
self.assertEqual(plan, expected_plan)
|
2018-03-16 03:32:56 +08:00
|
|
|
|
2018-09-12 00:51:11 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={'migrations': 'migrations.test_migrations_private'})
|
|
|
|
def test_ignore_files(self):
|
|
|
|
"""Files prefixed with underscore, tilde, or dot aren't loaded."""
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
loader.load_disk()
|
|
|
|
migrations = [name for app, name in loader.disk_migrations if app == 'migrations']
|
|
|
|
self.assertEqual(migrations, ['0001_initial'])
|
|
|
|
|
2019-03-29 04:47:05 +08:00
|
|
|
@override_settings(
|
|
|
|
MIGRATION_MODULES={'migrations': 'migrations.test_migrations_namespace_package'},
|
|
|
|
)
|
|
|
|
def test_loading_namespace_package(self):
|
2020-07-22 13:04:06 +08:00
|
|
|
"""Migration directories without an __init__.py file are ignored."""
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
loader.load_disk()
|
|
|
|
migrations = [name for app, name in loader.disk_migrations if app == 'migrations']
|
|
|
|
self.assertEqual(migrations, [])
|
2019-03-29 04:47:05 +08:00
|
|
|
|
2020-12-29 06:05:18 +08:00
|
|
|
@override_settings(MIGRATION_MODULES={'migrations': 'migrations.test_migrations'})
|
|
|
|
def test_loading_package_without__file__(self):
|
|
|
|
"""
|
|
|
|
To support frozen environments, MigrationLoader loads migrations from
|
|
|
|
regular packages with no __file__ attribute.
|
|
|
|
"""
|
|
|
|
test_module = import_module('migrations.test_migrations')
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
# __file__ == __spec__.origin or the latter is None and former is
|
|
|
|
# undefined.
|
|
|
|
module_file = test_module.__file__
|
|
|
|
module_origin = test_module.__spec__.origin
|
|
|
|
module_has_location = test_module.__spec__.has_location
|
|
|
|
try:
|
|
|
|
del test_module.__file__
|
|
|
|
test_module.__spec__.origin = None
|
|
|
|
test_module.__spec__.has_location = False
|
|
|
|
loader.load_disk()
|
|
|
|
migrations = [
|
|
|
|
name
|
|
|
|
for app, name in loader.disk_migrations
|
|
|
|
if app == 'migrations'
|
|
|
|
]
|
|
|
|
self.assertCountEqual(migrations, ['0001_initial', '0002_second'])
|
|
|
|
finally:
|
|
|
|
test_module.__file__ = module_file
|
|
|
|
test_module.__spec__.origin = module_origin
|
|
|
|
test_module.__spec__.has_location = module_has_location
|
|
|
|
|
2018-03-16 03:32:56 +08:00
|
|
|
|
|
|
|
class PycLoaderTests(MigrationTestBase):
|
|
|
|
|
|
|
|
def test_valid(self):
|
|
|
|
"""
|
|
|
|
To support frozen environments, MigrationLoader loads .pyc migrations.
|
|
|
|
"""
|
|
|
|
with self.temporary_migration_module(module='migrations.test_migrations') as migration_dir:
|
|
|
|
# Compile .py files to .pyc files and delete .py files.
|
|
|
|
compileall.compile_dir(migration_dir, force=True, quiet=1, legacy=True)
|
|
|
|
for name in os.listdir(migration_dir):
|
|
|
|
if name.endswith('.py'):
|
|
|
|
os.remove(os.path.join(migration_dir, name))
|
|
|
|
loader = MigrationLoader(connection)
|
|
|
|
self.assertIn(('migrations', '0001_initial'), loader.disk_migrations)
|
|
|
|
|
|
|
|
def test_invalid(self):
|
|
|
|
"""
|
|
|
|
MigrationLoader reraises ImportErrors caused by "bad magic number" pyc
|
|
|
|
files with a more helpful message.
|
|
|
|
"""
|
2018-06-16 01:56:10 +08:00
|
|
|
with self.temporary_migration_module(module='migrations.test_migrations_bad_pyc') as migration_dir:
|
|
|
|
# The -tpl suffix is to avoid the pyc exclusion in MANIFEST.in.
|
|
|
|
os.rename(
|
|
|
|
os.path.join(migration_dir, '0001_initial.pyc-tpl'),
|
|
|
|
os.path.join(migration_dir, '0001_initial.pyc'),
|
|
|
|
)
|
2018-03-16 03:32:56 +08:00
|
|
|
msg = (
|
2018-04-03 10:54:05 +08:00
|
|
|
r"Couldn't import '\w+.migrations.0001_initial' as it appears "
|
2018-03-16 03:32:56 +08:00
|
|
|
"to be a stale .pyc file."
|
|
|
|
)
|
|
|
|
with self.assertRaisesRegex(ImportError, msg):
|
|
|
|
MigrationLoader(connection)
|