2
0
Эх сурвалжийг харах

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 жил өмнө
parent
commit
80d74097b4

+ 9 - 0
django/__init__.py

@@ -6,3 +6,12 @@ def get_version(*args, **kwargs):
     # Only import if it's actually called.
     # Only import if it's actually called.
     from django.utils.version import get_version
     from django.utils.version import get_version
     return get_version(*args, **kwargs)
     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.
         Returns the model with the given case-insensitive model_name.
 
 
         Raises LookupError if no model exists with this name.
         Raises LookupError if no model exists with this name.
-
-        This method assumes that apps.populate_models() has run.
         """
         """
         if self.models is None:
         if self.models is None:
             raise LookupError(
             raise LookupError(
@@ -140,8 +138,6 @@ class AppConfig(object):
 
 
         Set the corresponding keyword argument to True to include such models.
         Set the corresponding keyword argument to True to include such models.
         Keyword arguments aren't documented; they're a private API.
         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():
         for model in self.models.values():
             if model._deferred and not include_deferred:
             if model._deferred and not include_deferred:
@@ -156,7 +152,8 @@ class AppConfig(object):
         # Dictionary of models for this app, primarily maintained in the
         # Dictionary of models for this app, primarily maintained in the
         # 'all_models' attribute of the Apps this AppConfig is attached to.
         # 'all_models' attribute of the Apps this AppConfig is attached to.
         # Injected as a parameter because it gets populated when models are
         # 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
         self.models = all_models
 
 
         if module_has_submodule(self.module, MODELS_MODULE_NAME):
         if module_has_submodule(self.module, MODELS_MODULE_NAME):

+ 22 - 22
django/apps/registry.py

@@ -3,7 +3,6 @@ import os
 import sys
 import sys
 import warnings
 import warnings
 
 
-from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
 from django.core.exceptions import ImproperlyConfigured
 from django.utils import lru_cache
 from django.utils import lru_cache
 from django.utils.module_loading import import_lock
 from django.utils.module_loading import import_lock
@@ -79,8 +78,6 @@ class Apps(object):
             # Application modules aren't expected to import anything, and
             # Application modules aren't expected to import anything, and
             # especially not other application modules, even indirectly.
             # especially not other application modules, even indirectly.
             # Therefore we simply import them sequentially.
             # Therefore we simply import them sequentially.
-            if installed_apps is None:
-                installed_apps = settings.INSTALLED_APPS
             for entry in installed_apps:
             for entry in installed_apps:
                 if isinstance(entry, AppConfig):
                 if isinstance(entry, AppConfig):
                     app_config = entry
                     app_config = entry
@@ -108,7 +105,9 @@ class Apps(object):
             if self._models_loaded:
             if self._models_loaded:
                 return
                 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
             # Models modules are likely to import other models modules, for
             # example to reference related objects. As a consequence:
             # example to reference related objects. As a consequence:
@@ -144,6 +143,15 @@ class Apps(object):
                 for app_config in self.get_app_configs():
                 for app_config in self.get_app_configs():
                     app_config.setup()
                     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
     @property
     def ready(self):
     def ready(self):
         """
         """
@@ -161,11 +169,7 @@ class Apps(object):
         If only_with_models_module in True (non-default), imports models and
         If only_with_models_module in True (non-default), imports models and
         considers only applications containing a models module.
         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():
         for app_config in self.app_configs.values():
             if only_with_models_module and app_config.models_module is None:
             if only_with_models_module and app_config.models_module is None:
                 continue
                 continue
@@ -180,11 +184,7 @@ class Apps(object):
         If only_with_models_module in True (non-default), imports models and
         If only_with_models_module in True (non-default), imports models and
         considers only applications containing a models module.
         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)
         app_config = self.app_configs.get(app_label)
         if app_config is None:
         if app_config is None:
             raise LookupError("No installed app with label '%s'." % app_label)
             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.
         Set the corresponding keyword argument to True to include such models.
         """
         """
-        self.populate_models()
-
+        self.check_ready()
         if app_mod:
         if app_mod:
             warnings.warn(
             warnings.warn(
                 "The app_mod argument of get_models is deprecated.",
                 "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
         Raises LookupError if no application exists with this label, or no
         model exists with this name in the application.
         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())
         return self.get_app_config(app_label).get_model(model_name.lower())
 
 
     def register_model(self, app_label, model):
     def register_model(self, app_label, model):
@@ -328,7 +327,8 @@ class Apps(object):
         imports safely (eg. that could lead to registering listeners twice),
         imports safely (eg. that could lead to registering listeners twice),
         models are registered when they're imported and never removed.
         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.app_configs = OrderedDict()
         self.clear_cache()
         self.clear_cache()
         self._apps_loaded = False
         self._apps_loaded = False
@@ -340,7 +340,9 @@ class Apps(object):
         """
         """
         Cancels a previous call to set_installed_apps().
         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()
         self.clear_cache()
 
 
     def clear_cache(self):
     def clear_cache(self):
@@ -429,9 +431,7 @@ class Apps(object):
         warnings.warn(
         warnings.warn(
             "[a.path for a in get_app_configs()] supersedes get_app_paths().",
             "[a.path for a in get_app_configs()] supersedes get_app_paths().",
             PendingDeprecationWarning, stacklevel=2)
             PendingDeprecationWarning, stacklevel=2)
-
-        self.populate_models()
-
+        self.check_ready()
         app_paths = []
         app_paths = []
         for app in self.get_apps():
         for app in self.get_apps():
             app_paths.append(self._get_app_path(app))
             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
         The default implementation checks that admin and contenttypes apps are
         installed, as well as the auth context processor.
         installed, as well as the auth context processor.
         """
         """
-        apps.populate_apps()
         if not apps.has_app('django.contrib.admin'):
         if not apps.has_app('django.contrib.admin'):
             raise ImproperlyConfigured("Put 'django.contrib.admin' in your "
             raise ImproperlyConfigured("Put 'django.contrib.admin' in your "
                 "INSTALLED_APPS setting in order to use the admin application.")
                 "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.core.exceptions import ImproperlyConfigured
 from django.db import models
 from django.db import models
 from django.db.models.fields import FieldDoesNotExist
 from django.db.models.fields import FieldDoesNotExist
@@ -15,10 +14,6 @@ __all__ = ['BaseValidator', 'InlineValidator']
 
 
 
 
 class BaseValidator(object):
 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):
     def validate(self, cls, model):
         for m in dir(self):
         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
 import warnings
 
 
-from django.apps import apps
 from django.db import models
 from django.db import models
 from django.utils import six
 from django.utils import six
 
 
@@ -137,9 +136,6 @@ class Deserializer(six.Iterator):
             self.stream = six.StringIO(stream_or_string)
             self.stream = six.StringIO(stream_or_string)
         else:
         else:
             self.stream = stream_or_string
             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):
     def __iter__(self):
         return 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)
     db = options.pop('using', DEFAULT_DB_ALIAS)
     ignore = options.pop('ignorenonexistent', False)
     ignore = options.pop('ignorenonexistent', False)
 
 
