From 1c2d0fdf3eb903d2323b39e0ca596e5322922a87 Mon Sep 17 00:00:00 2001
From: Jon Dufresne <jon.dufresne@gmail.com>
Date: Thu, 14 May 2020 10:27:35 +0200
Subject: [PATCH] [3.1.x] Fixed #31575 -- Added system check for admin sidebar
 request context processor dependency.

Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>

Backport of d522b51c401429c169d88742178a9b3777903d9e from master
---
 django/contrib/admin/checks.py        | 10 ++++++++++
 docs/ref/checks.txt                   |  3 +++
 tests/admin_checks/tests.py           | 12 +++++++++++-
 tests/admin_scripts/tests.py          |  1 +
 tests/admin_views/test_nav_sidebar.py | 26 ++++++++++++++++++++++++--
 tests/admin_views/tests.py            |  2 ++
 tests/auth_tests/settings.py          |  1 +
 7 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py
index 533e66a19f..5e4b785c33 100644
--- a/django/contrib/admin/checks.py
+++ b/django/contrib/admin/checks.py
@@ -59,6 +59,7 @@ def check_dependencies(**kwargs):
     """
     Check that the admin's dependencies are correctly installed.
     """
+    from django.contrib.admin.sites import all_sites
     if not apps.is_installed('django.contrib.admin'):
         return []
     errors = []
@@ -105,6 +106,15 @@ def check_dependencies(**kwargs):
                 "the admin application.",
                 id='admin.E404',
             ))
+        sidebar_enabled = any(site.enable_nav_sidebar for site in all_sites)
+        if (sidebar_enabled and 'django.template.context_processors.request'
+                not in django_templates_instance.context_processors):
+            errors.append(checks.Warning(
+                "'django.template.context_processors.request' must be enabled "
+                "in DjangoTemplates (TEMPLATES) in order to use the admin "
+                "navigation sidebar.",
+                id='admin.W411',
+            ))
 
     if not _contains_subclass('django.contrib.auth.middleware.AuthenticationMiddleware', settings.MIDDLEWARE):
         errors.append(checks.Error(
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 5d10482b0b..1259a4a285 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -734,6 +734,9 @@ The following checks are performed on the default
   must be in :setting:`MIDDLEWARE` in order to use the admin application.
 * **admin.E410**: :class:`django.contrib.sessions.middleware.SessionMiddleware`
   must be in :setting:`MIDDLEWARE` in order to use the admin application.
+* **admin.W411**: ``django.template.context_processors.request`` must be
+  enabled in :class:`~django.template.backends.django.DjangoTemplates`
+  (:setting:`TEMPLATES`) in order to use the admin navigation sidebar.
 
 ``auth``
 --------
diff --git a/tests/admin_checks/tests.py b/tests/admin_checks/tests.py
index 2a9643cd95..52f2188c9f 100644
--- a/tests/admin_checks/tests.py
+++ b/tests/admin_checks/tests.py
@@ -134,6 +134,12 @@ class SystemChecksTestCase(SimpleTestCase):
                 "be enabled in DjangoTemplates (TEMPLATES) in order to use "
                 "the admin application.",
                 id='admin.E404',
+            ),
+            checks.Warning(
+                "'django.template.context_processors.request' must be enabled "
+                "in DjangoTemplates (TEMPLATES) in order to use the admin "
+                "navigation sidebar.",
+                id='admin.W411',
             )
         ]
         self.assertEqual(admin.checks.check_dependencies(), expected)
@@ -150,7 +156,10 @@ class SystemChecksTestCase(SimpleTestCase):
             'DIRS': [],
             'APP_DIRS': True,
             'OPTIONS': {
-                'context_processors': ['django.contrib.messages.context_processors.messages'],
+                'context_processors': [
+                    'django.template.context_processors.request',
+                    'django.contrib.messages.context_processors.messages',
+                ],
             },
         }],
     )
@@ -177,6 +186,7 @@ class SystemChecksTestCase(SimpleTestCase):
                 'APP_DIRS': True,
                 'OPTIONS': {
                     'context_processors': [
+                        'django.template.context_processors.request',
                         'django.contrib.auth.context_processors.auth',
                         'django.contrib.messages.context_processors.messages',
                     ],
diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py
index d0af160c5f..fac1c99c97 100644
--- a/tests/admin_scripts/tests.py
+++ b/tests/admin_scripts/tests.py
@@ -1124,6 +1124,7 @@ class ManageCheck(AdminScriptTestCase):
                         'APP_DIRS': True,
                         'OPTIONS': {
                             'context_processors': [
+                                'django.template.context_processors.request',
                                 'django.contrib.auth.context_processors.auth',
                                 'django.contrib.messages.context_processors.messages',
                             ],
diff --git a/tests/admin_views/test_nav_sidebar.py b/tests/admin_views/test_nav_sidebar.py
index 2225376cd5..9d52c541c0 100644
--- a/tests/admin_views/test_nav_sidebar.py
+++ b/tests/admin_views/test_nav_sidebar.py
@@ -51,9 +51,31 @@ class AdminSidebarTests(TestCase):
         self.assertNotContains(response, '<nav class="sticky" id="nav-sidebar">')
 
     def test_sidebar_aria_current_page(self):
-        response = self.client.get(reverse('test_with_sidebar:auth_user_changelist'))
+        url = reverse('test_with_sidebar:auth_user_changelist')
+        response = self.client.get(url)
         self.assertContains(response, '<nav class="sticky" id="nav-sidebar">')
-        self.assertContains(response, 'aria-current="page">Users</a>')
+        self.assertContains(response, '<a href="%s" aria-current="page">Users</a>' % url)
+
+    @override_settings(
+        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',
+                ],
+            },
+        }]
+    )
+    def test_sidebar_aria_current_page_missing_without_request_context_processor(self):
+        url = reverse('test_with_sidebar:auth_user_changelist')
+        response = self.client.get(url)
+        self.assertContains(response, '<nav class="sticky" id="nav-sidebar">')
+        # Does not include aria-current attribute.
+        self.assertContains(response, '<a href="%s">Users</a>' % url)
+        self.assertNotContains(response, 'aria-current')
 
 
 @override_settings(ROOT_URLCONF='admin_views.test_nav_sidebar')
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 03ec445eeb..eb6f009f03 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -1425,6 +1425,7 @@ def get_perm(Model, codename):
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [
+                'django.template.context_processors.request',
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
             ],
@@ -2424,6 +2425,7 @@ class AdminViewPermissionsTest(TestCase):
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [
+                'django.template.context_processors.request',
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
             ],
diff --git a/tests/auth_tests/settings.py b/tests/auth_tests/settings.py
index 9fd71dfe87..5de7f3be45 100644
--- a/tests/auth_tests/settings.py
+++ b/tests/auth_tests/settings.py
@@ -11,6 +11,7 @@ AUTH_TEMPLATES = [{
     'APP_DIRS': True,
     'OPTIONS': {
         'context_processors': [
+            'django.template.context_processors.request',
             'django.contrib.auth.context_processors.auth',
             'django.contrib.messages.context_processors.messages',
         ],