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

View File

@ -43,7 +43,7 @@ class Apps(object):
self.stored_app_configs = [] self.stored_app_configs = []
# Whether the registry is populated. # Whether the registry is populated.
self.ready = False self.apps_ready = self.models_ready = self.ready = False
# Lock for thread-safe population. # Lock for thread-safe population.
self._lock = threading.Lock() self._lock = threading.Lock()
@ -100,29 +100,41 @@ class Apps(object):
"Application names aren't unique, " "Application names aren't unique, "
"duplicates: %s" % ", ".join(duplicates)) "duplicates: %s" % ", ".join(duplicates))
self.apps_ready = True
# Load models. # Load models.
for app_config in self.app_configs.values(): for app_config in self.app_configs.values():
all_models = self.all_models[app_config.label] all_models = self.all_models[app_config.label]
app_config.import_models(all_models) app_config.import_models(all_models)
self.clear_cache() self.clear_cache()
self.ready = True
self.models_ready = True
for app_config in self.get_app_configs(): for app_config in self.get_app_configs():
app_config.ready() 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: if not self.apps_ready:
raise AppRegistryNotReady() 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): def get_app_configs(self):
""" """
Imports applications and returns an iterable of app configs. Imports applications and returns an iterable of app configs.
""" """
self.check_ready() self.check_apps_ready()
return self.app_configs.values() return self.app_configs.values()
def get_app_config(self, app_label): def get_app_config(self, app_label):
@ -131,7 +143,7 @@ class Apps(object):
Raises LookupError if no application exists with this label. Raises LookupError if no application exists with this label.
""" """
self.check_ready() self.check_apps_ready()
try: try:
return self.app_configs[app_label] return self.app_configs[app_label]
except KeyError: except KeyError:
@ -153,7 +165,7 @@ class Apps(object):
Set the corresponding keyword argument to True to include such models. Set the corresponding keyword argument to True to include such models.
""" """
self.check_ready() self.check_models_ready()
if app_mod: if app_mod:
warnings.warn( warnings.warn(
"The app_mod argument of get_models is deprecated.", "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 model exists with this name in the application. Raises ValueError if
called with a single argument that doesn't contain exactly one dot. called with a single argument that doesn't contain exactly one dot.
""" """
self.check_ready() self.check_models_ready()
if model_name is None: if model_name is None:
app_label, model_name = app_label.split('.') app_label, model_name = app_label.split('.')
return self.get_app_config(app_label).get_model(model_name.lower()) 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. Checks whether an application with this name exists in the registry.
app_name is the full name of the app eg. 'django.contrib.admin'. 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()) return any(ac.name == app_name for ac in self.app_configs.values())
def get_containing_app_config(self, object_name): 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 the app config for the inner application in case of nesting.
Returns None if the object isn't in any registered app config. 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 = [] candidates = []
for app_config in self.app_configs.values(): for app_config in self.app_configs.values():
if object_name.startswith(app_config.name): if object_name.startswith(app_config.name):
@ -297,10 +307,11 @@ class Apps(object):
imports safely (eg. that could lead to registering listeners twice), imports safely (eg. that could lead to registering listeners twice),
models are registered when they're imported and never removed. 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.stored_app_configs.append(self.app_configs)
self.app_configs = OrderedDict() self.app_configs = OrderedDict()
self.ready = False self.apps_ready = self.models_ready = self.ready = False
self.clear_cache() self.clear_cache()
self.populate(installed) self.populate(installed)
@ -309,7 +320,7 @@ class Apps(object):
Cancels a previous call to set_installed_apps(). Cancels a previous call to set_installed_apps().
""" """
self.app_configs = self.stored_app_configs.pop() self.app_configs = self.stored_app_configs.pop()
self.ready = True self.apps_ready = self.models_ready = self.ready = True
self.clear_cache() self.clear_cache()
def clear_cache(self): def clear_cache(self):
@ -402,7 +413,7 @@ class Apps(object):
warnings.warn( warnings.warn(
"[a.path for a in get_app_configs()] supersedes get_app_paths().", "[a.path for a in get_app_configs()] supersedes get_app_paths().",
RemovedInDjango19Warning, stacklevel=2) RemovedInDjango19Warning, stacklevel=2)
self.check_ready() self.check_apps_ready()
app_paths = [] app_paths = []
for app in self.get_apps(): for app in self.get_apps():
app_paths.append(self._get_app_path(app)) 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. 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'``. ``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) .. method:: apps.get_model(app_label, model_name)
Returns the :class:`~django.db.models.Model` with the given ``app_label`` 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 the order of :setting:`INSTALLED_APPS`, it's strongly recommended not
import any models at this stage. 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, #. Then Django attempts to import the ``models`` submodule of each application,
if there is one. 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 ``models/__init__.py``. Otherwise, the application registry may not be fully
populated at this point, which could cause the ORM to malfunction. 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 #. Finally Django runs the :meth:`~AppConfig.ready()` method of each application
configuration. configuration.