-    apps.populate_models()
-
     for d in object_list:
     for d in object_list:
         # Look up the model and starting build a dict of data for it.
         # Look up the model and starting build a dict of data for it.
         Model = _get_model(d["model"])
         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
 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.
     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()
     return WSGIHandler()

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

@@ -30,6 +30,5 @@ def get_app_errors():
     try:
     try:
         return apps.app_errors
         return apps.app_errors
     except AttributeError:
     except AttributeError:
-        apps.populate_models()
         apps.app_errors = {}
         apps.app_errors = {}
         return 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
 .. admonition:: Bypassing manage.py
 
 
     If you'd rather not use :file:`manage.py`, no problem. Just set the
     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
     For more information on all of this, see the :doc:`django-admin.py
     documentation </ref/django-admin>`.
     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 sets the :envvar:`DJANGO_SETTINGS_MODULE` environment variable so that
   it points to your project's ``settings.py`` file.
   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
 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
 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
 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
 in addition to application modules, you should review code that accesses this
 setting directly and use the app registry (:attr:`django.apps.apps`) instead.
 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
 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
 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
 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 tempfile
 import warnings
 import warnings
 
 
+import django
 from django import contrib
 from django import contrib
 from django.utils._os import upath
 from django.utils._os import upath
 from django.utils import six
 from django.utils import six
@@ -85,7 +86,6 @@ def get_installed():
 
 
 
 
 def setup(verbosity, test_labels):
 def setup(verbosity, test_labels):
-    import django
     from django.apps import apps, AppConfig
     from django.apps import apps, AppConfig
     from django.conf import settings
     from django.conf import settings
     from django.test import TransactionTestCase, TestCase
     from django.test import TransactionTestCase, TestCase
@@ -128,7 +128,7 @@ def setup(verbosity, test_labels):
     # Load all the ALWAYS_INSTALLED_APPS.
     # Load all the ALWAYS_INSTALLED_APPS.
     with warnings.catch_warnings():
     with warnings.catch_warnings():
         warnings.filterwarnings('ignore', 'django.contrib.comments is deprecated and will be removed before Django 1.8.', DeprecationWarning)
         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.
     # Load all the test model apps.
     test_modules = get_test_modules()
     test_modules = get_test_modules()