123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- import os
- from django.apps import AppConfig, apps
- from django.apps.registry import Apps
- from django.contrib.admin.models import LogEntry
- from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
- from django.db import models
- from django.test import SimpleTestCase, ignore_warnings, override_settings
- from django.test.utils import extend_sys_path, isolate_apps
- from django.utils.deprecation import RemovedInDjango41Warning
- from .explicit_default_config_app.apps import ExplicitDefaultConfig
- from .explicit_default_config_mismatch_app.not_apps import (
- ExplicitDefaultConfigMismatch,
- )
- from .models import SoAlternative, TotallyNormal, new_apps
- from .one_config_app.apps import OneConfig
- from .two_configs_one_default_app.apps import TwoConfig
- SOME_INSTALLED_APPS = [
- 'apps.apps.MyAdmin',
- 'apps.apps.MyAuth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- ]
- SOME_INSTALLED_APPS_NAMES = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- ] + SOME_INSTALLED_APPS[2:]
- HERE = os.path.dirname(__file__)
- class AppsTests(SimpleTestCase):
- def test_singleton_master(self):
- """
- Only one master registry can exist.
- """
- with self.assertRaises(RuntimeError):
- Apps(installed_apps=None)
- def test_ready(self):
- """
- Tests the ready property of the master registry.
- """
-
- self.assertIs(apps.ready, True)
-
- self.assertIs(Apps().ready, True)
-
- self.assertIs(apps.ready_event.is_set(), True)
- self.assertIs(Apps().ready_event.is_set(), True)
- def test_bad_app_config(self):
- """
- Tests when INSTALLED_APPS contains an incorrect app config.
- """
- msg = "'apps.apps.BadConfig' must supply a name attribute."
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- with self.settings(INSTALLED_APPS=['apps.apps.BadConfig']):
- pass
- def test_not_an_app_config(self):
- """
- Tests when INSTALLED_APPS contains a class that isn't an app config.
- """
- msg = "'apps.apps.NotAConfig' isn't a subclass of AppConfig."
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- with self.settings(INSTALLED_APPS=['apps.apps.NotAConfig']):
- pass
- def test_no_such_app(self):
- """
- Tests when INSTALLED_APPS contains an app that doesn't exist, either
- directly or via an app config.
- """
- with self.assertRaises(ImportError):
- with self.settings(INSTALLED_APPS=['there is no such app']):
- pass
- msg = "Cannot import 'there is no such app'. Check that 'apps.apps.NoSuchApp.name' is correct."
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- with self.settings(INSTALLED_APPS=['apps.apps.NoSuchApp']):
- pass
- def test_no_such_app_config(self):
- msg = "Module 'apps' does not contain a 'NoSuchConfig' class."
- with self.assertRaisesMessage(ImportError, msg):
- with self.settings(INSTALLED_APPS=['apps.NoSuchConfig']):
- pass
- def test_no_such_app_config_with_choices(self):
- msg = (
- "Module 'apps.apps' does not contain a 'NoSuchConfig' class. "
- "Choices are: 'BadConfig', 'MyAdmin', 'MyAuth', 'NoSuchApp', "
- "'PlainAppsConfig', 'RelabeledAppsConfig'."
- )
- with self.assertRaisesMessage(ImportError, msg):
- with self.settings(INSTALLED_APPS=['apps.apps.NoSuchConfig']):
- pass
- def test_no_config_app(self):
- """Load an app that doesn't provide an AppConfig class."""
- with self.settings(INSTALLED_APPS=['apps.no_config_app']):
- config = apps.get_app_config('no_config_app')
- self.assertIsInstance(config, AppConfig)
- def test_one_config_app(self):
- """Load an app that provides an AppConfig class."""
- with self.settings(INSTALLED_APPS=['apps.one_config_app']):
- config = apps.get_app_config('one_config_app')
- self.assertIsInstance(config, OneConfig)
- def test_two_configs_app(self):
- """Load an app that provides two AppConfig classes."""
- with self.settings(INSTALLED_APPS=['apps.two_configs_app']):
- config = apps.get_app_config('two_configs_app')
- self.assertIsInstance(config, AppConfig)
- def test_two_default_configs_app(self):
- """Load an app that provides two default AppConfig classes."""
- msg = (
- "'apps.two_default_configs_app.apps' declares more than one "
- "default AppConfig: 'TwoConfig', 'TwoConfigBis'."
- )
- with self.assertRaisesMessage(RuntimeError, msg):
- with self.settings(INSTALLED_APPS=['apps.two_default_configs_app']):
- pass
- def test_two_configs_one_default_app(self):
- """
- Load an app that provides two AppConfig classes, one being the default.
- """
- with self.settings(INSTALLED_APPS=['apps.two_configs_one_default_app']):
- config = apps.get_app_config('two_configs_one_default_app')
- self.assertIsInstance(config, TwoConfig)
- @override_settings(INSTALLED_APPS=SOME_INSTALLED_APPS)
- def test_get_app_configs(self):
- """
- Tests apps.get_app_configs().
- """
- app_configs = apps.get_app_configs()
- self.assertEqual([app_config.name for app_config in app_configs], SOME_INSTALLED_APPS_NAMES)
- @override_settings(INSTALLED_APPS=SOME_INSTALLED_APPS)
- def test_get_app_config(self):
- """
- Tests apps.get_app_config().
- """
- app_config = apps.get_app_config('admin')
- self.assertEqual(app_config.name, 'django.contrib.admin')
- app_config = apps.get_app_config('staticfiles')
- self.assertEqual(app_config.name, 'django.contrib.staticfiles')
- with self.assertRaises(LookupError):
- apps.get_app_config('admindocs')
- msg = "No installed app with label 'django.contrib.auth'. Did you mean 'myauth'"
- with self.assertRaisesMessage(LookupError, msg):
- apps.get_app_config('django.contrib.auth')
- @override_settings(INSTALLED_APPS=SOME_INSTALLED_APPS)
- def test_is_installed(self):
- """
- Tests apps.is_installed().
- """
- self.assertIs(apps.is_installed('django.contrib.admin'), True)
- self.assertIs(apps.is_installed('django.contrib.auth'), True)
- self.assertIs(apps.is_installed('django.contrib.staticfiles'), True)
- self.assertIs(apps.is_installed('django.contrib.admindocs'), False)
- @override_settings(INSTALLED_APPS=SOME_INSTALLED_APPS)
- def test_get_model(self):
- """
- Tests apps.get_model().
- """
- self.assertEqual(apps.get_model('admin', 'LogEntry'), LogEntry)
- with self.assertRaises(LookupError):
- apps.get_model('admin', 'LogExit')
-
- self.assertEqual(apps.get_model('admin', 'loGentrY'), LogEntry)
- with self.assertRaises(LookupError):
- apps.get_model('Admin', 'LogEntry')
-
- self.assertEqual(apps.get_model('admin.LogEntry'), LogEntry)
- with self.assertRaises(LookupError):
- apps.get_model('admin.LogExit')
- with self.assertRaises(ValueError):
- apps.get_model('admin_LogEntry')
- @override_settings(INSTALLED_APPS=['apps.apps.RelabeledAppsConfig'])
- def test_relabeling(self):
- self.assertEqual(apps.get_app_config('relabeled').name, 'apps')
- def test_duplicate_labels(self):
- with self.assertRaisesMessage(ImproperlyConfigured, "Application labels aren't unique"):
- with self.settings(INSTALLED_APPS=['apps.apps.PlainAppsConfig', 'apps']):
- pass
- def test_duplicate_names(self):
- with self.assertRaisesMessage(ImproperlyConfigured, "Application names aren't unique"):
- with self.settings(INSTALLED_APPS=['apps.apps.RelabeledAppsConfig', 'apps']):
- pass
- def test_import_exception_is_not_masked(self):
- """
- App discovery should preserve stack traces. Regression test for #22920.
- """
- with self.assertRaisesMessage(ImportError, "Oops"):
- with self.settings(INSTALLED_APPS=['import_error_package']):
- pass
- def test_models_py(self):
- """
- The models in the models.py file were loaded correctly.
- """
- self.assertEqual(apps.get_model("apps", "TotallyNormal"), TotallyNormal)
- with self.assertRaises(LookupError):
- apps.get_model("apps", "SoAlternative")
- with self.assertRaises(LookupError):
- new_apps.get_model("apps", "TotallyNormal")
- self.assertEqual(new_apps.get_model("apps", "SoAlternative"), SoAlternative)
- def test_models_not_loaded(self):
- """
- apps.get_models() raises an exception if apps.models_ready isn't True.
- """
- apps.models_ready = False
- try:
-
- apps.get_models.cache_clear()
- with self.assertRaisesMessage(AppRegistryNotReady, "Models aren't loaded yet."):
- apps.get_models()
- finally:
- apps.models_ready = True
- def test_dynamic_load(self):
- """
- Makes a new model at runtime and ensures it goes into the right place.
- """
- old_models = list(apps.get_app_config("apps").get_models())
-
- body = {}
- new_apps = Apps(["apps"])
- meta_contents = {
- 'app_label': "apps",
- 'apps': new_apps,
- }
- meta = type("Meta", (), meta_contents)
- body['Meta'] = meta
- body['__module__'] = TotallyNormal.__module__
- temp_model = type("SouthPonies", (models.Model,), body)
-
- self.assertEqual(list(apps.get_app_config("apps").get_models()), old_models)
- with self.assertRaises(LookupError):
- apps.get_model("apps", "SouthPonies")
- self.assertEqual(new_apps.get_model("apps", "SouthPonies"), temp_model)
- def test_model_clash(self):
- """
- Test for behavior when two models clash in the app registry.
- """
- new_apps = Apps(["apps"])
- meta_contents = {
- 'app_label': "apps",
- 'apps': new_apps,
- }
- body = {}
- body['Meta'] = type("Meta", (), meta_contents)
- body['__module__'] = TotallyNormal.__module__
- type("SouthPonies", (models.Model,), body)
-
-
-
- body = {}
- body['Meta'] = type("Meta", (), meta_contents)
- body['__module__'] = TotallyNormal.__module__
- msg = (
- "Model 'apps.southponies' was already registered. "
- "Reloading models is not advised as it can lead to inconsistencies, "
- "most notably with related models."
- )
- with self.assertRaisesMessage(RuntimeWarning, msg):
- type("SouthPonies", (models.Model,), body)
-
-
- body = {}
- body['Meta'] = type("Meta", (), meta_contents)
- body['__module__'] = TotallyNormal.__module__ + '.whatever'
- with self.assertRaisesMessage(RuntimeError, "Conflicting 'southponies' models in application 'apps':"):
- type("SouthPonies", (models.Model,), body)
- def test_get_containing_app_config_apps_not_ready(self):
- """
- apps.get_containing_app_config() should raise an exception if
- apps.apps_ready isn't True.
- """
- apps.apps_ready = False
- try:
- with self.assertRaisesMessage(AppRegistryNotReady, "Apps aren't loaded yet"):
- apps.get_containing_app_config('foo')
- finally:
- apps.apps_ready = True
- @isolate_apps('apps', kwarg_name='apps')
- def test_lazy_model_operation(self, apps):
- """
- Tests apps.lazy_model_operation().
- """
- model_classes = []
- initial_pending = set(apps._pending_operations)
- def test_func(*models):
- model_classes[:] = models
- class LazyA(models.Model):
- pass
-
- model_keys = [('apps', model_name) for model_name in ['lazya', 'lazyb', 'lazyb', 'lazyc', 'lazya']]
- apps.lazy_model_operation(test_func, *model_keys)
-
-
- self.assertEqual(set(apps._pending_operations) - initial_pending, {('apps', 'lazyb')})
-
- apps.lazy_model_operation(test_func, ('apps', 'lazyb'))
- class LazyB(models.Model):
- pass
- self.assertEqual(model_classes, [LazyB])
-
- self.assertEqual(set(apps._pending_operations) - initial_pending, {('apps', 'lazyc')})
- class LazyC(models.Model):
- pass
-
- self.assertEqual(model_classes, [LazyA, LazyB, LazyB, LazyC, LazyA])
- class Stub:
- def __init__(self, **kwargs):
- self.__dict__.update(kwargs)
- class AppConfigTests(SimpleTestCase):
- """Unit tests for AppConfig class."""
- def test_path_set_explicitly(self):
- """If subclass sets path as class attr, no module attributes needed."""
- class MyAppConfig(AppConfig):
- path = 'foo'
- ac = MyAppConfig('label', Stub())
- self.assertEqual(ac.path, 'foo')
- def test_explicit_path_overrides(self):
- """If path set as class attr, overrides __path__ and __file__."""
- class MyAppConfig(AppConfig):
- path = 'foo'
- ac = MyAppConfig('label', Stub(__path__=['a'], __file__='b/__init__.py'))
- self.assertEqual(ac.path, 'foo')
- def test_dunder_path(self):
- """If single element in __path__, use it (in preference to __file__)."""
- ac = AppConfig('label', Stub(__path__=['a'], __file__='b/__init__.py'))
- self.assertEqual(ac.path, 'a')
- def test_no_dunder_path_fallback_to_dunder_file(self):
- """If there is no __path__ attr, use __file__."""
- ac = AppConfig('label', Stub(__file__='b/__init__.py'))
- self.assertEqual(ac.path, 'b')
- def test_empty_dunder_path_fallback_to_dunder_file(self):
- """If the __path__ attr is empty, use __file__ if set."""
- ac = AppConfig('label', Stub(__path__=[], __file__='b/__init__.py'))
- self.assertEqual(ac.path, 'b')
- def test_multiple_dunder_path_fallback_to_dunder_file(self):
- """If the __path__ attr is length>1, use __file__ if set."""
- ac = AppConfig('label', Stub(__path__=['a', 'b'], __file__='c/__init__.py'))
- self.assertEqual(ac.path, 'c')
- def test_no_dunder_path_or_dunder_file(self):
- """If there is no __path__ or __file__, raise ImproperlyConfigured."""
- with self.assertRaises(ImproperlyConfigured):
- AppConfig('label', Stub())
- def test_empty_dunder_path_no_dunder_file(self):
- """If the __path__ attr is empty and there is no __file__, raise."""
- with self.assertRaises(ImproperlyConfigured):
- AppConfig('label', Stub(__path__=[]))
- def test_multiple_dunder_path_no_dunder_file(self):
- """If the __path__ attr is length>1 and there is no __file__, raise."""
- with self.assertRaises(ImproperlyConfigured):
- AppConfig('label', Stub(__path__=['a', 'b']))
- def test_duplicate_dunder_path_no_dunder_file(self):
- """
- If the __path__ attr contains duplicate paths and there is no
- __file__, they duplicates should be deduplicated (#25246).
- """
- ac = AppConfig('label', Stub(__path__=['a', 'a']))
- self.assertEqual(ac.path, 'a')
- def test_repr(self):
- ac = AppConfig('label', Stub(__path__=['a']))
- self.assertEqual(repr(ac), '<AppConfig: label>')
- class NamespacePackageAppTests(SimpleTestCase):
-
-
-
-
- base_location = os.path.join(HERE, 'namespace_package_base')
- other_location = os.path.join(HERE, 'namespace_package_other_base')
- app_path = os.path.join(base_location, 'nsapp')
- def test_single_path(self):
- """
- A Py3.3+ namespace package can be an app if it has only one path.
- """
- with extend_sys_path(self.base_location):
- with self.settings(INSTALLED_APPS=['nsapp']):
- app_config = apps.get_app_config('nsapp')
- self.assertEqual(app_config.path, self.app_path)
- def test_multiple_paths(self):
- """
- A Py3.3+ namespace package with multiple locations cannot be an app.
- (Because then we wouldn't know where to load its templates, static
- assets, etc. from.)
- """
-
-
- with extend_sys_path(self.base_location, self.other_location):
- with self.assertRaises(ImproperlyConfigured):
- with self.settings(INSTALLED_APPS=['nsapp']):
- pass
- def test_multiple_paths_explicit_path(self):
- """
- Multiple locations are ok only if app-config has explicit path.
- """
-
-
- with extend_sys_path(self.base_location, self.other_location):
- with self.settings(INSTALLED_APPS=['nsapp.apps.NSAppConfig']):
- app_config = apps.get_app_config('nsapp')
- self.assertEqual(app_config.path, self.app_path)
- class DeprecationTests(SimpleTestCase):
- @ignore_warnings(category=RemovedInDjango41Warning)
- def test_explicit_default_app_config(self):
- with self.settings(INSTALLED_APPS=['apps.explicit_default_config_app']):
- config = apps.get_app_config('explicit_default_config_app')
- self.assertIsInstance(config, ExplicitDefaultConfig)
- def test_explicit_default_app_config_warning(self):
- """
- Load an app that specifies a default AppConfig class matching the
- autodetected one.
- """
- msg = (
- "'apps.explicit_default_config_app' defines default_app_config = "
- "'apps.explicit_default_config_app.apps.ExplicitDefaultConfig'. "
- "Django now detects this configuration automatically. You can "
- "remove default_app_config."
- )
- with self.assertRaisesMessage(RemovedInDjango41Warning, msg):
- with self.settings(INSTALLED_APPS=['apps.explicit_default_config_app']):
- config = apps.get_app_config('explicit_default_config_app')
- self.assertIsInstance(config, ExplicitDefaultConfig)
- def test_explicit_default_app_config_mismatch(self):
- """
- Load an app that specifies a default AppConfig class not matching the
- autodetected one.
- """
- msg = (
- "'apps.explicit_default_config_mismatch_app' defines "
- "default_app_config = 'apps.explicit_default_config_mismatch_app."
- "not_apps.ExplicitDefaultConfigMismatch'. However, Django's "
- "automatic detection picked another configuration, 'apps."
- "explicit_default_config_mismatch_app.apps."
- "ImplicitDefaultConfigMismatch'. You should move the default "
- "config class to the apps submodule of your application and, if "
- "this module defines several config classes, mark the default one "
- "with default = True."
- )
- with self.assertRaisesMessage(RemovedInDjango41Warning, msg):
- with self.settings(INSTALLED_APPS=['apps.explicit_default_config_mismatch_app']):
- config = apps.get_app_config('explicit_default_config_mismatch_app')
- self.assertIsInstance(config, ExplicitDefaultConfigMismatch)
|