summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/migrations/loader.py4
-rw-r--r--tests/migrations/test_loader.py69
-rw-r--r--tests/migrations/test_migrations_squashed_replaced_order/__init__.py0
-rw-r--r--tests/migrations/test_migrations_squashed_replaced_order/app1/0001_initial.py9
-rw-r--r--tests/migrations/test_migrations_squashed_replaced_order/app1/0001_squashed_initial.py13
-rw-r--r--tests/migrations/test_migrations_squashed_replaced_order/app1/0002_squashed_initial.py16
-rw-r--r--tests/migrations/test_migrations_squashed_replaced_order/app1/__init__.py0
-rw-r--r--tests/migrations/test_migrations_squashed_replaced_order/app2/0001_initial.py11
-rw-r--r--tests/migrations/test_migrations_squashed_replaced_order/app2/0001_squashed_initial.py13
-rw-r--r--tests/migrations/test_migrations_squashed_replaced_order/app2/__init__.py0
10 files changed, 133 insertions, 2 deletions
diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index 66944c7ab7..db81f6f78f 100644
--- a/django/db/migrations/loader.py
+++ b/django/db/migrations/loader.py
@@ -109,11 +109,11 @@ class MigrationLoader:
if was_loaded:
reload(module)
self.migrated_apps.add(app_config.label)
- migration_names = {
+ migration_names = [
name
for _, name, is_pkg in pkgutil.iter_modules(module.__path__)
if not is_pkg and name[0] not in "_~"
- }
+ ]
# Load migrations
for migration_name in migration_names:
migration_path = "%s.%s" % (module_name, migration_name)
diff --git a/tests/migrations/test_loader.py b/tests/migrations/test_loader.py
index 7cf11f7faa..efa7a94166 100644
--- a/tests/migrations/test_loader.py
+++ b/tests/migrations/test_loader.py
@@ -1,7 +1,12 @@
import compileall
import os
+import subprocess
+import sys
+import tempfile
from importlib import import_module
+from pathlib import Path
+from django.conf import settings
from django.db import connection, connections
from django.db.migrations.exceptions import (
AmbiguityError,
@@ -649,6 +654,70 @@ class LoaderTests(TestCase):
test_module.__spec__.origin = module_origin
test_module.__spec__.has_location = module_has_location
+ def test_loading_order_does_not_create_circular_dependency(self):
+ """
+ Before, for these migrations:
+ app1
+ [ ] 0001_squashed_initial <- replaces app1.0001
+ [ ] 0002_squashed_initial <- replaces app1.0001
+ depends on app1.0001_squashed_initial & app2.0001_squashed_initial
+ app2
+ [ ] 0001_squashed_initial <- replaces app2.0001
+
+ When loading app1's migrations, if 0002_squashed_initial was first:
+ {'0002_squashed_initial', '0001_initial', '0001_squashed_initial'}
+ Then CircularDependencyError was raised, but it's resolvable as:
+ {'0001_initial', '0001_squashed_initial', '0002_squashed_initial'}
+ """
+ # Create a test settings file to provide to the subprocess.
+ MIGRATION_MODULES = {
+ "app1": "migrations.test_migrations_squashed_replaced_order.app1",
+ "app2": "migrations.test_migrations_squashed_replaced_order.app2",
+ }
+ INSTALLED_APPS = [
+ "migrations.test_migrations_squashed_replaced_order.app1",
+ "migrations.test_migrations_squashed_replaced_order.app2",
+ ]
+ tests_dir = Path(__file__).parent.parent
+ with tempfile.NamedTemporaryFile(
+ mode="w", encoding="utf-8", suffix=".py", dir=tests_dir, delete=False
+ ) as test_settings:
+ for attr, value in settings._wrapped.__dict__.items():
+ if attr.isupper():
+ test_settings.write(f"{attr} = {value!r}\n")
+ # Provide overrides here, instead of via decorators.
+ test_settings.write(f"DATABASES = {settings.DATABASES}\n")
+ test_settings.write(f"MIGRATION_MODULES = {MIGRATION_MODULES}\n")
+ # Isolate away other test apps.
+ test_settings.write(
+ "INSTALLED_APPS=[a for a in INSTALLED_APPS if a.startswith('django')]\n"
+ )
+ test_settings.write(f"INSTALLED_APPS += {INSTALLED_APPS}\n")
+ test_settings_name = test_settings.name
+ self.addCleanup(os.remove, test_settings_name)
+
+ test_environ = os.environ.copy()
+ test_environ["PYTHONPATH"] = str(tests_dir)
+ # Ensure deterministic failures.
+ test_environ["PYTHONHASHSEED"] = "1"
+
+ args = [
+ sys.executable,
+ "-m",
+ "django",
+ "showmigrations",
+ "app1",
+ "--skip-checks",
+ "--settings",
+ Path(test_settings_name).stem,
+ ]
+ try:
+ subprocess.run(
+ args, capture_output=True, env=test_environ, check=True, text=True
+ )
+ except subprocess.CalledProcessError as err:
+ self.fail(err.stderr)
+
class PycLoaderTests(MigrationTestBase):
def test_valid(self):
diff --git a/tests/migrations/test_migrations_squashed_replaced_order/__init__.py b/tests/migrations/test_migrations_squashed_replaced_order/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_replaced_order/__init__.py
diff --git a/tests/migrations/test_migrations_squashed_replaced_order/app1/0001_initial.py b/tests/migrations/test_migrations_squashed_replaced_order/app1/0001_initial.py
new file mode 100644
index 0000000000..cf311d1f93
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_replaced_order/app1/0001_initial.py
@@ -0,0 +1,9 @@
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ dependencies = []
+
+ operations = []
diff --git a/tests/migrations/test_migrations_squashed_replaced_order/app1/0001_squashed_initial.py b/tests/migrations/test_migrations_squashed_replaced_order/app1/0001_squashed_initial.py
new file mode 100644
index 0000000000..7a77d339bc
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_replaced_order/app1/0001_squashed_initial.py
@@ -0,0 +1,13 @@
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ replaces = [
+ ("app1", "0001_initial"),
+ ]
+
+ dependencies = []
+
+ operations = []
diff --git a/tests/migrations/test_migrations_squashed_replaced_order/app1/0002_squashed_initial.py b/tests/migrations/test_migrations_squashed_replaced_order/app1/0002_squashed_initial.py
new file mode 100644
index 0000000000..fc16ed6d6e
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_replaced_order/app1/0002_squashed_initial.py
@@ -0,0 +1,16 @@
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ replaces = [
+ ("app1", "0001_initial"),
+ ]
+
+ dependencies = [
+ ("app1", "0001_squashed_initial"),
+ ("app2", "0001_squashed_initial"),
+ ]
+
+ operations = []
diff --git a/tests/migrations/test_migrations_squashed_replaced_order/app1/__init__.py b/tests/migrations/test_migrations_squashed_replaced_order/app1/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_replaced_order/app1/__init__.py
diff --git a/tests/migrations/test_migrations_squashed_replaced_order/app2/0001_initial.py b/tests/migrations/test_migrations_squashed_replaced_order/app2/0001_initial.py
new file mode 100644
index 0000000000..05d0409496
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_replaced_order/app2/0001_initial.py
@@ -0,0 +1,11 @@
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ dependencies = [
+ ("app1", "0001_initial"),
+ ]
+
+ operations = []
diff --git a/tests/migrations/test_migrations_squashed_replaced_order/app2/0001_squashed_initial.py b/tests/migrations/test_migrations_squashed_replaced_order/app2/0001_squashed_initial.py
new file mode 100644
index 0000000000..de6bfe4135
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_replaced_order/app2/0001_squashed_initial.py
@@ -0,0 +1,13 @@
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ replaces = [
+ ("app2", "0001_initial"),
+ ]
+
+ dependencies = []
+
+ operations = []
diff --git a/tests/migrations/test_migrations_squashed_replaced_order/app2/__init__.py b/tests/migrations/test_migrations_squashed_replaced_order/app2/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/migrations/test_migrations_squashed_replaced_order/app2/__init__.py