diff options
| author | Alex Hill <alex@hill.net.au> | 2015-03-03 16:43:56 +0800 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2015-03-25 16:48:17 -0400 |
| commit | 720ff740e70e649a97fcf0232fec132569a52c7e (patch) | |
| tree | 688533ec18f1d970b8c7671ef94be83b67720b80 /django/apps | |
| parent | 0f6f80c2e7736ec4e2aa40287fe8c37ffff0a783 (diff) | |
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.
Diffstat (limited to 'django/apps')
| -rw-r--r-- | django/apps/registry.py | 45 |
1 files changed, 43 insertions, 2 deletions
diff --git a/django/apps/registry.py b/django/apps/registry.py index 164427d85f..96d4db5f17 100644 --- a/django/apps/registry.py +++ b/django/apps/registry.py @@ -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) |
