diff --git a/django/contrib/admin/apps.py b/django/contrib/admin/apps.py index c965cdbc89..194ec9f89d 100644 --- a/django/contrib/admin/apps.py +++ b/django/contrib/admin/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.contrib.admin.checks import check_admin_app +from django.contrib.admin.checks import check_admin_app, check_dependencies from django.core import checks from django.utils.translation import ugettext_lazy as _ @@ -11,6 +11,7 @@ class SimpleAdminConfig(AppConfig): verbose_name = _("Administration") def ready(self): + checks.register(check_dependencies, checks.Tags.admin) checks.register(check_admin_app, checks.Tags.admin) diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py index 60bd236927..48539fe9e5 100644 --- a/django/contrib/admin/checks.py +++ b/django/contrib/admin/checks.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals from itertools import chain +from django.apps import apps +from django.conf import settings from django.contrib.admin.utils import ( NotRelationField, flatten, get_fields_from_path, ) @@ -12,6 +14,7 @@ from django.db import models from django.forms.models import ( BaseModelForm, BaseModelFormSet, _get_foreign_key, ) +from django.template.engine import Engine def check_admin_app(**kwargs): @@ -20,6 +23,46 @@ def check_admin_app(**kwargs): return system_check_errors +def check_dependencies(**kwargs): + """ + Check that the admin's dependencies are correctly installed. + """ + 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 + 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) + return errors + + class BaseModelAdminChecks(object): def check(self, admin_obj, **kwargs): diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 77da4c0fd7..ba9cbcc135 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -7,7 +7,6 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.db.models.base import ModelBase from django.http import Http404, HttpResponseRedirect -from django.template.engine import Engine from django.template.response import TemplateResponse from django.urls import NoReverseMatch, reverse from django.utils import six @@ -172,40 +171,6 @@ class AdminSite(object): """ return request.user.is_active and request.user.is_staff - def check_dependencies(self): - """ - Check that all things needed to run the admin have been correctly installed. - - The default implementation checks that admin and contenttypes apps are - installed, as well as the auth context processor. - """ - if not apps.is_installed('django.contrib.admin'): - raise ImproperlyConfigured( - "Put 'django.contrib.admin' in your INSTALLED_APPS " - "setting in order to use the admin application.") - if not apps.is_installed('django.contrib.contenttypes'): - raise ImproperlyConfigured( - "Put 'django.contrib.contenttypes' in your INSTALLED_APPS " - "setting in order to use the admin application.") - 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 - else: - if ('django.contrib.auth.context_processors.auth' - not in default_template_engine.context_processors): - raise ImproperlyConfigured( - "Enable 'django.contrib.auth.context_processors.auth' " - "in your TEMPLATES setting in order to use the admin " - "application.") - def admin_view(self, view, cacheable=False): """ Decorator to create an admin view attached to this ``AdminSite``. This @@ -257,9 +222,6 @@ class AdminSite(object): # and django.contrib.contenttypes.views imports ContentType. from django.contrib.contenttypes import views as contenttype_views - if settings.DEBUG: - self.check_dependencies() - def wrap(view, cacheable=False): def wrapper(*args, **kwargs): return self.admin_view(view, cacheable)(*args, **kwargs) diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index e9c8e45cbb..854a9ca254 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -409,6 +409,16 @@ registered as an inline on a :class:`~django.contrib.admin.ModelAdmin`. * **admin.E304**: ```` has no ``GenericForeignKey`` using content type field ```` and object ID field ````. +AdminSite +~~~~~~~~~ + +The following checks are performed on the default +:class:`~django.contrib.admin.AdminSite`: + +* **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. Auth ---- diff --git a/tests/admin_checks/tests.py b/tests/admin_checks/tests.py index 280af17e1f..56437c4a49 100644 --- a/tests/admin_checks/tests.py +++ b/tests/admin_checks/tests.py @@ -54,6 +54,44 @@ class SystemChecksTestCase(SimpleTestCase): admin.site.unregister(Song) admin.sites.system_check_errors = [] + @override_settings(INSTALLED_APPS=['django.contrib.admin']) + def test_contenttypes_dependency(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", + ) + ] + self.assertEqual(errors, expected) + + @override_settings( + INSTALLED_APPS=[ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + ], + TEMPLATES=[{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [], + }, + }], + ) + def test_auth_contextprocessor_dependency(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", + ) + ] + self.assertEqual(errors, expected) + @override_settings(DEBUG=True) def test_custom_adminsite(self): class CustomAdminSite(admin.AdminSite):