diff --git a/tests/migrations/migrations_test_apps/without_init_file/migrations/.keep b/tests/migrations/migrations_test_apps/distributed_app_location_1/namespace_app/migrations/.gitkeep similarity index 100% rename from tests/migrations/migrations_test_apps/without_init_file/migrations/.keep rename to tests/migrations/migrations_test_apps/distributed_app_location_1/namespace_app/migrations/.gitkeep diff --git a/tests/migrations/migrations_test_apps/distributed_app_location_2/namespace_app/migrations/.gitkeep b/tests/migrations/migrations_test_apps/distributed_app_location_2/namespace_app/migrations/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/migrations_test_apps/without_init_file/migrations/.gitkeep b/tests/migrations/migrations_test_apps/without_init_file/migrations/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 6ef172ee6f..cab2906ed1 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -4,6 +4,7 @@ import io import os import shutil import sys +from pathlib import Path from unittest import mock from django.apps import apps @@ -21,7 +22,7 @@ from django.db.backends.utils import truncate_name from django.db.migrations.exceptions import InconsistentMigrationHistory from django.db.migrations.recorder import MigrationRecorder from django.test import TestCase, override_settings, skipUnlessDBFeature -from django.test.utils import captured_stdout +from django.test.utils import captured_stdout, extend_sys_path from django.utils import timezone from django.utils.version import get_docs_version @@ -1729,6 +1730,25 @@ class MakeMigrationsTests(MigrationTestBase): call_command("makemigrations", stdout=out) self.assertIn("0001_initial.py", out.getvalue()) + def test_makemigrations_no_init_ambiguous(self): + """ + Migration directories without an __init__.py file are not allowed if + there are multiple namespace search paths that resolve to them. + """ + out = io.StringIO() + with self.temporary_migration_module( + module="migrations.test_migrations_no_init" + ) as migration_dir: + # Copy the project directory into another place under sys.path. + app_dir = Path(migration_dir).parent + os.remove(app_dir / "__init__.py") + project_dir = app_dir.parent + dest = project_dir.parent / "other_dir_in_path" + shutil.copytree(project_dir, dest) + with extend_sys_path(str(dest)): + call_command("makemigrations", stdout=out) + self.assertEqual("No changes detected\n", out.getvalue()) + def test_makemigrations_migrations_announce(self): """ makemigrations announces the migration at the default verbosity level. diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index 891efd8ac7..51783b7346 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -22,7 +22,8 @@ from django.core.validators import EmailValidator, RegexValidator from django.db import migrations, models from django.db.migrations.serializer import BaseSerializer from django.db.migrations.writer import MigrationWriter, OperationWriter -from django.test import SimpleTestCase +from django.test import SimpleTestCase, override_settings +from django.test.utils import extend_sys_path from django.utils.deconstruct import deconstructible from django.utils.functional import SimpleLazyObject from django.utils.timezone import get_default_timezone, get_fixed_timezone @@ -954,6 +955,29 @@ class WriterTests(SimpleTestCase): writer = MigrationWriter(migration) self.assertEqual(writer.path, expected_path) + @override_settings( + MIGRATION_MODULES={"namespace_app": "namespace_app.migrations"}, + INSTALLED_APPS=[ + "migrations.migrations_test_apps.distributed_app_location_2.namespace_app" + ], + ) + def test_migration_path_distributed_namespace(self): + base_dir = os.path.dirname(os.path.dirname(__file__)) + test_apps_dir = os.path.join(base_dir, "migrations", "migrations_test_apps") + expected_msg = ( + "Could not locate an appropriate location to create " + "migrations package namespace_app.migrations. Make sure the toplevel " + "package exists and can be imported." + ) + with extend_sys_path( + os.path.join(test_apps_dir, "distributed_app_location_1"), + os.path.join(test_apps_dir, "distributed_app_location_2"), + ): + migration = migrations.Migration("0001_initial", "namespace_app") + writer = MigrationWriter(migration) + with self.assertRaisesMessage(ValueError, expected_msg): + writer.path + def test_custom_operation(self): migration = type( "Migration",