Checked more precisely whether the app registry is ready.

Accounted for the three stages of population: app configs, models,
ready() methods of app configs.
This commit is contained in:
Aymeric Augustin 2014-07-12 15:33:21 +02:00
parent b48c2c5925
commit a764a9ccff
3 changed files with 48 additions and 28 deletions

View File

@ -1,7 +1,7 @@
from importlib import import_module
import os
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
from django.utils.module_loading import module_has_submodule
from django.utils._os import upath
@ -139,15 +139,21 @@ class AppConfig(object):
# Entry is a path to an app config class.
return cls(app_name, app_module)
def check_models_ready(self):
"""
Raises an exception if models haven't been imported yet.
"""
if self.models is None:
raise AppRegistryNotReady(
"Models for app '%s' haven't been imported yet." % self.label)
def get_model(self, model_name):
"""
Returns the model with the given case-insensitive model_name.
Raises LookupError if no model exists with this name.
"""
if self.models is None:
raise LookupError(
"App '%s' doesn't have any models." % self.label)
self.check_models_ready()
try:
return self.models[model_name.lower()]
except KeyError:
@ -169,6 +175,7 @@ class AppConfig(object):
Set the corresponding keyword argument to True to include such models.
Keyword arguments aren't documented; they're a private API.
"""
self.check_models_ready()
for model in self.models.values():
if model._deferred and not include_deferred:
continue

View File

@ -43,7 +43,7 @@ class Apps(object):
self.stored_app_configs = []
# Whether the registry is populated.
self.ready = False
self.apps_ready = self.models_ready = self.ready = False
# Lock for thread-safe population.
self._lock = threading.Lock()
@ -100,29 +100,41 @@ class Apps(object):
"Application names aren't unique, "
"duplicates: %s" % ", ".join(duplicates))
self.apps_ready = True
# Load models.
for app_config in self.app_configs.values():
all_models = self.all_models[app_config.label]
app_config.import_models(all_models)
self.clear_cache()
self.ready = True
self.models_ready = True
for app_config in self.get_app_configs():
app_config.ready()
def check_ready(self):
self.ready = True
def check_apps_ready(self):
"""
Raises an exception if the registry isn't ready.
Raises an exception if all apps haven't been imported yet.
"""
if not self.ready:
raise AppRegistryNotReady()
if not self.apps_ready:
raise AppRegistryNotReady("Apps aren't loaded yet.")
def check_models_ready(self):
"""
Raises an exception if all models haven't been imported yet.
"""
if not self.models_ready:
raise AppRegistryNotReady("Models aren't loaded yet.")
def get_app_configs(self):
"""
Imports applications and returns an iterable of app configs.
"""
self.check_ready()
self.check_apps_ready()
return self.app_configs.values()
def get_app_config(self, app_label):
@ -131,7 +143,7 @@ class Apps(object):
Raises LookupError if no application exists with this label.
"""
self.check_ready()
self.check_apps_ready()
try:
return self.app_configs[app_label]
except KeyError:
@ -153,7 +165,7 @@ class Apps(object):
Set the corresponding keyword argument to True to include such models.
"""
self.check_ready()
self.check_models_ready()
if app_mod:
warnings.warn(
"The app_mod argument of get_models is deprecated.",
@ -184,7 +196,7 @@ class Apps(object):
model exists with this name in the application. Raises ValueError if
called with a single argument that doesn't contain exactly one dot.
"""
self.check_ready()
self.check_models_ready()
if model_name is None:
app_label, model_name = app_label.split('.')
return self.get_app_config(app_label).get_model(model_name.lower())
@ -207,10 +219,8 @@ class Apps(object):
Checks whether an application with this name exists in the registry.
app_name is the full name of the app eg. 'django.contrib.admin'.
It's safe to call this method at import time, even while the registry
is being populated. It returns False for apps that aren't loaded yet.
"""
self.check_apps_ready()
return any(ac.name == app_name for ac in self.app_configs.values())
def get_containing_app_config(self, object_name):
@ -221,10 +231,10 @@ class Apps(object):
Returns the app config for the inner application in case of nesting.
Returns None if the object isn't in any registered app config.
It's safe to call this method at import time, even while the registry
is being populated.
"""
# In Django 1.7 and 1.8, it's allowed to call this method at import
# time, even while the registry is being populated. In Django 1.9 and
# later, that should be forbidden with `self.check_apps_ready()`.
candidates = []
for app_config in self.app_configs.values():
if object_name.startswith(app_config.name):
@ -297,10 +307,11 @@ class Apps(object):
imports safely (eg. that could lead to registering listeners twice),
models are registered when they're imported and never removed.
"""
self.check_ready()
if not self.ready:
raise AppRegistryNotReady("App registry isn't ready yet.")
self.stored_app_configs.append(self.app_configs)
self.app_configs = OrderedDict()
self.ready = False
self.apps_ready = self.models_ready = self.ready = False
self.clear_cache()
self.populate(installed)
@ -309,7 +320,7 @@ class Apps(object):
Cancels a previous call to set_installed_apps().
"""
self.app_configs = self.stored_app_configs.pop()
self.ready = True
self.apps_ready = self.models_ready = self.ready = True
self.clear_cache()
def clear_cache(self):
@ -402,7 +413,7 @@ class Apps(object):
warnings.warn(
"[a.path for a in get_app_configs()] supersedes get_app_paths().",
RemovedInDjango19Warning, stacklevel=2)
self.check_ready()
self.check_apps_ready()
app_paths = []
for app in self.get_apps():
app_paths.append(self._get_app_path(app))

View File

@ -301,10 +301,6 @@ Application registry
Checks whether an application with the given name exists in the registry.
``app_name`` is the full name of the app, e.g. ``'django.contrib.admin'``.
Unlike :meth:`~django.apps.apps.get_app_config`, this method can be called
safely at import time. If the registry is still being populated, it may
return ``False``, even though the app will become available later.
.. method:: apps.get_model(app_label, model_name)
Returns the :class:`~django.db.models.Model` with the given ``app_label``
@ -365,6 +361,9 @@ processes all applications in the order of :setting:`INSTALLED_APPS`.
the order of :setting:`INSTALLED_APPS`, it's strongly recommended not
import any models at this stage.
Once this stage completes, APIs that operate of application configurations
such as :meth:`~apps.get_app_config()` become usable.
#. Then Django attempts to import the ``models`` submodule of each application,
if there is one.
@ -372,6 +371,9 @@ processes all applications in the order of :setting:`INSTALLED_APPS`.
``models/__init__.py``. Otherwise, the application registry may not be fully
populated at this point, which could cause the ORM to malfunction.
Once this stage completes, APIs that operate on models such as
:meth:`~apps.get_model()` become usable.
#. Finally Django runs the :meth:`~AppConfig.ready()` method of each application
configuration.