From efcb7e1ebf2b852a442d00a31634438359eb4039 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 30 Sep 2016 21:08:35 +0200 Subject: [PATCH] Modified readiness check in AppConfig.get_model(s). It was inconsistent with the equivalent check in Apps.get_model(s) because I made incorrect assumptions when I wrote that code and needlessly complicated readiness checks. This is a backwards-incompatible change. --- django/apps/config.py | 18 +++++++----------- django/apps/registry.py | 1 + django/db/migrations/state.py | 8 ++++++++ docs/ref/applications.txt | 9 +++++++-- docs/releases/1.11.txt | 7 +++++++ 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/django/apps/config.py b/django/apps/config.py index 88e52c1f05c..178fe971305 100644 --- a/django/apps/config.py +++ b/django/apps/config.py @@ -1,7 +1,7 @@ import os from importlib import import_module -from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured from django.utils._os import upath from django.utils.module_loading import module_has_submodule @@ -21,6 +21,10 @@ class AppConfig(object): # from 'django/contrib/admin/__init__.pyc'>. self.module = app_module + # Reference to the Apps registry that holds this AppConfig. Set by the + # registry when it registers the AppConfig instance. + self.apps = None + # The following attributes could be defined at the class level in a # subclass, hence the test-and-set pattern. @@ -151,21 +155,13 @@ 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. """ - self.check_models_ready() + self.apps.check_models_ready() try: return self.models[model_name.lower()] except KeyError: @@ -186,7 +182,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() + self.apps.check_models_ready() for model in self.models.values(): if model._meta.auto_created and not include_auto_created: continue diff --git a/django/apps/registry.py b/django/apps/registry.py index 8a45830ccf4..91d6d5ccef8 100644 --- a/django/apps/registry.py +++ b/django/apps/registry.py @@ -89,6 +89,7 @@ class Apps(object): "duplicates: %s" % app_config.label) self.app_configs[app_config.label] = app_config + app_config.apps = self # Check for duplicate app names. counts = Counter( diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 9cbf5659d4b..60d192443b7 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -239,6 +239,10 @@ class StateApps(Apps): app_configs = [AppConfigStub(label) for label in sorted(real_apps + list(app_labels))] super(StateApps, self).__init__(app_configs) + # The lock gets in the way of copying as implemented in clone(), which + # is called whenever Django duplicates a StateApps before updating it. + self._lock = None + self.render_multiple(list(models.values()) + self.real_models) # There shouldn't be any operations pending at this point. @@ -293,6 +297,9 @@ class StateApps(Apps): clone = StateApps([], {}) clone.all_models = copy.deepcopy(self.all_models) clone.app_configs = copy.deepcopy(self.app_configs) + # Set the pointer to the correct app registry. + for app_config in clone.app_configs.values(): + app_config.apps = clone # No need to actually clone them, they'll never change clone.real_models = self.real_models return clone @@ -301,6 +308,7 @@ class StateApps(Apps): self.all_models[app_label][model._meta.model_name] = model if app_label not in self.app_configs: self.app_configs[app_label] = AppConfigStub(app_label) + self.app_configs[app_label].apps = self self.app_configs[app_label].models = OrderedDict() self.app_configs[app_label].models[model._meta.model_name] = model self.do_pending_operations(model) diff --git a/docs/ref/applications.txt b/docs/ref/applications.txt index c289afcef97..110e5e867db 100644 --- a/docs/ref/applications.txt +++ b/docs/ref/applications.txt @@ -227,11 +227,16 @@ Methods Returns an iterable of :class:`~django.db.models.Model` classes for this application. + Requires the app registry to be fully populated. + .. method:: AppConfig.get_model(model_name) Returns the :class:`~django.db.models.Model` with the given - ``model_name``. Raises :exc:`LookupError` if no such model exists in this - application. ``model_name`` is case-insensitive. + ``model_name``. ``model_name`` is case-insensitive. + + Raises :exc:`LookupError` if no such model exists in this application. + + Requires the app registry to be fully populated. .. method:: AppConfig.ready() diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 27f7481fa63..8fd0795d67c 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -579,6 +579,13 @@ Miscellaneous * ``ConditionalGetMiddleware`` no longer sets the ``Date`` header as Web servers set that header. +* :meth:`~django.apps.AppConfig.get_model` and + :meth:`~django.apps.AppConfig.get_models` now raise + :exc:`~django.core.exceptions.AppRegistryNotReady` if they're called before + models of all applications have been loaded. Previously they only required + the target application's models to be loaded and thus could return models + without all their relations set up. + .. _deprecated-features-1.11: Features deprecated in 1.11