From 4c4536f7442a3ed1218f5b1dc791295d65c24f4c Mon Sep 17 00:00:00 2001
From: Bruno Alla <alla.brunoo@gmail.com>
Date: Mon, 17 Jul 2023 13:41:33 +0100
Subject: [PATCH] Refs #34712 -- Added system check for staticfiles storage in
 STORAGES setting.

Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Co-authored-by: Natalia Bidart <124304+nessita@users.noreply.github.com>
---
 django/contrib/staticfiles/apps.py     |  3 +-
 django/contrib/staticfiles/checks.py   | 15 ++++++++
 docs/ref/checks.txt                    |  2 +
 tests/staticfiles_tests/test_checks.py | 53 ++++++++++++++++++++++++--
 4 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/django/contrib/staticfiles/apps.py b/django/contrib/staticfiles/apps.py
index 67acf042aa..08d827c5cd 100644
--- a/django/contrib/staticfiles/apps.py
+++ b/django/contrib/staticfiles/apps.py
@@ -1,5 +1,5 @@
 from django.apps import AppConfig
-from django.contrib.staticfiles.checks import check_finders
+from django.contrib.staticfiles.checks import check_finders, check_storages
 from django.core import checks
 from django.utils.translation import gettext_lazy as _
 
@@ -11,3 +11,4 @@ class StaticFilesConfig(AppConfig):
 
     def ready(self):
         checks.register(check_finders, checks.Tags.staticfiles)
+        checks.register(check_storages, checks.Tags.staticfiles)
diff --git a/django/contrib/staticfiles/checks.py b/django/contrib/staticfiles/checks.py
index fb57bf726d..06b87c281c 100644
--- a/django/contrib/staticfiles/checks.py
+++ b/django/contrib/staticfiles/checks.py
@@ -1,4 +1,11 @@
+from django.conf import STATICFILES_STORAGE_ALIAS, settings
 from django.contrib.staticfiles.finders import get_finders
+from django.core.checks import Error
+
+E005 = Error(
+    f"The STORAGES setting must define a '{STATICFILES_STORAGE_ALIAS}' storage.",
+    id="staticfiles.E005",
+)
 
 
 def check_finders(app_configs=None, **kwargs):
@@ -12,3 +19,11 @@ def check_finders(app_configs=None, **kwargs):
         else:
             errors.extend(finder_errors)
     return errors
+
+
+def check_storages(app_configs=None, **kwargs):
+    """Ensure staticfiles is defined in STORAGES setting."""
+    errors = []
+    if STATICFILES_STORAGE_ALIAS not in settings.STORAGES:
+        errors.append(E005)
+    return errors
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index cd539aa964..b8789ecf6f 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -911,3 +911,5 @@ configured:
   :setting:`STATICFILES_DIRS` setting must not end with a slash.
 * **staticfiles.W004**: The directory ``<directory>`` in the
   :setting:`STATICFILES_DIRS` does not exist.
+* **staticfiles.E005**: The :setting:`STORAGES` setting must define a
+  ``staticfiles`` storage.
diff --git a/tests/staticfiles_tests/test_checks.py b/tests/staticfiles_tests/test_checks.py
index a8c6b78a96..14f72ef357 100644
--- a/tests/staticfiles_tests/test_checks.py
+++ b/tests/staticfiles_tests/test_checks.py
@@ -1,11 +1,11 @@
 from pathlib import Path
 from unittest import mock
 
-from django.conf import settings
-from django.contrib.staticfiles.checks import check_finders
+from django.conf import DEFAULT_STORAGE_ALIAS, STATICFILES_STORAGE_ALIAS, settings
+from django.contrib.staticfiles.checks import E005, check_finders, check_storages
 from django.contrib.staticfiles.finders import BaseFinder, get_finder
 from django.core.checks import Error, Warning
-from django.test import override_settings
+from django.test import SimpleTestCase, override_settings
 
 from .cases import CollectionTestCase
 from .settings import TEST_ROOT
@@ -132,3 +132,50 @@ class FindersCheckTests(CollectionTestCase):
             # Nonexistent directories are skipped.
             finder = get_finder("django.contrib.staticfiles.finders.FileSystemFinder")
             self.assertEqual(list(finder.list(None)), [])
+
+
+class StoragesCheckTests(SimpleTestCase):
+    @override_settings(STORAGES={})
+    def test_error_empty_storages(self):
+        # DEFAULT_STORAGE_ALIAS and STATICFILES_STORAGE_ALIAS need to be
+        # popped from STORAGES since UserSettingsHolder has code to maintain
+        # backward compatibility until 5.1 is out.
+        settings.STORAGES.clear()  # RemovedInDjango51Warning
+        assert settings.STORAGES == {}  # RemovedInDjango51Warning
+        errors = check_storages(None)
+        self.assertEqual(errors, [E005])
+
+    @override_settings(
+        STORAGES={
+            DEFAULT_STORAGE_ALIAS: {
+                "BACKEND": "django.core.files.storage.FileSystemStorage",
+            },
+            "example": {
+                "BACKEND": "ignore.me",
+            },
+        }
+    )
+    def test_error_missing_staticfiles(self):
+        # Check out the previous comment about UserSettingsHolder compat code.
+        settings.STORAGES.pop(STATICFILES_STORAGE_ALIAS)  # RemovedInDjango51Warning
+        assert (
+            STATICFILES_STORAGE_ALIAS not in settings.STORAGES
+        )  # RemovedInDjango51Warning
+        errors = check_storages(None)
+        self.assertEqual(errors, [E005])
+
+    @override_settings(
+        STORAGES={
+            STATICFILES_STORAGE_ALIAS: {
+                "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
+            },
+        }
+    )
+    def test_staticfiles_no_errors(self):
+        # Check out the previous comment about UserSettingsHolder compat code.
+        settings.STORAGES.pop(DEFAULT_STORAGE_ALIAS)  # RemovedInDjango51Warning
+        assert (
+            DEFAULT_STORAGE_ALIAS not in settings.STORAGES
+        )  # RemovedInDjango51Warning
+        errors = check_storages(None)
+        self.assertEqual(errors, [])