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.
This commit is contained in:
parent
7ed20e0153
commit
80d74097b4
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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.check_ready()
|
||||||
self.populate_models()
|
|
||||||
else:
|
|
||||||
self.populate_apps()
|
|
||||||
|
|
||||||
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.check_ready()
|
||||||
self.populate_models()
|
|
||||||
else:
|
|
||||||
self.populate_apps()
|
|
||||||
|
|
||||||
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.check_ready()
|
||||||
self.populate_models()
|
|
||||||
|
|
||||||
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))
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.apps import apps
|
import django
|
||||||
from django.conf import settings
|
|
||||||
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).
|
django.setup()
|
||||||
# Populate the app registry.
|
|
||||||
apps.populate_apps(settings.INSTALLED_APPS)
|
|
||||||
apps.populate_models()
|
|
||||||
|
|
||||||
return WSGIHandler()
|
return WSGIHandler()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
:envvar:`DJANGO_SETTINGS_MODULE` environment variable to
|
||||||
run ``python`` from the same directory :file:`manage.py` is in (or ensure
|
``mysite.settings``, start a plain Python shell, and set up Django::
|
||||||
that directory is on the Python path, so that ``import mysite`` works).
|
|
||||||
|
>>> 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>`.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue