Fixed #21829 -- Added default AppConfigs.
Thanks Russell for the report, Marc for the initial patch, Carl for the final review, and everyone who contributed to the design discussion.
This commit is contained in:
parent
29ddae7436
commit
2ff93e027c
|
@ -61,13 +61,12 @@ class AppConfig(object):
|
||||||
Factory that creates an app config from an entry in INSTALLED_APPS.
|
Factory that creates an app config from an entry in INSTALLED_APPS.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# If import_module succeeds, entry is a path to an app module.
|
# If import_module succeeds, entry is a path to an app module,
|
||||||
|
# which may specify an app config class with default_app_config.
|
||||||
# Otherwise, entry is a path to an app config class or an error.
|
# Otherwise, entry is a path to an app config class or an error.
|
||||||
module = import_module(entry)
|
module = import_module(entry)
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Avoid django.utils.module_loading.import_by_path because it
|
|
||||||
# masks errors -- it reraises ImportError as ImproperlyConfigured.
|
|
||||||
mod_path, _, cls_name = entry.rpartition('.')
|
mod_path, _, cls_name = entry.rpartition('.')
|
||||||
|
|
||||||
# Raise the original exception when entry cannot be a path to an
|
# Raise the original exception when entry cannot be a path to an
|
||||||
|
@ -75,39 +74,51 @@ class AppConfig(object):
|
||||||
if not mod_path:
|
if not mod_path:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
mod = import_module(mod_path)
|
|
||||||
try:
|
|
||||||
cls = getattr(mod, cls_name)
|
|
||||||
except AttributeError:
|
|
||||||
# Emulate the error that "from <mod_path> import <cls_name>"
|
|
||||||
# would raise when <mod_path> exists but not <cls_name>, with
|
|
||||||
# more context (Python just says "cannot import name ...").
|
|
||||||
raise ImportError(
|
|
||||||
"cannot import name '%s' from '%s'" % (cls_name, mod_path))
|
|
||||||
|
|
||||||
# Check for obvious errors. (This check prevents duck typing, but
|
|
||||||
# it could be removed if it became a problem in practice.)
|
|
||||||
if not issubclass(cls, AppConfig):
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
"'%s' isn't a subclass of AppConfig." % entry)
|
|
||||||
|
|
||||||
# Obtain app name here rather than in AppClass.__init__ to keep
|
|
||||||
# all error checking for entries in INSTALLED_APPS in one place.
|
|
||||||
try:
|
|
||||||
app_name = cls.name
|
|
||||||
except AttributeError:
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
"'%s' must supply a name attribute." % entry)
|
|
||||||
|
|
||||||
# Ensure app_name points to a valid module.
|
|
||||||
app_module = import_module(app_name)
|
|
||||||
|
|
||||||
# Entry is a path to an app config class.
|
|
||||||
return cls(app_name, app_module)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Entry is a path to an app module.
|
try:
|
||||||
return cls(entry, module)
|
# If this works, the app module specifies an app config class.
|
||||||
|
entry = module.default_app_config
|
||||||
|
except AttributeError:
|
||||||
|
# Otherwise, it simply uses the default app config class.
|
||||||
|
return cls(entry, module)
|
||||||
|
else:
|
||||||
|
mod_path, _, cls_name = entry.rpartition('.')
|
||||||
|
|
||||||
|
# If we're reaching this point, we must load the app config class
|
||||||
|
# located at <mod_path>.<cls_name>.
|
||||||
|
|
||||||
|
# Avoid django.utils.module_loading.import_by_path because it
|
||||||
|
# masks errors -- it reraises ImportError as ImproperlyConfigured.
|
||||||
|
mod = import_module(mod_path)
|
||||||
|
try:
|
||||||
|
cls = getattr(mod, cls_name)
|
||||||
|
except AttributeError:
|
||||||
|
# Emulate the error that "from <mod_path> import <cls_name>"
|
||||||
|
# would raise when <mod_path> exists but not <cls_name>, with
|
||||||
|
# more context (Python just says "cannot import name ...").
|
||||||
|
raise ImportError(
|
||||||
|
"cannot import name '%s' from '%s'" % (cls_name, mod_path))
|
||||||
|
|
||||||
|
# Check for obvious errors. (This check prevents duck typing, but
|
||||||
|
# it could be removed if it became a problem in practice.)
|
||||||
|
if not issubclass(cls, AppConfig):
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"'%s' isn't a subclass of AppConfig." % entry)
|
||||||
|
|
||||||
|
# Obtain app name here rather than in AppClass.__init__ to keep
|
||||||
|
# all error checking for entries in INSTALLED_APPS in one place.
|
||||||
|
try:
|
||||||
|
app_name = cls.name
|
||||||
|
except AttributeError:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"'%s' must supply a name attribute." % entry)
|
||||||
|
|
||||||
|
# Ensure app_name points to a valid module.
|
||||||
|
app_module = import_module(app_name)
|
||||||
|
|
||||||
|
# Entry is a path to an app config class.
|
||||||
|
return cls(app_name, app_module)
|
||||||
|
|
||||||
|
|
||||||
def get_model(self, model_name):
|
def get_model(self, model_name):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -30,7 +30,7 @@ ALLOWED_HOSTS = []
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'django.contrib.admin.apps.AdminConfig',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# ACTION_CHECKBOX_NAME is unused, but should stay since its import from here
|
# ACTION_CHECKBOX_NAME is unused, but should stay since its import from here
|
||||||
# has been referenced in documentation.
|
# has been referenced in documentation.
|
||||||
from django.contrib.admin.checks import check_admin_app
|
|
||||||
from django.contrib.admin.decorators import register
|
from django.contrib.admin.decorators import register
|
||||||
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
||||||
from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
|
from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
|
||||||
|
@ -9,7 +8,6 @@ from django.contrib.admin.filters import (ListFilter, SimpleListFilter,
|
||||||
FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
|
FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
|
||||||
ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
|
ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
|
||||||
from django.contrib.admin.sites import AdminSite, site
|
from django.contrib.admin.sites import AdminSite, site
|
||||||
from django.core import checks
|
|
||||||
from django.utils.module_loading import autodiscover_modules
|
from django.utils.module_loading import autodiscover_modules
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -24,4 +22,5 @@ __all__ = [
|
||||||
def autodiscover():
|
def autodiscover():
|
||||||
autodiscover_modules('admin', register_to=site)
|
autodiscover_modules('admin', register_to=site)
|
||||||
|
|
||||||
checks.register('admin')(check_admin_app)
|
|
||||||
|
default_app_config = 'django.contrib.admin.apps.AdminConfig'
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.core import checks
|
||||||
|
from django.contrib.admin.checks import check_admin_app
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class AdminConfig(AppConfig):
|
class SimpleAdminConfig(AppConfig):
|
||||||
|
"""Simple AppConfig which does not do automatic discovery."""
|
||||||
|
|
||||||
name = 'django.contrib.admin'
|
name = 'django.contrib.admin'
|
||||||
verbose_name = _("administration")
|
verbose_name = _("administration")
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
checks.register('admin')(check_admin_app)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminConfig(SimpleAdminConfig):
|
||||||
|
"""The default AppConfig for admin which does autodiscovery."""
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
super(AdminConfig, self).ready()
|
||||||
self.module.autodiscover()
|
self.module.autodiscover()
|
||||||
|
|
|
@ -161,7 +161,7 @@ class AdminSite(object):
|
||||||
installed, as well as the auth context processor.
|
installed, as well as the auth context processor.
|
||||||
"""
|
"""
|
||||||
if not apps.is_installed('django.contrib.admin'):
|
if not apps.is_installed('django.contrib.admin'):
|
||||||
raise ImproperlyConfigured("Put 'django.contrib.admin.apps.AdminConfig' in "
|
raise ImproperlyConfigured("Put 'django.contrib.admin' in "
|
||||||
"your INSTALLED_APPS setting in order to use the admin application.")
|
"your INSTALLED_APPS setting in order to use the admin application.")
|
||||||
if not apps.is_installed('django.contrib.contenttypes'):
|
if not apps.is_installed('django.contrib.contenttypes'):
|
||||||
raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in "
|
raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in "
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.admindocs.apps.AdminDocsConfig'
|
|
@ -2,8 +2,6 @@ import inspect
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.checks import check_user_model
|
|
||||||
from django.core import checks
|
|
||||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
from django.utils.module_loading import import_by_path
|
from django.utils.module_loading import import_by_path
|
||||||
from django.middleware.csrf import rotate_token
|
from django.middleware.csrf import rotate_token
|
||||||
|
@ -15,10 +13,6 @@ BACKEND_SESSION_KEY = '_auth_user_backend'
|
||||||
REDIRECT_FIELD_NAME = 'next'
|
REDIRECT_FIELD_NAME = 'next'
|
||||||
|
|
||||||
|
|
||||||
# Register the user model checks
|
|
||||||
checks.register('models')(check_user_model)
|
|
||||||
|
|
||||||
|
|
||||||
def load_backend(path):
|
def load_backend(path):
|
||||||
return import_by_path(path)()
|
return import_by_path(path)()
|
||||||
|
|
||||||
|
@ -164,3 +158,6 @@ def get_permission_codename(action, opts):
|
||||||
Returns the codename of the permission for the specified action.
|
Returns the codename of the permission for the specified action.
|
||||||
"""
|
"""
|
||||||
return '%s_%s' % (action, opts.model_name)
|
return '%s_%s' % (action, opts.model_name)
|
||||||
|
|
||||||
|
|
||||||
|
default_app_config = 'django.contrib.auth.apps.AuthConfig'
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.core import checks
|
||||||
|
from django.contrib.auth.checks import check_user_model
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
@ -6,3 +8,6 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
class AuthConfig(AppConfig):
|
class AuthConfig(AppConfig):
|
||||||
name = 'django.contrib.auth'
|
name = 'django.contrib.auth'
|
||||||
verbose_name = _("authentication and authorization")
|
verbose_name = _("authentication and authorization")
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
checks.register('models')(check_user_model)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
import warnings
|
import warnings
|
||||||
from django.apps import apps
|
from django.apps import apps as django_apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
@ -16,7 +16,7 @@ def get_comment_app():
|
||||||
Get the comment app (i.e. "django.contrib.comments") as defined in the settings
|
Get the comment app (i.e. "django.contrib.comments") as defined in the settings
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
app_config = apps.get_app_config(get_comment_app_name().rpartition(".")[2])
|
app_config = django_apps.get_app_config(get_comment_app_name().rpartition(".")[2])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise ImproperlyConfigured("The COMMENTS_APP (%r) "
|
raise ImproperlyConfigured("The COMMENTS_APP (%r) "
|
||||||
"must be in INSTALLED_APPS" % settings.COMMENTS_APP)
|
"must be in INSTALLED_APPS" % settings.COMMENTS_APP)
|
||||||
|
@ -85,3 +85,6 @@ def get_approve_url(comment):
|
||||||
else:
|
else:
|
||||||
return urlresolvers.reverse("django.contrib.comments.views.moderation.approve",
|
return urlresolvers.reverse("django.contrib.comments.views.moderation.approve",
|
||||||
args=(comment.id,))
|
args=(comment.id,))
|
||||||
|
|
||||||
|
|
||||||
|
default_app_config = 'django.contrib.comments.apps.CommentsConfig'
|
||||||
|
|
|
@ -1,8 +1 @@
|
||||||
# -*- coding: utf-8 -*-
|
default_app_config = 'django.contrib.contenttypes.apps.ContentTypesConfig'
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.checks import check_generic_foreign_keys
|
|
||||||
from django.core import checks
|
|
||||||
|
|
||||||
|
|
||||||
checks.register('models')(check_generic_foreign_keys)
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.contrib.contenttypes.checks import check_generic_foreign_keys
|
||||||
|
from django.core import checks
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ContentTypesConfig(AppConfig):
|
class ContentTypesConfig(AppConfig):
|
||||||
name = 'django.contrib.contenttypes'
|
name = 'django.contrib.contenttypes'
|
||||||
verbose_name = _("content types")
|
verbose_name = _("content types")
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
checks.register('models')(check_generic_foreign_keys)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.flatpages.apps.FlatPagesConfig'
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.formtools.apps.FormToolsConfig'
|
|
@ -4,3 +4,6 @@ if six.PY3:
|
||||||
memoryview = memoryview
|
memoryview = memoryview
|
||||||
else:
|
else:
|
||||||
memoryview = buffer
|
memoryview = buffer
|
||||||
|
|
||||||
|
|
||||||
|
default_app_config = 'django.contrib.gis.apps.GISConfig'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.humanize.apps.HumanizeConfig'
|
|
@ -1,2 +1,5 @@
|
||||||
from django.contrib.messages.api import * # NOQA
|
from django.contrib.messages.api import * # NOQA
|
||||||
from django.contrib.messages.constants import * # NOQA
|
from django.contrib.messages.constants import * # NOQA
|
||||||
|
|
||||||
|
|
||||||
|
default_app_config = 'django.contrib.messages.apps.MessagesConfig'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.redirects.apps.RedirectsConfig'
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.sessions.apps.SessionsConfig'
|
|
@ -133,3 +133,6 @@ class GenericSitemap(Sitemap):
|
||||||
if self.date_field is not None:
|
if self.date_field is not None:
|
||||||
return getattr(item, self.date_field)
|
return getattr(item, self.date_field)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
default_app_config = 'django.contrib.sitemaps.apps.SiteMapsConfig'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.sites.apps.SitesConfig'
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.staticfiles.apps.StaticFilesConfig'
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.syndication.apps.SyndicationConfig'
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'django.contrib.webdesign.apps.WebDesignConfig'
|
|
@ -29,7 +29,8 @@ class CheckRegistry(object):
|
||||||
|
|
||||||
def inner(check):
|
def inner(check):
|
||||||
check.tags = tags
|
check.tags = tags
|
||||||
self.registered_checks.append(check)
|
if check not in self.registered_checks:
|
||||||
|
self.registered_checks.append(check)
|
||||||
return check
|
return check
|
||||||
|
|
||||||
return inner
|
return inner
|
||||||
|
|
|
@ -435,7 +435,7 @@ look like this:
|
||||||
:filename: mysite/settings.py
|
:filename: mysite/settings.py
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'django.contrib.admin.apps.AdminConfig',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
|
@ -444,13 +444,6 @@ look like this:
|
||||||
'polls',
|
'polls',
|
||||||
)
|
)
|
||||||
|
|
||||||
.. admonition:: Doesn't match what you see?
|
|
||||||
|
|
||||||
If you're seeing ``'django.contrib.admin'`` instead of
|
|
||||||
``'django.contrib.admin.apps.AdminConfig'``, 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.
|
|
||||||
|
|
||||||
Now Django knows to include the ``polls`` app. Let's run another command:
|
Now Django knows to include the ``polls`` app. Let's run another command:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
|
@ -49,9 +49,15 @@ Configuring applications
|
||||||
To configure an application, subclass :class:`~django.apps.AppConfig` and put
|
To configure an application, subclass :class:`~django.apps.AppConfig` and put
|
||||||
the dotted path to that subclass in :setting:`INSTALLED_APPS`.
|
the dotted path to that subclass in :setting:`INSTALLED_APPS`.
|
||||||
|
|
||||||
Django uses the default :class:`~django.apps.AppConfig` class when
|
When :setting:`INSTALLED_APPS` simply contains the dotted path to an
|
||||||
:setting:`INSTALLED_APPS` simply contains the dotted path to an application
|
application module, Django checks for a ``default_app_config`` variable in
|
||||||
module.
|
that module.
|
||||||
|
|
||||||
|
If it's defined, it's the dotted path to the :class:`~django.apps.AppConfig`
|
||||||
|
subclass for that application.
|
||||||
|
|
||||||
|
If there is no ``default_app_config``, Django uses the base
|
||||||
|
:class:`~django.apps.AppConfig` class.
|
||||||
|
|
||||||
For application authors
|
For application authors
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -67,8 +73,23 @@ would provide a proper name for the admin::
|
||||||
name = 'rock_n_roll'
|
name = 'rock_n_roll'
|
||||||
verbose_name = "Rock ’n’ roll"
|
verbose_name = "Rock ’n’ roll"
|
||||||
|
|
||||||
You would then tell your users to add ``'rock_n_roll.apps.RockNRollConfig'``
|
You can make your application load this :class:`~django.apps.AppConfig`
|
||||||
to their :setting:`INSTALLED_APPS`.
|
subclass by default as follows::
|
||||||
|
|
||||||
|
# rock_n_roll/__init__.py
|
||||||
|
|
||||||
|
default_app_config = 'rock_n_roll.apps.RockNRollConfig'
|
||||||
|
|
||||||
|
That will cause ``RockNRollConfig`` to be used when :setting:`INSTALLED_APPS`
|
||||||
|
just contains ``'rock_n_roll'``. This allows you to make use of
|
||||||
|
:class:`~django.apps.AppConfig` features without requiring your users to
|
||||||
|
update their :setting:`INSTALLED_APPS` setting.
|
||||||
|
|
||||||
|
Of course, you can also tell your users to put
|
||||||
|
``'rock_n_roll.apps.RockNRollConfig'`` in their :setting:`INSTALLED_APPS`
|
||||||
|
setting. You can even provide several different
|
||||||
|
:class:`~django.apps.AppConfig` subclasses with different behaviors and allow
|
||||||
|
your users to choose one via their :setting:`INSTALLED_APPS` setting.
|
||||||
|
|
||||||
The recommended convention is to put the configuration class in a submodule of
|
The recommended convention is to put the configuration class in a submodule of
|
||||||
the application called ``apps``. However, this isn't enforced by Django.
|
the application called ``apps``. However, this isn't enforced by Django.
|
||||||
|
@ -87,7 +108,7 @@ configuration::
|
||||||
|
|
||||||
# anthology/apps.py
|
# anthology/apps.py
|
||||||
|
|
||||||
from rock_n_roll.app import RockNRollConfig
|
from rock_n_roll.apps import RockNRollConfig
|
||||||
|
|
||||||
class GypsyJazzConfig(RockNRollConfig):
|
class GypsyJazzConfig(RockNRollConfig):
|
||||||
verbose_name = "Gypsy jazz"
|
verbose_name = "Gypsy jazz"
|
||||||
|
@ -213,7 +234,7 @@ Application registry
|
||||||
.. method:: apps.is_installed(app_name)
|
.. method:: apps.is_installed(app_name)
|
||||||
|
|
||||||
Checks whether an application with the given name exists in the registry.
|
Checks whether an application with the given name exists in the registry.
|
||||||
``app_name`` is the full name of the app, e.g. 'django.contrib.admin'.
|
``app_name`` is the full name of the app, e.g. ``'django.contrib.admin'``.
|
||||||
|
|
||||||
Unlike :meth:`~django.apps.apps.get_app_config`, this method can be called
|
Unlike :meth:`~django.apps.apps.get_app_config`, this method can be called
|
||||||
safely at import time. If the registry is still being populated, it may
|
safely at import time. If the registry is still being populated, it may
|
||||||
|
|
|
@ -23,12 +23,7 @@ The admin is enabled in the default project template used by
|
||||||
|
|
||||||
For reference, here are the requirements:
|
For reference, here are the requirements:
|
||||||
|
|
||||||
1. Add ``'django.contrib.admin.apps.AdminConfig'`` to your
|
1. Add ``'django.contrib.admin'`` to your :setting:`INSTALLED_APPS` setting.
|
||||||
:setting:`INSTALLED_APPS` setting.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
|
|
||||||
:setting:`INSTALLED_APPS` used to contain ``'django.contrib.admin'``.
|
|
||||||
|
|
||||||
2. The admin has four dependencies - :mod:`django.contrib.auth`,
|
2. The admin has four dependencies - :mod:`django.contrib.auth`,
|
||||||
:mod:`django.contrib.contenttypes`,
|
:mod:`django.contrib.contenttypes`,
|
||||||
|
@ -136,16 +131,23 @@ The register decorator
|
||||||
Discovery of admin files
|
Discovery of admin files
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
The admin needs to be instructed to look for ``admin.py`` files in your project.
|
When you put ``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS`
|
||||||
The easiest solution is to put ``'django.contrib.admin.apps.AdminConfig'`` in
|
setting, Django automatically looks for an ``admin`` module in each
|
||||||
your :setting:`INSTALLED_APPS` setting.
|
application and imports it.
|
||||||
|
|
||||||
.. class:: apps.AdminConfig
|
.. class:: apps.AdminConfig
|
||||||
|
|
||||||
.. versionadded:: 1.7
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
This class calls :func:`~django.contrib.admin.autodiscover()` when Django
|
This is the default :class:`~django.apps.AppConfig` class for the admin.
|
||||||
starts.
|
It calls :func:`~django.contrib.admin.autodiscover()` when Django starts.
|
||||||
|
|
||||||
|
.. class:: apps.SimpleAdminConfig
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
This class works like :class:`~django.contrib.admin.apps.AdminConfig`,
|
||||||
|
except it doesn't call :func:`~django.contrib.admin.autodiscover()`.
|
||||||
|
|
||||||
.. function:: autodiscover
|
.. function:: autodiscover
|
||||||
|
|
||||||
|
@ -155,13 +157,23 @@ your :setting:`INSTALLED_APPS` setting.
|
||||||
.. versionchanged:: 1.7
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
Previous versions of Django recommended calling this function directly
|
Previous versions of Django recommended calling this function directly
|
||||||
in the URLconf. :class:`~django.contrib.admin.apps.AdminConfig`
|
in the URLconf. As of Django 1.7 this isn't needed anymore.
|
||||||
replaces that mechanism and is more robust.
|
:class:`~django.contrib.admin.apps.AdminConfig` takes care of running
|
||||||
|
the auto-discovery automatically.
|
||||||
|
|
||||||
If you are using a custom ``AdminSite``, it is common to import all of the
|
If you are using a custom ``AdminSite``, it is common to import all of the
|
||||||
``ModelAdmin`` subclasses into your code and register them to the custom
|
``ModelAdmin`` subclasses into your code and register them to the custom
|
||||||
``AdminSite``. In that case, simply put ``'django.contrib.admin'`` in your
|
``AdminSite``. In that case, in order to disable auto-discovery, you should
|
||||||
:setting:`INSTALLED_APPS` setting, as you don't need autodiscovery.
|
put ``'django.contrib.admin.apps.SimpleAdminConfig'`` instead of
|
||||||
|
``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS` setting.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
In previous versions, the admin needed to be instructed to look for
|
||||||
|
``admin.py`` files with :func:`~django.contrib.admin.autodiscover()`.
|
||||||
|
As of Django 1.7, auto-discovery is enabled by default and must be
|
||||||
|
explicitly disabled when it's undesirable.
|
||||||
|
|
||||||
|
|
||||||
``ModelAdmin`` options
|
``ModelAdmin`` options
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -2426,11 +2438,12 @@ In this example, we register the ``AdminSite`` instance
|
||||||
(r'^myadmin/', include(admin_site.urls)),
|
(r'^myadmin/', include(admin_site.urls)),
|
||||||
)
|
)
|
||||||
|
|
||||||
Note that you don't need autodiscovery of ``admin`` modules when using your
|
Note that you may not want autodiscovery of ``admin`` modules when using your
|
||||||
own ``AdminSite`` instance since you will likely be importing all the per-app
|
own ``AdminSite`` instance since you will likely be importing all the per-app
|
||||||
``admin`` modules in your ``myproject.admin`` module. This means you likely do
|
``admin`` modules in your ``myproject.admin`` module. This means you need to
|
||||||
not need ``'django.contrib.admin.app.AdminConfig'`` in your
|
put ``'django.contrib.admin.app.SimpleAdminConfig'`` instead of
|
||||||
:setting:`INSTALLED_APPS` and can just use ``'django.contrib.admin'``.
|
``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS` setting.
|
||||||
|
|
||||||
|
|
||||||
Multiple admin sites in the same URLconf
|
Multiple admin sites in the same URLconf
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
|
@ -115,7 +115,7 @@ In addition, modify the :setting:`INSTALLED_APPS` setting to include
|
||||||
and ``world`` (your newly created application)::
|
and ``world`` (your newly created application)::
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'django.contrib.admin.apps.AdminConfig',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
|
|
|
@ -89,17 +89,14 @@ Improvements thus far include:
|
||||||
* The name of applications can be customized in the admin with the
|
* The name of applications can be customized in the admin with the
|
||||||
:attr:`~django.apps.AppConfig.verbose_name` of application configurations.
|
:attr:`~django.apps.AppConfig.verbose_name` of application configurations.
|
||||||
|
|
||||||
|
* The admin automatically calls :func:`~django.contrib.admin.autodiscover()`
|
||||||
|
when Django starts. You can consequently remove this line from your
|
||||||
|
URLconf.
|
||||||
|
|
||||||
* Django imports all application configurations and models as soon as it
|
* Django imports all application configurations and models as soon as it
|
||||||
starts, through a deterministic and straightforward process. This should
|
starts, through a deterministic and straightforward process. This should
|
||||||
make it easier to diagnose import issues such as import loops.
|
make it easier to diagnose import issues such as import loops.
|
||||||
|
|
||||||
* The admin has an :class:`~django.contrib.admin.apps.AdminConfig` application
|
|
||||||
configuration class. When Django starts, this class takes care of calling
|
|
||||||
:func:`~django.contrib.admin.autodiscover()`. To use it, simply replace
|
|
||||||
``'django.contrib.admin'`` in :setting:`INSTALLED_APPS` with
|
|
||||||
``'django.contrib.admin.apps.AdminConfig'`` and remove
|
|
||||||
``admin.autodiscover()`` from your URLconf.
|
|
||||||
|
|
||||||
New method on Field subclasses
|
New method on Field subclasses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -698,6 +695,12 @@ regressions cannot be ruled out. You may encounter the following exceptions:
|
||||||
results. The code will be executed when you first need its results. This
|
results. The code will be executed when you first need its results. This
|
||||||
concept is known as "lazy evaluation".
|
concept is known as "lazy evaluation".
|
||||||
|
|
||||||
|
* ``django.contrib.admin`` will now automatically perform autodiscovery of
|
||||||
|
``admin`` modules in installed applications. To prevent it, change your
|
||||||
|
:setting:`INSTALLED_APPS` to contain
|
||||||
|
``'django.contrib.admin.apps.SimpleAdminConfig'`` instead of
|
||||||
|
``'django.contrib.admin'``.
|
||||||
|
|
||||||
Standalone scripts
|
Standalone scripts
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -67,9 +67,8 @@ Creating superusers
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
:djadmin:`manage.py migrate <migrate>` prompts you to create a superuser the
|
:djadmin:`manage.py migrate <migrate>` prompts you to create a superuser the
|
||||||
first time you run it with ``'django.contrib.auth'`` in your
|
first time you run it with ``'django.contrib.auth'`` installed. If you need to
|
||||||
:setting:`INSTALLED_APPS`. If you need to create a superuser at a later date,
|
create a superuser at a later date, you can use a command line utility::
|
||||||
you can use a command line utility::
|
|
||||||
|
|
||||||
$ python manage.py createsuperuser --username=joe --email=joe@example.com
|
$ python manage.py createsuperuser --username=joe --email=joe@example.com
|
||||||
|
|
||||||
|
|
|
@ -1113,7 +1113,7 @@ class ManageCheck(AdminScriptTestCase):
|
||||||
apps=[
|
apps=[
|
||||||
'admin_scripts.complex_app',
|
'admin_scripts.complex_app',
|
||||||
'admin_scripts.simple_app',
|
'admin_scripts.simple_app',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin.apps.SimpleAdminConfig',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = 'apps.default_config_app.apps.CustomConfig'
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CustomConfig(AppConfig):
|
||||||
|
name = 'apps.default_config_app'
|
|
@ -7,6 +7,7 @@ from django.db import models
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
from .default_config_app.apps import CustomConfig
|
||||||
from .models import TotallyNormal, SoAlternative, new_apps
|
from .models import TotallyNormal, SoAlternative, new_apps
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,6 +83,11 @@ class AppsTests(TestCase):
|
||||||
with self.settings(INSTALLED_APPS=['apps.apps.NoSuchConfig']):
|
with self.settings(INSTALLED_APPS=['apps.apps.NoSuchConfig']):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_default_app_config(self):
|
||||||
|
with self.settings(INSTALLED_APPS=['apps.default_config_app']):
|
||||||
|
config = apps.get_app_config('default_config_app')
|
||||||
|
self.assertIsInstance(config, CustomConfig)
|
||||||
|
|
||||||
@override_settings(INSTALLED_APPS=SOME_INSTALLED_APPS)
|
@override_settings(INSTALLED_APPS=SOME_INSTALLED_APPS)
|
||||||
def test_get_app_configs(self):
|
def test_get_app_configs(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1049,7 +1049,7 @@ class AppResolutionOrderI18NTests(ResolutionOrderI18NTests):
|
||||||
# Doesn't work because it's added later in the list.
|
# Doesn't work because it's added later in the list.
|
||||||
self.assertUgettext('Date/time', 'Datum/Zeit')
|
self.assertUgettext('Date/time', 'Datum/Zeit')
|
||||||
|
|
||||||
with self.modify_settings(INSTALLED_APPS={'remove': 'django.contrib.admin'}):
|
with self.modify_settings(INSTALLED_APPS={'remove': 'django.contrib.admin.apps.SimpleAdminConfig'}):
|
||||||
self.flush_caches()
|
self.flush_caches()
|
||||||
activate('de')
|
activate('de')
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ ALWAYS_INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.comments',
|
'django.contrib.comments',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin.apps.SimpleAdminConfig',
|
||||||
'django.contrib.admindocs',
|
'django.contrib.admindocs',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.humanize',
|
'django.contrib.humanize',
|
||||||
|
@ -168,12 +168,11 @@ def setup(verbosity, test_labels):
|
||||||
for label in test_labels_set)
|
for label in test_labels_set)
|
||||||
|
|
||||||
installed_app_names = set(get_installed())
|
installed_app_names = set(get_installed())
|
||||||
if module_found_in_labels:
|
if module_found_in_labels and module_label not in installed_app_names:
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
print("Importing application %s" % module_name)
|
print("Importing application %s" % module_name)
|
||||||
# HACK.
|
# HACK.
|
||||||
if module_label not in installed_app_names:
|
settings.INSTALLED_APPS.append(module_label)
|
||||||
settings.INSTALLED_APPS.append(module_label)
|
|
||||||
app_config = AppConfig.create(module_label)
|
app_config = AppConfig.create(module_label)
|
||||||
apps.app_configs[app_config.label] = app_config
|
apps.app_configs[app_config.label] = app_config
|
||||||
app_config.import_models(apps.all_models[app_config.label])
|
app_config.import_models(apps.all_models[app_config.label])
|
||||||
|
|
Loading…
Reference in New Issue