فهرست منبع

Stopped populating the app registry as a side effect.

Since it triggers imports, it shouldn't be done lightly.

This commit adds a public API for doing it explicitly, django.setup(),
and does it automatically when using manage.py and wsgi.py.
Aymeric Augustin 11 سال پیش
والد
کامیت
80d74097b4

+ 9 - 0
django/__init__.py

@@ -6,3 +6,12 @@ def get_version(*args, **kwargs):
     # Only import if it's actually called.
     from django.utils.version import get_version
     return get_version(*args, **kwargs)
+
+
+def setup():
+    # Configure the settings (this happens as a side effect of accessing
+    # INSTALLED_APPS or any other setting) and populate the app registry.
+    from django.apps import apps
+    from django.conf import settings
+    apps.populate_apps(settings.INSTALLED_APPS)
+    apps.populate_models()

+ 2 - 5
django/apps/base.py

@@ -114,8 +114,6 @@ class AppConfig(object):
         Returns the model with the given case-insensitive model_name.
 
         Raises LookupError if no model exists with this name.
-
-        This method assumes that apps.populate_models() has run.
         """
         if self.models is None:
             raise LookupError(
@@ -140,8 +138,6 @@ class AppConfig(object):
 
         Set the corresponding keyword argument to True to include such models.
         Keyword arguments aren't documented; they're a private API.
-
-        This method assumes that apps.populate_models() has run.
         """
         for model in self.models.values():
             if model._deferred and not include_deferred:
@@ -156,7 +152,8 @@ class AppConfig(object):
         # Dictionary of models for this app, primarily maintained in the
         # 'all_models' attribute of the Apps this AppConfig is attached to.
         # Injected as a parameter because it gets populated when models are
-        # imported, which may happen before populate_models() runs.
+        # imported, which might happen before populate_models() runs (or at
+        # least used to).
         self.models = all_models
 
         if module_has_submodule(self.module, MODELS_MODULE_NAME):

+ 22 - 22
django/apps/registry.py

@@ -3,7 +3,6 @@ import os
 import sys
 import warnings
 
-from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 from django.utils import lru_cache
 from django.utils.module_loading import import_lock
@@ -79,8 +78,6 @@ class Apps(object):
             # Application modules aren't expected to import anything, and
             # especially not other application modules, even indirectly.
             # Therefore we simply import them sequentially.
-            if installed_apps is None:
-                installed_apps = settings.INSTALLED_APPS
             for entry in installed_apps:
                 if isinstance(entry, AppConfig):
                     app_config = entry
@@ -108,7 +105,9 @@ class Apps(object):
             if self._models_loaded:
                 return
 
-            self.populate_apps()
+            if not self._apps_loaded:
+                raise RuntimeError(
+                    "populate_models() must run after populate_apps()")
 
             # Models modules are likely to import other models modules, for
             # example to reference related objects. As a consequence:
@@ -144,6 +143,15 @@ class Apps(object):
                 for app_config in self.get_app_configs():
                     app_config.setup()
 
+    def check_ready(self):
+        """
+        Raises an exception if the registry isn't ready.
+        """
+        if not self._models_loaded:
+            raise RuntimeError(
+                "App registry isn't populated yet. "
+                "Have you called django.setup()?")
+
     @property
     def ready(self):
         """
@@ -161,11 +169,7 @@ class Apps(object):
         If only_with_models_module in True (non-default), imports models and
         considers only applications containing a models module.
         """
-        if only_with_models_module:
-            self.populate_models()
-        else:
-            self.populate_apps()
-
+        self.check_ready()
         for app_config in self.app_configs.values():
             if only_with_models_module and app_config.models_module is None:
                 continue
@@ -180,11 +184,7 @@ class Apps(object):
         If only_with_models_module in True (non-default), imports models and
         considers only applications containing a models module.
         """
-        if only_with_models_module:
-            self.populate_models()
-        else:
-            self.populate_apps()
-
+        self.check_ready()
         app_config = self.app_configs.get(app_label)
         if app_config is None:
             raise LookupError("No installed app with label '%s'." % app_label)
@@ -208,8 +208,7 @@ class Apps(object):
 
         Set the corresponding keyword argument to True to include such models.
         """
-        self.populate_models()
-
+        self.check_ready()
         if app_mod:
             warnings.warn(
                 "The app_mod argument of get_models is deprecated.",
@@ -236,7 +235,7 @@ class Apps(object):
         Raises LookupError if no application exists with this label, or no
         model exists with this name in the application.
         """
-        self.populate_models()
+        self.check_ready()
         return self.get_app_config(app_label).get_model(model_name.lower())
 
     def register_model(self, app_label, model):
@@ -328,7 +327,8 @@ class Apps(object):
         imports safely (eg. that could lead to registering listeners twice),
         models are registered when they're imported and never removed.
         """
-        self.stored_app_configs.append((self.app_configs, self._apps_loaded, self._models_loaded))
+        self.check_ready()
+        self.stored_app_configs.append(self.app_configs)
         self.app_configs = OrderedDict()
         self.clear_cache()
         self._apps_loaded = False
@@ -340,7 +340,9 @@ class Apps(object):
         """
         Cancels a previous call to set_installed_apps().
         """
-        self.app_configs, self._apps_loaded, self._models_loaded = self.stored_app_configs.pop()
+        self.app_configs = self.stored_app_configs.pop()
+        self._apps_loaded = True
+        self._models_loaded = True
         self.clear_cache()
 
     def clear_cache(self):
@@ -429,9 +431,7 @@ class Apps(object):
         warnings.warn(
             "[a.path for a in get_app_configs()] supersedes get_app_paths().",
             PendingDeprecationWarning, stacklevel=2)
-
-        self.populate_models()
-
+        self.check_ready()
         app_paths = []
         for app in self.get_apps():
             app_paths.append(self._get_app_path(app))

+ 0 - 1
django/contrib/admin/sites.py

@@ -160,7 +160,6 @@ class AdminSite(object):
         The default implementation checks that admin and contenttypes apps are
         installed, as well as the auth context processor.
         """
-        apps.populate_apps()
         if not apps.has_app('django.contrib.admin'):
             raise ImproperlyConfigured("Put 'django.contrib.admin' in your "
                 "INSTALLED_APPS setting in order to use the admin application.")

+ 0 - 5
django/contrib/admin/validation.py

@@ -1,4 +1,3 @@
-from django.apps import apps
 from django.core.exceptions import ImproperlyConfigured
 from django.db import models
 from django.db.models.fields import FieldDoesNotExist
@@ -15,10 +14,6 @@ __all__ = ['BaseValidator', 'InlineValidator']
 
 
 class BaseValidator(object):
-    def __init__(self):
-        # Before we can introspect models, they need the app registry to be
-        # fully loaded so that inter-relations are set up correctly.
-        apps.populate_models()
 
     def validate(self, cls, model):
         for m in dir(self):

+ 0 - 4
django/core/serializers/base.py

@@ -3,7 +3,6 @@ Module for abstract serializer/unserializer base classes.
 """
 import warnings
 
-from django.apps import apps
 from django.db import models
 from django.utils import six
 
@@ -137,9 +136,6 @@ class Deserializer(six.Iterator):
             self.stream = six.StringIO(stream_or_string)
         else:
             self.stream = stream_or_string
-        # Make sure the app registy is loaded before deserialization starts
-        # (otherwise subclass calls to get_model() and friends might fail...)
-        apps.populate_models()
 
     def __iter__(self):
         return self

+ 0 - 2
django/core/serializers/python.py

@@ -88,8 +88,6 @@ def Deserializer(object_list, **options):
     db = options.pop('using', DEFAULT_DB_ALIAS)
     ignore = options.pop('ignorenonexistent', False)
 
-    apps.populate_models()
-
     for d in object_list:
         # Look up the model and starting build a dict of data for it.
         Model = _get_model(d["model"])

+ 2 - 7
django/core/wsgi.py

@@ -1,5 +1,4 @@
-from django.apps import apps
-from django.conf import settings
+import django
 from django.core.handlers.wsgi import WSGIHandler
 
 
@@ -12,9 +11,5 @@ def get_wsgi_application():
     case the internal WSGI implementation changes or moves in the future.
 
     """
-    # Configure the settings (this happens automatically on the first access).
-    # Populate the app registry.
-    apps.populate_apps(settings.INSTALLED_APPS)
-    apps.populate_models()
-
+    django.setup()
     return WSGIHandler()

+ 0 - 1
django/db/models/loading.py

@@ -30,6 +30,5 @@ def get_app_errors():
     try:
         return apps.app_errors
     except AttributeError:
-        apps.populate_models()
         apps.app_errors = {}
         return apps.app_errors

+ 13 - 3
docs/intro/tutorial01.txt

@@ -602,9 +602,19 @@ the Python import path to your :file:`mysite/settings.py` file.
 .. admonition:: Bypassing manage.py
 
     If you'd rather not use :file:`manage.py`, no problem. Just set the
-    ``DJANGO_SETTINGS_MODULE`` environment variable to ``mysite.settings`` and
-    run ``python`` from the same directory :file:`manage.py` is in (or ensure
-    that directory is on the Python path, so that ``import mysite`` works).
+    :envvar:`DJANGO_SETTINGS_MODULE` environment variable to
+    ``mysite.settings``, start a plain Python shell, and set up Django::
+
+    >>> import django
+    >>> django.setup()
+
+    If this raises an :exc:`~exceptions.AttributeError`, you're probably using
+    a version of Django that doesn't match this tutorial version. You'll want
+    to either switch to the older tutorial or the newer Django version.
+
+    You must run ``python`` from the same directory :file:`manage.py` is in,
+    or ensure that directory is on the Python path, so that ``import mysite``
+    works.
 
     For more information on all of this, see the :doc:`django-admin.py
     documentation </ref/django-admin>`.

+ 6 - 0
docs/ref/django-admin.txt

@@ -14,6 +14,12 @@ two things for you before delegating to ``django-admin.py``:
 * It sets the :envvar:`DJANGO_SETTINGS_MODULE` environment variable so that
   it points to your project's ``settings.py`` file.
 
+* It calls ``django.setup()`` to initialize various internals of Django.
+
+.. versionadded:: 1.7
+
+    ``django.setup()`` didn't exist in previous versions of Django.
+
 The ``django-admin.py`` script should be on your system path if you installed
 Django via its ``setup.py`` utility. If it's not on your path, you can find it
 in ``site-packages/django/bin`` within your Python installation. Consider

+ 9 - 0
docs/releases/1.7.txt

@@ -613,6 +613,15 @@ Since :setting:`INSTALLED_APPS` now supports application configuration classes
 in addition to application modules, you should review code that accesses this
 setting directly and use the app registry (:attr:`django.apps.apps`) instead.
 
+If you're using Django in a plain Python script (not a management command) and
+rely on the :envvar:`DJANGO_SETTINGS_MODULE` environment variable, you must
+now explicitly initialize Django at the beginning of your script with::
+
+    >>> import django
+    >>> django.setup()
+
+Otherwise, you will most likely encounter a :exc:`~exceptions.RuntimeError`.
+
 The "app registry" that manages the list of installed applications doesn't
 have the same features as the old "app cache". Even though the "app cache" was
 a private API, obsolete methods and arguments will be removed after a standard

+ 2 - 2
tests/runtests.py

@@ -9,6 +9,7 @@ import sys
 import tempfile
 import warnings
 
+import django
 from django import contrib
 from django.utils._os import upath
 from django.utils import six
@@ -85,7 +86,6 @@ def get_installed():
 
 
 def setup(verbosity, test_labels):
-    import django
     from django.apps import apps, AppConfig
     from django.conf import settings
     from django.test import TransactionTestCase, TestCase
@@ -128,7 +128,7 @@ def setup(verbosity, test_labels):
     # Load all the ALWAYS_INSTALLED_APPS.
     with warnings.catch_warnings():
         warnings.filterwarnings('ignore', 'django.contrib.comments is deprecated and will be removed before Django 1.8.', DeprecationWarning)
-        apps.populate_models()
+        django.setup()
 
     # Load all the test model apps.
     test_modules = get_test_modules()