diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index a15967e024..ee945f73e5 100644 --- a/django/contrib/admin/validation.py +++ b/django/contrib/admin/validation.py @@ -18,7 +18,7 @@ class BaseValidator(object): def __init__(self): # Before we can introspect models, they need the app cache to be fully # loaded so that inter-relations are set up correctly. - app_cache.populate() + app_cache.populate_models() def validate(self, cls, model): for m in dir(self): diff --git a/django/core/apps/cache.py b/django/core/apps/cache.py index cf2489185e..e9a4215440 100644 --- a/django/core/apps/cache.py +++ b/django/core/apps/cache.py @@ -2,17 +2,16 @@ from collections import defaultdict, OrderedDict from contextlib import contextmanager -from importlib import import_module import os import sys import warnings from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.utils.module_loading import import_lock, module_has_submodule +from django.utils.module_loading import import_lock from django.utils._os import upath -from .base import AppConfig, MODELS_MODULE_NAME +from .base import AppConfig class UnavailableApp(Exception): @@ -51,15 +50,11 @@ class AppCache(object): # Used by TransactionTestCase.available_apps for performance reasons. self.available_apps = None - # Internal flags used when populating the cache. - self._apps_loaded = False - self._models_loaded = False + # Internal flags used when populating the master cache. + self._apps_loaded = not self.master + self._models_loaded = not self.master - # -- Everything below here is only used when populating the cache -- - self.loaded = False - self.handled = set() - self.postponed = [] - self.nesting_level = 0 + # Cache for get_models. self._get_models_cache = {} def populate_apps(self): @@ -138,71 +133,15 @@ class AppCache(object): self._models_loaded = True - def populate(self): - """ - Fill in all the cache information. This method is threadsafe, in the - sense that every caller will see the same state upon return, and if the - cache is already initialised, it does no work. - """ - if self.loaded: - return - if not self.master: - self.loaded = True - return - # Note that we want to use the import lock here - the app loading is - # in many cases initiated implicitly by importing, and thus it is - # possible to end up in deadlock when one thread initiates loading - # without holding the importer lock and another thread then tries to - # import something which also launches the app loading. For details of - # this situation see #18251. - with import_lock(): - if self.loaded: - return - for app_name in settings.INSTALLED_APPS: - if app_name in self.handled: - continue - self.load_app(app_name, can_postpone=True) - if not self.nesting_level: - for app_name in self.postponed: - self.load_app(app_name) - self.loaded = True - - def load_app(self, app_name, can_postpone=False): + def load_app(self, app_name): """ Loads the app with the provided fully qualified name, and returns the model module. """ - app_module = import_module(app_name) - self.handled.add(app_name) - self.nesting_level += 1 - try: - models_module = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME)) - except ImportError: - # If the app doesn't have a models module, we can just swallow the - # ImportError and return no models for this app. - if not module_has_submodule(app_module, MODELS_MODULE_NAME): - models_module = None - # But if the app does have a models module, we need to figure out - # whether to suppress or propagate the error. If can_postpone is - # True then it may be that the package is still being imported by - # Python and the models module isn't available yet. So we add the - # app to the postponed list and we'll try it again after all the - # recursion has finished (in populate). If can_postpone is False - # then it's time to raise the ImportError. - else: - if can_postpone: - self.postponed.append(app_name) - return - else: - raise - finally: - self.nesting_level -= 1 - app_config = AppConfig(app_name) app_config.import_models(self.all_models[app_config.label]) self.app_configs[app_config.label] = app_config - - return models_module + return app_config.models_module def app_cache_ready(self): """ @@ -211,7 +150,7 @@ class AppCache(object): Useful for code that wants to cache the results of get_models() for themselves once it is safe to do so. """ - return self.loaded + return self._models_loaded # implies self._apps_loaded. def get_app_configs(self, only_with_models_module=False): """ @@ -220,7 +159,7 @@ class AppCache(object): If only_with_models_module in True (non-default), only applications containing a models module are considered. """ - self.populate() + self.populate_models() for app_config in self.app_configs.values(): if only_with_models_module and app_config.models_module is None: continue @@ -240,7 +179,7 @@ class AppCache(object): If only_with_models_module in True (non-default), only applications containing a models module are considered. """ - self.populate() + self.populate_models() app_config = self.app_configs.get(app_label) if app_config is None: raise LookupError("No installed app with label %r." % app_label) @@ -288,7 +227,7 @@ class AppCache(object): return model_list except KeyError: pass - self.populate() + self.populate_models() if app_mod: app_label = app_mod.__name__.split('.')[-2] if only_installed: @@ -331,7 +270,7 @@ class AppCache(object): """ if not self.master: only_installed = False - self.populate() + self.populate_models() if only_installed: app_config = self.app_configs.get(app_label) if app_config is None: @@ -496,7 +435,7 @@ class AppCache(object): "[a.path for a in get_app_configs()] supersedes get_app_paths().", PendingDeprecationWarning, stacklevel=2) - self.populate() + self.populate_models() app_paths = [] for app in self.get_apps(): diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index db4b79a020..b0bc8138ab 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -139,7 +139,7 @@ class Deserializer(six.Iterator): self.stream = stream_or_string # Make sure the app cache is loaded before deserialization starts # (otherwise subclass calls to get_model() and friends might fail...) - app_cache.populate() + app_cache.populate_models() def __iter__(self): return self diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 13d6f01a4e..1e4c37e2ae 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -88,7 +88,7 @@ def Deserializer(object_list, **options): db = options.pop('using', DEFAULT_DB_ALIAS) ignore = options.pop('ignorenonexistent', False) - app_cache.populate() + app_cache.populate_models() for d in object_list: # Look up the model and starting build a dict of data for it. diff --git a/django/db/models/base.py b/django/db/models/base.py index a694350b97..d1e7323d17 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -6,7 +6,7 @@ from functools import update_wrapper from django.utils.six.moves import zip from django.core.apps import app_cache -from django.core.apps.cache import MODELS_MODULE_NAME +from django.core.apps.base import MODELS_MODULE_NAME import django.db.models.manager # NOQA: Imported to register signal handler. from django.conf import settings from django.core.exceptions import (ObjectDoesNotExist, diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 5745dbed4d..00fae36769 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -30,6 +30,6 @@ def get_app_errors(): try: return app_cache.app_errors except AttributeError: - app_cache.populate() + app_cache.populate_models() app_cache.app_errors = {} return app_cache.app_errors diff --git a/django/db/models/options.py b/django/db/models/options.py index f7be089539..fec9aa4fc4 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -93,7 +93,7 @@ class Options(object): @property def app_config(self): - # Don't go through get_app_config to avoid triggering populate(). + # Don't go through get_app_config to avoid triggering imports. return self.app_cache.app_configs.get(self.app_label) @property diff --git a/tests/app_loading/tests.py b/tests/app_loading/tests.py index e282306ec0..45f6cd6c31 100644 --- a/tests/app_loading/tests.py +++ b/tests/app_loading/tests.py @@ -77,8 +77,9 @@ class EggLoadingTest(TestCase): error. Refs #17667. """ app_cache = AppCache() - # Pretend we're the master app cache to test populate(). - app_cache.master = True + # Pretend we're the master app cache to test the population process. + app_cache._apps_loaded = False + app_cache._models_loaded = False with override_settings(INSTALLED_APPS=('notexists',)): with self.assertRaises(ImportError): app_cache.get_model('notexists', 'nomodel') diff --git a/tests/runtests.py b/tests/runtests.py index badbd6fbec..77cf005267 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -128,7 +128,7 @@ def setup(verbosity, test_labels): # Load all the ALWAYS_INSTALLED_APPS. with warnings.catch_warnings(): warnings.filterwarnings('ignore', 'django.contrib.comments is deprecated and will be removed before Django 1.8.', DeprecationWarning) - app_cache.populate() + app_cache.populate_models() # Load all the test model apps. test_modules = get_test_modules()