Browse Source

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.
Aymeric Augustin 8 years ago
parent
commit
efcb7e1ebf

+ 7 - 11
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

+ 1 - 0
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(

+ 8 - 0
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)

+ 7 - 2
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()
 

+ 7 - 0
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