Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #29695 -- Added system checks for admin's app dependencies and TEMPLATES setting. #9868

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
91 changes: 61 additions & 30 deletions django/contrib/admin/checks.py
Expand Up @@ -14,7 +14,8 @@
from django.forms.models import (
BaseModelForm, BaseModelFormSet, _get_foreign_key,
)
from django.template.engine import Engine
from django.template import engines
from django.template.backends.django import DjangoTemplates
from django.utils.deprecation import RemovedInDjango30Warning
from django.utils.inspect import get_func_args

Expand All @@ -31,38 +32,68 @@ def check_dependencies(**kwargs):
"""
Check that the admin's dependencies are correctly installed.
"""
if not apps.is_installed('django.contrib.admin'):
return []
errors = []
# contrib.contenttypes must be installed.
if not apps.is_installed('django.contrib.contenttypes'):
missing_app = checks.Error(
"'django.contrib.contenttypes' must be in INSTALLED_APPS in order "
"to use the admin application.",
id="admin.E401",
)
errors.append(missing_app)
# The auth context processor must be installed if using the default
# authentication backend.
try:
default_template_engine = Engine.get_default()
except Exception:
# Skip this non-critical check:
# 1. if the user has a non-trivial TEMPLATES setting and Django
# can't find a default template engine
# 2. if anything goes wrong while loading template engines, in
# order to avoid raising an exception from a confusing location
# Catching ImproperlyConfigured suffices for 1. but 2. requires
# catching all exceptions.
pass
app_dependencies = (
('django.contrib.contenttypes', 401),
('django.contrib.auth', 405),
('django.contrib.messages', 406),
('django.contrib.sessions', 407),
)
for app_name, error_code in app_dependencies:
if not apps.is_installed(app_name):
errors.append(checks.Error(
"'%s' must be in INSTALLED_APPS in order to use the admin "
"application." % app_name,
id='admin.E%d' % error_code,
))
for engine in engines.all():
if isinstance(engine, DjangoTemplates):
django_templates_instance = engine.engine
break
else:
django_templates_instance = None
if not django_templates_instance:
errors.append(checks.Error(
"A 'django.template.backends.django.DjangoTemplates' instance "
"must be configured in TEMPLATES in order to use the admin "
"application.",
id='admin.E403',
))
else:
if ('django.contrib.auth.context_processors.auth'
not in default_template_engine.context_processors and
'django.contrib.auth.backends.ModelBackend' in settings.AUTHENTICATION_BACKENDS):
missing_template = checks.Error(
"'django.contrib.auth.context_processors.auth' must be in "
"TEMPLATES in order to use the admin application.",
id="admin.E402"
)
errors.append(missing_template)
not in django_templates_instance.context_processors and
'django.contrib.auth.backends.ModelBackend'
in settings.AUTHENTICATION_BACKENDS):
errors.append(checks.Error(
"'django.contrib.auth.context_processors.auth' must be "
"enabled in DjangoTemplates (TEMPLATES) if using the default "
"auth backend in order to use the admin application.",
id='admin.E402',
))
if ('django.contrib.messages.context_processors.messages'
not in django_templates_instance.context_processors):
errors.append(checks.Error(
"'django.contrib.messages.context_processors.messages' must "
"be enabled in DjangoTemplates (TEMPLATES) in order to use "
"the admin application.",
id='admin.E404',
))
if ('django.contrib.auth.middleware.AuthenticationMiddleware'
not in settings.MIDDLEWARE):
errors.append(checks.Error(
"'django.contrib.auth.middleware.AuthenticationMiddleware' must "
"be in MIDDLEWARE in order to use the admin application.",
id='admin.E408',
))
if ('django.contrib.messages.middleware.MessageMiddleware'
not in settings.MIDDLEWARE):
errors.append(checks.Error(
"'django.contrib.messages.middleware.MessageMiddleware' must "
"be in MIDDLEWARE in order to use the admin application.",
id='admin.E409',
))
return errors


