Skip to content

Commit a764a9c

Browse files
committed
Checked more precisely whether the app registry is ready.
Accounted for the three stages of population: app configs, models, ready() methods of app configs.
1 parent b48c2c5 commit a764a9c

File tree

3 files changed

+48
-28
lines changed

3 files changed

+48
-28
lines changed

django/apps/config.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from importlib import import_module
22
import os
33

4-
from django.core.exceptions import ImproperlyConfigured
4+
from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
55
from django.utils.module_loading import module_has_submodule
66
from django.utils._os import upath
77

@@ -139,15 +139,21 @@ def create(cls, entry):
139139
# Entry is a path to an app config class.
140140
return cls(app_name, app_module)
141141

142+
def check_models_ready(self):
143+
"""
144+
Raises an exception if models haven't been imported yet.
145+
"""
146+
if self.models is None:
147+
raise AppRegistryNotReady(
148+
"Models for app '%s' haven't been imported yet." % self.label)
149+
142150
def get_model(self, model_name):
143151
"""
144152
Returns the model with the given case-insensitive model_name.
145153
146154
Raises LookupError if no model exists with this name.
147155
"""
148-
if self.models is None:
149-
raise LookupError(
150-
"App '%s' doesn't have any models." % self.label)
156+
self.check_models_ready()
151157
try:
152158
return self.models[model_name.lower()]
153159
except KeyError:
@@ -169,6 +175,7 @@ def get_models(self, include_auto_created=False,
169175
Set the corresponding keyword argument to True to include such models.
170176
Keyword arguments aren't documented; they're a private API.
171177
"""
178+
self.check_models_ready()
172179
for model in self.models.values():
173180
if model._deferred and not include_deferred:
174181
continue

django/apps/registry.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def __init__(self, installed_apps=()):
4343
self.stored_app_configs = []
4444

4545
# Whether the registry is populated.
46-
self.ready = False
46+
self.apps_ready = self.models_ready = self.ready = False
4747

4848
# Lock for thread-safe population.
4949
self._lock = threading.Lock()
@@ -100,29 +100,41 @@ def populate(self, installed_apps=None):
100100
"Application names aren't unique, "
101101
"duplicates: %s" % ", ".join(duplicates))
102102

103+
self.apps_ready = True
104+
103105
# Load models.
104106
for app_config in self.app_configs.values():
105107
all_models = self.all_models[app_config.label]
106108
app_config.import_models(all_models)
107109

108110
self.clear_cache()
109-
self.ready = True
111+
112+
self.models_ready = True
110113

111114
for app_config in self.get_app_configs():
112115
app_config.ready()
113116

114-
def check_ready(self):
117+
self.ready = True
118+
119+
def check_apps_ready(self):
115120
"""
116-
Raises an exception if the registry isn't ready.
121+
Raises an exception if all apps haven't been imported yet.
117122
"""
118-
if not self.ready:
119-
raise AppRegistryNotReady()
123+
if not self.apps_ready:
124+
raise AppRegistryNotReady("Apps aren't loaded yet.")
125+
126+
def check_models_ready(self):
127+
"""
128+
Raises an exception if all models haven't been imported yet.
129+
"""
130+
if not self.models_ready:
131+
raise AppRegistryNotReady("Models aren't loaded yet.")
120132

121133
def get_app_configs(self):
122134
"""
123135
Imports applications and returns an iterable of app configs.
124136
"""
125-
self.check_ready()
137+
self.check_apps_ready()
126138
return self.app_configs.values()
127139

128140
def get_app_config(self, app_label):
@@ -131,7 +143,7 @@ def get_app_config(self, app_label):
131143
132144
Raises LookupError if no application exists with this label.
133145
"""
134-
self.check_ready()
146+
self.check_apps_ready()
135147
try:
136148
return self.app_configs[app_label]
137149
except KeyError:
@@ -153,7 +165,7 @@ def get_models(self, app_mod=None, include_auto_created=False,
153165
154166
Set the corresponding keyword argument to True to include such models.
155167
"""
156-
self.check_ready()
168+
self.check_models_ready()
157169
if app_mod:
158170
warnings.warn(
159171
"The app_mod argument of get_models is deprecated.",
@@ -184,7 +196,7 @@ def get_model(self, app_label, model_name=None):
184196
model exists with this name in the application. Raises ValueError if
185197
called with a single argument that doesn't contain exactly one dot.
186198
"""
187-
self.check_ready()
199+
self.check_models_ready()
188200
if model_name is None:
189201
app_label, model_name = app_label.split('.')
190202
return self.get_app_config(app_label).get_model(model_name.lower())
@@ -207,10 +219,8 @@ def is_installed(self, app_name):
207219
Checks whether an application with this name exists in the registry.
208220
209221
app_name is the full name of the app eg. 'django.contrib.admin'.
210-
211-
It's safe to call this method at import time, even while the registry
212-
is being populated. It returns False for apps that aren't loaded yet.
213222
"""
223+
self.check_apps_ready()
214224
return any(ac.name == app_name for ac in self.app_configs.values())
215225

216226
def get_containing_app_config(self, object_name):
@@ -221,10 +231,10 @@ def get_containing_app_config(self, object_name):
221231
222232
Returns the app config for the inner application in case of nesting.
223233
Returns None if the object isn't in any registered app config.
224-
225-
It's safe to call this method at import time, even while the registry
226-
is being populated.
227234
"""
235+
# In Django 1.7 and 1.8, it's allowed to call this method at import
236+
# time, even while the registry is being populated. In Django 1.9 and
237+
# later, that should be forbidden with `self.check_apps_ready()`.
228238
candidates = []
229239
for app_config in self.app_configs.values():
230240
if object_name.startswith(app_config.name):
@@ -297,10 +307,11 @@ def set_installed_apps(self, installed):
297307
imports safely (eg. that could lead to registering listeners twice),
298308
models are registered when they're imported and never removed.
299309
"""
300-
self.check_ready()
310+
if not self.ready:
311+
raise AppRegistryNotReady("App registry isn't ready yet.")
301312
self.stored_app_configs.append(self.app_configs)
302313
self.app_configs = OrderedDict()
303-
self.ready = False
314+
self.apps_ready = self.models_ready = self.ready = False
304315
self.clear_cache()
305316
self.populate(installed)
306317

@@ -309,7 +320,7 @@ def unset_installed_apps(self):
309320
Cancels a previous call to set_installed_apps().
310321
"""
311322
self.app_configs = self.stored_app_configs.pop()
312-
self.ready = True
323+
self.apps_ready = self.models_ready = self.ready = True
313324
self.clear_cache()
314325

315326
def clear_cache(self):
@@ -402,7 +413,7 @@ def get_app_paths(self):
402413
warnings.warn(
403414
"[a.path for a in get_app_configs()] supersedes get_app_paths().",
404415
RemovedInDjango19Warning, stacklevel=2)
405-
self.check_ready()
416+
self.check_apps_ready()
406417
app_paths = []
407418
for app in self.get_apps():
408419
app_paths.append(self._get_app_path(app))

docs/ref/applications.txt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,6 @@ Application registry
301301
Checks whether an application with the given name exists in the registry.
302302
``app_name`` is the full name of the app, e.g. ``'django.contrib.admin'``.
303303

304-
Unlike :meth:`~django.apps.apps.get_app_config`, this method can be called
305-
safely at import time. If the registry is still being populated, it may
306-
return ``False``, even though the app will become available later.
307-
308304
.. method:: apps.get_model(app_label, model_name)
309305

310306
Returns the :class:`~django.db.models.Model` with the given ``app_label``
@@ -365,13 +361,19 @@ processes all applications in the order of :setting:`INSTALLED_APPS`.
365361
the order of :setting:`INSTALLED_APPS`, it's strongly recommended not
366362
import any models at this stage.
367363

364+
Once this stage completes, APIs that operate of application configurations
365+
such as :meth:`~apps.get_app_config()` become usable.
366+
368367
#. Then Django attempts to import the ``models`` submodule of each application,
369368
if there is one.
370369

371370
You must define or import all models in your application's ``models.py`` or
372371
``models/__init__.py``. Otherwise, the application registry may not be fully
373372
populated at this point, which could cause the ORM to malfunction.
374373

374+
Once this stage completes, APIs that operate on models such as
375+
:meth:`~apps.get_model()` become usable.
376+
375377
#. Finally Django runs the :meth:`~AppConfig.ready()` method of each application
376378
configuration.
377379

0 commit comments

Comments
 (0)