diff --git a/django/apps/registry.py b/django/apps/registry.py index 7ff1c6625d..3cd6d480f1 100644 --- a/django/apps/registry.py +++ b/django/apps/registry.py @@ -79,7 +79,11 @@ class Apps(object): app_config = entry else: app_config = AppConfig.create(entry) - # TODO: check for duplicate app labels here (#21679). + if app_config.label in self.app_configs: + raise ImproperlyConfigured( + "Application labels aren't unique, " + "duplicates: %s" % app_config.label) + self.app_configs[app_config.label] = app_config # Check for duplicate app names. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 453714b6f1..af0acd7152 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -655,6 +655,22 @@ script with:: Otherwise, you will hit ``RuntimeError: App registry isn't ready yet.`` +App registry consistency +^^^^^^^^^^^^^^^^^^^^^^^^ + +It is no longer possible to have multiple installed applications with the same +label. In previous versions of Django, this didn't always work correctly, but +didn't crash outright either. + +If you have two apps with the same label, you should create an +:class:`~django.apps.AppConfig` for one of them and override its +:class:`~django.apps.AppConfig.label` there. You should then adjust your code +wherever it references this application or its models with the old label. + +You should make sure that your project doesn't import models from applications +that aren't in :setting:`INSTALLED_APPS`. Relations involving such models may +not be created properly. Future versions of Django may forbid this entirely. + Subclassing AppCommand ^^^^^^^^^^^^^^^^^^^^^^ @@ -663,13 +679,6 @@ Subclasses of :class:`~django.core.management.AppCommand` must now implement a ``handle_app()``. This method receives an :class:`~django.apps.AppConfig` instance instead of a models module. -App registry consistency -^^^^^^^^^^^^^^^^^^^^^^^^ - -You should make sure that your project doesn't import models from applications -that aren't in :setting:`INSTALLED_APPS`. Relations involving such models may -not be created properly. - Introspecting applications ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/apps/apps.py b/tests/apps/apps.py index c9d761deb0..a2c49fcbff 100644 --- a/tests/apps/apps.py +++ b/tests/apps/apps.py @@ -25,6 +25,10 @@ class NoSuchApp(AppConfig): name = 'there is no such app' +class PlainAppsConfig(AppConfig): + name = 'apps' + + class RelabeledAppsConfig(AppConfig): name = 'apps' label = 'relabeled' diff --git a/tests/apps/tests.py b/tests/apps/tests.py index 495d24dc3e..c31174a1f1 100644 --- a/tests/apps/tests.py +++ b/tests/apps/tests.py @@ -5,6 +5,7 @@ from django.apps.registry import Apps from django.core.exceptions import ImproperlyConfigured from django.db import models from django.test import TestCase, override_settings +from django.utils import six from .models import TotallyNormal, SoAlternative, new_apps @@ -115,6 +116,16 @@ class AppsTests(TestCase): def test_relabeling(self): self.assertEqual(apps.get_app_config('relabeled').name, 'apps') + def test_duplicate_labels(self): + with six.assertRaisesRegex(self, ImproperlyConfigured, "Application labels aren't unique"): + with self.settings(INSTALLED_APPS=['apps.apps.PlainAppsConfig', 'apps']): + pass + + def test_duplicate_names(self): + with six.assertRaisesRegex(self, ImproperlyConfigured, "Application names aren't unique"): + with self.settings(INSTALLED_APPS=['apps.apps.RelabeledAppsConfig', 'apps']): + pass + def test_models_py(self): """ Tests that the models in the models.py file were loaded correctly.