Expand Down
21 changes: 20 additions & 1 deletion docs/ref/checks.txt
Expand Up @@ -624,7 +624,26 @@ The following checks are performed on the default
* **admin.E401**: :mod:`django.contrib.contenttypes` must be in
:setting:`INSTALLED_APPS` in order to use the admin application.
* **admin.E402**: :mod:`django.contrib.auth.context_processors.auth`
must be in :setting:`TEMPLATES` in order to use the admin application.
must be enabled in :class:`~django.template.backends.django.DjangoTemplates`
(:setting:`TEMPLATES`) if using the default auth backend in order to use the
admin application.
* **admin.E403**: A :class:`django.template.backends.django.DjangoTemplates`
instance must be configured in :setting:`TEMPLATES` in order to use the
admin application.
* **admin.E404**: ``django.contrib.messages.context_processors.messages``
must be enabled in :class:`~django.template.backends.django.DjangoTemplates`
(:setting:`TEMPLATES`) in order to use the admin application.
* **admin.E405**: :mod:`django.contrib.auth` must be in
:setting:`INSTALLED_APPS` in order to use the admin application.
* **admin.E406**: :mod:`django.contrib.messages` must be in
:setting:`INSTALLED_APPS` in order to use the admin application.
* **admin.E407**: :mod:`django.contrib.sessions` must be in
:setting:`INSTALLED_APPS` in order to use the admin application.
* **admin.E408**:
:class:`django.contrib.auth.middleware.AuthenticationMiddleware` must be in
:setting:`MIDDLEWARE` in order to use the admin application.
* **admin.E409**: :class:`django.contrib.messages.middleware.MessageMiddleware`
must be in :setting:`MIDDLEWARE` in order to use the admin application.

``auth``
--------
Expand Down
87 changes: 81 additions & 6 deletions tests/admin_checks/tests.py
Expand Up @@ -43,6 +43,8 @@ def check(self, **kwargs):
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'admin_checks',
],
)
Expand All @@ -58,20 +60,42 @@ def test_checks_are_performed(self):
admin.site.unregister(Song)

@override_settings(INSTALLED_APPS=['django.contrib.admin'])
def test_contenttypes_dependency(self):
def test_apps_dependencies(self):
errors = admin.checks.check_dependencies()
expected = [
checks.Error(
"'django.contrib.contenttypes' must be in "
"INSTALLED_APPS in order to use the admin application.",
id="admin.E401",
),
checks.Error(
"'django.contrib.auth' must be in INSTALLED_APPS in order "
"to use the admin application.",
id='admin.E405',
),
checks.Error(
"'django.contrib.messages' must be in INSTALLED_APPS in order "
"to use the admin application.",
id='admin.E406',
),
checks.Error(
"'django.contrib.sessions' must be in INSTALLED_APPS in order "
"to use the admin application.",
id='admin.E407',
)
]
self.assertEqual(errors, expected)

@override_settings(TEMPLATES=[])
def test_no_template_engines(self):
self.assertEqual(admin.checks.check_dependencies(), [])
self.assertEqual(admin.checks.check_dependencies(), [
checks.Error(
"A 'django.template.backends.django.DjangoTemplates' "
"instance must be configured in TEMPLATES in order to use "
"the admin application.",
id='admin.E403',
)
])

@override_settings(
TEMPLATES=[{
Expand All @@ -83,13 +107,64 @@ def test_no_template_engines(self):
},
}],
)
def test_auth_contextprocessor_dependency(self):
def test_context_processor_dependencies(self):
expected = [
checks.Error(
"'django.contrib.auth.context_processors.auth' must be "
"enabled in DjangoTemplates (TEMPLATES) if using the default "
"auth backend in order to use the admin application.",
id='admin.E402',
),
checks.Error(
"'django.contrib.messages.context_processors.messages' must "
"be enabled in DjangoTemplates (TEMPLATES) in order to use "
"the admin application.",
id='admin.E404',
)
]
self.assertEqual(admin.checks.check_dependencies(), expected)
# The first error doesn't happen if
# 'django.contrib.auth.backends.ModelBackend' isn't in
# AUTHENTICATION_BACKENDS.
with self.settings(AUTHENTICATION_BACKENDS=[]):
self.assertEqual(admin.checks.check_dependencies(), expected[1:])

@override_settings(
TEMPLATES=[
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [],
'APP_DIRS': True,
},
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
],
)
def test_several_templates_backends(self):
self.assertEqual(admin.checks.check_dependencies(), [])

@override_settings(MIDDLEWARE=[])
def test_middleware_dependencies(self):
errors = admin.checks.check_dependencies()
expected = [
checks.Error(
"'django.contrib.auth.context_processors.auth' must be in "
"TEMPLATES in order to use the admin application.",
id="admin.E402",
"'django.contrib.auth.middleware.AuthenticationMiddleware' "
"must be in MIDDLEWARE in order to use the admin application.",
id='admin.E408',
),
checks.Error(
"'django.contrib.messages.middleware.MessageMiddleware' "
"must be in MIDDLEWARE in order to use the admin application.",
id='admin.E409',
)
]
self.assertEqual(errors, expected)
Expand Down
21 changes: 20 additions & 1 deletion tests/admin_scripts/tests.py
Expand Up @@ -1172,9 +1172,28 @@ def test_complex_app(self):
'django.contrib.admin.apps.SimpleAdminConfig',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.messages',
'django.contrib.sessions',
],
sdict={
'DEBUG': True
'DEBUG': True,
'MIDDLEWARE': [
'django.contrib.messages.middleware.MessageMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
],
'TEMPLATES': [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
],
}
)
args = ['check']
Expand Down