1
0
mirror of https://github.com/django/django.git synced 2025-10-28 08:06:09 +00:00

Fixed #24215 -- Refactored lazy model operations

This adds a new method, Apps.lazy_model_operation(), and a helper function,
lazy_related_operation(), which together supersede add_lazy_relation() and
make lazy model operations the responsibility of the App registry. This
system no longer uses the class_prepared signal.
This commit is contained in:
Alex Hill
2015-03-03 16:43:56 +08:00
committed by Tim Graham
parent 0f6f80c2e7
commit 720ff740e7
10 changed files with 231 additions and 165 deletions

View File

@@ -2,6 +2,7 @@ import sys
import threading
import warnings
from collections import Counter, OrderedDict, defaultdict
from functools import partial
from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
from django.utils import lru_cache
@@ -45,8 +46,10 @@ class Apps(object):
# Lock for thread-safe population.
self._lock = threading.Lock()
# Pending lookups for lazy relations.
self._pending_lookups = {}
# Maps ("app_label", "modelname") tuples to lists of functions to be
# called when the corresponding model is ready. Used by this class's
# `lazy_model_operation()` and `do_pending_operations()` methods.
self._pending_operations = defaultdict(list)
# Populate apps and models, unless it's the master registry.
if installed_apps is not None:
@@ -207,6 +210,7 @@ class Apps(object):
"Conflicting '%s' models in application '%s': %s and %s." %
(model_name, app_label, app_models[model_name], model))
app_models[model_name] = model
self.do_pending_operations(model)
self.clear_cache()
def is_installed(self, app_name):
@@ -332,5 +336,42 @@ class Apps(object):
for model in app_config.get_models(include_auto_created=True):
model._meta._expire_cache()
def lazy_model_operation(self, function, *model_keys):
"""
Take a function and a number of ("app_label", "modelname") tuples, and
when all the corresponding models have been imported and registered,
call the function with the model classes as its arguments.
The function passed to this method must accept exactly n models as
arguments, where n=len(model_keys).
"""
# If this function depends on more than one model, we recursively turn
# it into a chain of functions that accept a single model argument and
# pass each in turn to lazy_model_operation.
model_key, more_models = model_keys[0], model_keys[1:]
if more_models:
supplied_fn = function
def function(model):
next_function = partial(supplied_fn, model)
self.lazy_model_operation(next_function, *more_models)
# If the model is already loaded, pass it to the function immediately.
# Otherwise, delay execution until the class is prepared.
try:
model_class = self.get_registered_model(*model_key)
except LookupError:
self._pending_operations[model_key].append(function)
else:
function(model_class)
def do_pending_operations(self, model):
"""
Take a newly-prepared model and pass it to each function waiting for
it. This is called at the very end of `Apps.register_model()`.
"""
key = model._meta.app_label, model._meta.model_name
for function in self._pending_operations.pop(key, []):
function(model)
apps = Apps(installed_apps=None)