Browse Source

Refs #26029 -- Deprecated DEFAULT_FILE_STORAGE and STATICFILES_STORAGE settings.

Jarosław Wygoda 2 years ago
parent
commit
32940d390a

+ 61 - 1
django/conf/__init__.py

@@ -16,10 +16,12 @@ from pathlib import Path
 import django
 import django
 from django.conf import global_settings
 from django.conf import global_settings
 from django.core.exceptions import ImproperlyConfigured
 from django.core.exceptions import ImproperlyConfigured
-from django.utils.deprecation import RemovedInDjango50Warning
+from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning
 from django.utils.functional import LazyObject, empty
 from django.utils.functional import LazyObject, empty
 
 
 ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
 ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
+DEFAULT_STORAGE_ALIAS = "default"
+STATICFILES_STORAGE_ALIAS = "staticfiles"
 
 
 # RemovedInDjango50Warning
 # RemovedInDjango50Warning
 USE_DEPRECATED_PYTZ_DEPRECATED_MSG = (
 USE_DEPRECATED_PYTZ_DEPRECATED_MSG = (
@@ -39,6 +41,14 @@ CSRF_COOKIE_MASKED_DEPRECATED_MSG = (
     "it will be removed in Django 5.0."
     "it will be removed in Django 5.0."
 )
 )
 
 
+DEFAULT_FILE_STORAGE_DEPRECATED_MSG = (
+    "The DEFAULT_FILE_STORAGE setting is deprecated. Use STORAGES instead."
+)
+
+STATICFILES_STORAGE_DEPRECATED_MSG = (
+    "The STATICFILES_STORAGE setting is deprecated. Use STORAGES instead."
+)
+
 
 
 class SettingsReference(str):
 class SettingsReference(str):
     """
     """
@@ -177,6 +187,22 @@ class LazySettings(LazyObject):
         # paths.
         # paths.
         return self.__getattr__("USE_L10N")
         return self.__getattr__("USE_L10N")
 
 
+    # RemovedInDjango51Warning.
+    @property
+    def DEFAULT_FILE_STORAGE(self):
+        self._show_deprecation_warning(
+            DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
+        )
+        return self.__getattr__("DEFAULT_FILE_STORAGE")
+
+    # RemovedInDjango51Warning.
+    @property
+    def STATICFILES_STORAGE(self):
+        self._show_deprecation_warning(
+            STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
+        )
+        return self.__getattr__("STATICFILES_STORAGE")
+
 
 
 class Settings:
 class Settings:
     def __init__(self, settings_module):
     def __init__(self, settings_module):
@@ -240,6 +266,20 @@ class Settings:
         if self.is_overridden("USE_L10N"):
         if self.is_overridden("USE_L10N"):
             warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
             warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
 
 
+        if self.is_overridden("DEFAULT_FILE_STORAGE"):
+            if self.is_overridden("STORAGES"):
+                raise ImproperlyConfigured(
+                    "DEFAULT_FILE_STORAGE/STORAGES are mutually exclusive."
+                )
+            warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
+
+        if self.is_overridden("STATICFILES_STORAGE"):
+            if self.is_overridden("STORAGES"):
+                raise ImproperlyConfigured(
+                    "STATICFILES_STORAGE/STORAGES are mutually exclusive."
+                )
+            warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
+
     def is_overridden(self, setting):
     def is_overridden(self, setting):
         return setting in self._explicit_settings
         return setting in self._explicit_settings
 
 
@@ -276,9 +316,29 @@ class UserSettingsHolder:
             warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
             warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
         if name == "CSRF_COOKIE_MASKED":
         if name == "CSRF_COOKIE_MASKED":
             warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)
             warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)
+        if name == "DEFAULT_FILE_STORAGE":
+            self.STORAGES[DEFAULT_STORAGE_ALIAS] = {
+                "BACKEND": self.DEFAULT_FILE_STORAGE
+            }
+            warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
+        if name == "STATICFILES_STORAGE":
+            self.STORAGES[STATICFILES_STORAGE_ALIAS] = {
+                "BACKEND": self.STATICFILES_STORAGE
+            }
+            warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
         super().__setattr__(name, value)
         super().__setattr__(name, value)
         if name == "USE_DEPRECATED_PYTZ":
         if name == "USE_DEPRECATED_PYTZ":
             warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
             warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
+        # RemovedInDjango51Warning.
+        if name == "STORAGES":
+            self.STORAGES.setdefault(
+                DEFAULT_STORAGE_ALIAS,
+                {"BACKEND": "django.core.files.storage.FileSystemStorage"},
+            )
+            self.STORAGES.setdefault(
+                STATICFILES_STORAGE_ALIAS,
+                {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"},
+            )
 
 
     def __delattr__(self, name):
     def __delattr__(self, name):
         self._deleted.add(name)
         self._deleted.add(name)

+ 8 - 1
django/conf/global_settings.py

@@ -280,7 +280,14 @@ SECRET_KEY_FALLBACKS = []
 # Default file storage mechanism that holds media.
 # Default file storage mechanism that holds media.
 DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
 DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
 
 
-STORAGES = {}
+STORAGES = {
+    "default": {
+        "BACKEND": "django.core.files.storage.FileSystemStorage",
+    },
+    "staticfiles": {
+        "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
+    },
+}
 
 
 # Absolute filesystem path to the directory that will hold user-uploaded files.
 # Absolute filesystem path to the directory that will hold user-uploaded files.
 # Example: "/var/www/example.com/media/"
 # Example: "/var/www/example.com/media/"

+ 3 - 3
django/contrib/staticfiles/storage.py

@@ -4,11 +4,11 @@ import posixpath
 import re
 import re
 from urllib.parse import unquote, urldefrag, urlsplit, urlunsplit
 from urllib.parse import unquote, urldefrag, urlsplit, urlunsplit
 
 
-from django.conf import settings
+from django.conf import STATICFILES_STORAGE_ALIAS, settings
 from django.contrib.staticfiles.utils import check_settings, matches_patterns
 from django.contrib.staticfiles.utils import check_settings, matches_patterns
 from django.core.exceptions import ImproperlyConfigured
 from django.core.exceptions import ImproperlyConfigured
 from django.core.files.base import ContentFile
 from django.core.files.base import ContentFile
-from django.core.files.storage import FileSystemStorage, get_storage_class
+from django.core.files.storage import FileSystemStorage, storages
 from django.utils.crypto import md5
 from django.utils.crypto import md5
 from django.utils.functional import LazyObject
 from django.utils.functional import LazyObject
 
 
@@ -526,7 +526,7 @@ class ManifestStaticFilesStorage(ManifestFilesMixin, StaticFilesStorage):
 
 
 class ConfiguredStorage(LazyObject):
 class ConfiguredStorage(LazyObject):
     def _setup(self):
     def _setup(self):
-        self._wrapped = get_storage_class(settings.STATICFILES_STORAGE)()
+        self._wrapped = storages[STATICFILES_STORAGE_ALIAS]
 
 
 
 
 staticfiles_storage = ConfiguredStorage()
 staticfiles_storage = ConfiguredStorage()

+ 11 - 2
django/core/files/storage/__init__.py

@@ -1,4 +1,7 @@
-from django.conf import settings
+import warnings
+
+from django.conf import DEFAULT_STORAGE_ALIAS, settings
+from django.utils.deprecation import RemovedInDjango51Warning
 from django.utils.functional import LazyObject
 from django.utils.functional import LazyObject
 from django.utils.module_loading import import_string
 from django.utils.module_loading import import_string
 
 
@@ -19,14 +22,20 @@ __all__ = (
     "storages",
     "storages",
 )
 )
 
 
+GET_STORAGE_CLASS_DEPRECATED_MSG = (
+    "django.core.files.storage.get_storage_class is deprecated in favor of "
+    "using django.core.files.storage.storages."
+)
+
 
 
 def get_storage_class(import_path=None):
 def get_storage_class(import_path=None):
+    warnings.warn(GET_STORAGE_CLASS_DEPRECATED_MSG, RemovedInDjango51Warning)
     return import_string(import_path or settings.DEFAULT_FILE_STORAGE)
     return import_string(import_path or settings.DEFAULT_FILE_STORAGE)
 
 
 
 
 class DefaultStorage(LazyObject):
 class DefaultStorage(LazyObject):
     def _setup(self):
     def _setup(self):
-        self._wrapped = get_storage_class()()
+        self._wrapped = storages[DEFAULT_STORAGE_ALIAS]
 
 
 
 
 storages = StorageHandler()
 storages = StorageHandler()

+ 10 - 1
django/core/files/storage/handler.py

@@ -1,4 +1,4 @@
-from django.conf import settings
+from django.conf import DEFAULT_STORAGE_ALIAS, STATICFILES_STORAGE_ALIAS, settings
 from django.core.exceptions import ImproperlyConfigured
 from django.core.exceptions import ImproperlyConfigured
 from django.utils.functional import cached_property
 from django.utils.functional import cached_property
 from django.utils.module_loading import import_string
 from django.utils.module_loading import import_string
@@ -19,6 +19,15 @@ class StorageHandler:
     def backends(self):
     def backends(self):
         if self._backends is None:
         if self._backends is None:
             self._backends = settings.STORAGES.copy()
             self._backends = settings.STORAGES.copy()
+            # RemovedInDjango51Warning.
+            if settings.is_overridden("DEFAULT_FILE_STORAGE"):
+                self._backends[DEFAULT_STORAGE_ALIAS] = {
+                    "BACKEND": settings.DEFAULT_FILE_STORAGE
+                }
+            if settings.is_overridden("STATICFILES_STORAGE"):
+                self._backends[STATICFILES_STORAGE_ALIAS] = {
+                    "BACKEND": settings.STATICFILES_STORAGE
+                }
         return self._backends
         return self._backends
 
 
     def __getitem__(self, alias):
     def __getitem__(self, alias):

+ 25 - 2
django/test/signals.py

@@ -13,6 +13,7 @@ from django.dispatch import Signal, receiver
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.formats import FORMAT_SETTINGS, reset_format_cache
 from django.utils.formats import FORMAT_SETTINGS, reset_format_cache
 from django.utils.functional import empty
 from django.utils.functional import empty
+from django.utils.module_loading import import_string
 
 
 template_rendered = Signal()
 template_rendered = Signal()
 
 
@@ -113,7 +114,8 @@ def reset_template_engines(*, setting, **kwargs):
 
 
 @receiver(setting_changed)
 @receiver(setting_changed)
 def storages_changed(*, setting, **kwargs):
 def storages_changed(*, setting, **kwargs):
-    from django.core.files.storage import storages
+    from django.contrib.staticfiles.storage import staticfiles_storage
+    from django.core.files.storage import default_storage, storages
 
 
     if setting in (
     if setting in (
         "STORAGES",
         "STORAGES",
@@ -127,6 +129,9 @@ def storages_changed(*, setting, **kwargs):
         storages._backends = None
         storages._backends = None
         storages._storages = {}
         storages._storages = {}
 
 
+        default_storage._wrapped = empty
+        staticfiles_storage._wrapped = empty
+
 
 
 @receiver(setting_changed)
 @receiver(setting_changed)
 def clear_serializers_cache(*, setting, **kwargs):
 def clear_serializers_cache(*, setting, **kwargs):
@@ -156,11 +161,18 @@ def localize_settings_changed(*, setting, **kwargs):
         reset_format_cache()
         reset_format_cache()
 
 
 
 
+# RemovedInDjango51Warning.
 @receiver(setting_changed)
 @receiver(setting_changed)
 def file_storage_changed(*, setting, **kwargs):
 def file_storage_changed(*, setting, **kwargs):
     if setting == "DEFAULT_FILE_STORAGE":
     if setting == "DEFAULT_FILE_STORAGE":
-        from django.core.files.storage import default_storage
+        from django.conf import DEFAULT_STORAGE_ALIAS
+        from django.core.files.storage import default_storage, storages
 
 
+        try:
+            del storages.backends
+        except AttributeError:
+            pass
+        storages._storages[DEFAULT_STORAGE_ALIAS] = import_string(kwargs["value"])()
         default_storage._wrapped = empty
         default_storage._wrapped = empty
 
 
 
 
@@ -195,6 +207,17 @@ def static_storage_changed(*, setting, **kwargs):
 
 
         staticfiles_storage._wrapped = empty
         staticfiles_storage._wrapped = empty
 
 
+    # RemovedInDjango51Warning.
+    if setting == "STATICFILES_STORAGE":
+        from django.conf import STATICFILES_STORAGE_ALIAS
+        from django.core.files.storage import storages
+
+        try:
+            del storages.backends
+        except AttributeError:
+            pass
+        storages._storages[STATICFILES_STORAGE_ALIAS] = import_string(kwargs["value"])()
+
 
 
 @receiver(setting_changed)
 @receiver(setting_changed)
 def static_finders_changed(*, setting, **kwargs):
 def static_finders_changed(*, setting, **kwargs):

+ 12 - 5
docs/howto/static-files/deployment.txt

@@ -15,8 +15,8 @@ Serving static files in production
 The basic outline of putting static files into production consists of two
 The basic outline of putting static files into production consists of two
 steps: run the :djadmin:`collectstatic` command when static files change, then
 steps: run the :djadmin:`collectstatic` command when static files change, then
 arrange for the collected static files directory (:setting:`STATIC_ROOT`) to be
 arrange for the collected static files directory (:setting:`STATIC_ROOT`) to be
-moved to the static file server and served. Depending on
-:setting:`STATICFILES_STORAGE`, files may need to be moved to a new location
+moved to the static file server and served. Depending the ``staticfiles``
+:setting:`STORAGES` alias, files may need to be moved to a new location
 manually or the :func:`post_process
 manually or the :func:`post_process
 <django.contrib.staticfiles.storage.StaticFilesStorage.post_process>` method of
 <django.contrib.staticfiles.storage.StaticFilesStorage.post_process>` method of
 the ``Storage`` class might take care of that.
 the ``Storage`` class might take care of that.
@@ -85,17 +85,20 @@ There's any number of ways you might do this, but if the provider has an API,
 you can use a :doc:`custom file storage backend </howto/custom-file-storage>`
 you can use a :doc:`custom file storage backend </howto/custom-file-storage>`
 to integrate the CDN with your Django project. If you've written or are using a
 to integrate the CDN with your Django project. If you've written or are using a
 3rd party custom storage backend, you can tell :djadmin:`collectstatic` to use
 3rd party custom storage backend, you can tell :djadmin:`collectstatic` to use
-it by setting :setting:`STATICFILES_STORAGE` to the storage engine.
+it by setting ``staticfiles`` in :setting:`STORAGES`.
 
 
 For example, if you've written an S3 storage backend in
 For example, if you've written an S3 storage backend in
 ``myproject.storage.S3Storage`` you could use it with::
 ``myproject.storage.S3Storage`` you could use it with::
 
 
-    STATICFILES_STORAGE = 'myproject.storage.S3Storage'
+    STORAGES = {
+        # ...
+        "staticfiles": {"BACKEND": "myproject.storage.S3Storage"}
+    }
 
 
 Once that's done, all you have to do is run :djadmin:`collectstatic` and your
 Once that's done, all you have to do is run :djadmin:`collectstatic` and your
 static files would be pushed through your storage package up to S3. If you
 static files would be pushed through your storage package up to S3. If you
 later needed to switch to a different storage provider, you may only have to
 later needed to switch to a different storage provider, you may only have to
-change your :setting:`STATICFILES_STORAGE` setting.
+change ``staticfiles`` in the :setting:`STORAGES` setting.
 
 
 For details on how you'd write one of these backends, see
 For details on how you'd write one of these backends, see
 :doc:`/howto/custom-file-storage`. There are 3rd party apps available that
 :doc:`/howto/custom-file-storage`. There are 3rd party apps available that
@@ -103,6 +106,10 @@ provide storage backends for many common file storage APIs. A good starting
 point is the `overview at djangopackages.org
 point is the `overview at djangopackages.org
 <https://djangopackages.org/grids/g/storage-backends/>`_.
 <https://djangopackages.org/grids/g/storage-backends/>`_.
 
 
+.. versionchanged:: 4.2
+
+    The :setting:`STORAGES` setting was added.
+
 Learn more
 Learn more
 ==========
 ==========
 
 

+ 2 - 1
docs/howto/static-files/index.txt

@@ -19,7 +19,8 @@ Configuring static files
       STATIC_URL = 'static/'
       STATIC_URL = 'static/'
 
 
 #. In your templates, use the :ttag:`static` template tag to build the URL for
 #. In your templates, use the :ttag:`static` template tag to build the URL for
-   the given relative path using the configured :setting:`STATICFILES_STORAGE`.
+   the given relative path using the configured ``staticfiles``
+   :setting:`STORAGES` alias.
 
 
    .. _staticfiles-in-templates:
    .. _staticfiles-in-templates:
 
 

+ 6 - 0
docs/internals/deprecation.txt

@@ -45,6 +45,12 @@ details on these changes.
 * Support for passing positional arguments to ``Signer`` and
 * Support for passing positional arguments to ``Signer`` and
   ``TimestampSigner`` will be removed.
   ``TimestampSigner`` will be removed.
 
 
+* The ``DEFAULT_FILE_STORAGE`` and ``STATICFILES_STORAGE`` settings will be
+  removed.
+
+* The ``django.core.files.storage.get_storage_class()`` function will be
+  removed.
+
 .. _deprecation-removed-in-5.0:
 .. _deprecation-removed-in-5.0:
 
 
 5.0
 5.0

+ 18 - 12
docs/ref/contrib/staticfiles.txt

@@ -60,11 +60,12 @@ specified by the :setting:`INSTALLED_APPS` setting.
 
 
 The :djadmin:`collectstatic` management command calls the
 The :djadmin:`collectstatic` management command calls the
 :meth:`~django.contrib.staticfiles.storage.StaticFilesStorage.post_process`
 :meth:`~django.contrib.staticfiles.storage.StaticFilesStorage.post_process`
-method of the :setting:`STATICFILES_STORAGE` after each run and passes
-a list of paths that have been found by the management command. It also
-receives all command line options of :djadmin:`collectstatic`. This is used
-by the :class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`
-by default.
+method of the ``staticfiles`` storage backend from :setting:`STORAGES` after
+each run and passes a list of paths that have been found by the management
+command. It also receives all command line options of :djadmin:`collectstatic`.
+This is used by the
+:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage` by
+default.
 
 
 By default, collected files receive permissions from
 By default, collected files receive permissions from
 :setting:`FILE_UPLOAD_PERMISSIONS` and collected directories receive permissions
 :setting:`FILE_UPLOAD_PERMISSIONS` and collected directories receive permissions
@@ -82,7 +83,7 @@ respectively. For example::
             kwargs['directory_permissions_mode'] = 0o760
             kwargs['directory_permissions_mode'] = 0o760
             super().__init__(*args, **kwargs)
             super().__init__(*args, **kwargs)
 
 
-Then set the :setting:`STATICFILES_STORAGE` setting to
+Then set the ``staticfiles`` storage backend in :setting:`STORAGES` setting to
 ``'path.to.MyStaticFilesStorage'``.
 ``'path.to.MyStaticFilesStorage'``.
 
 
 Some commonly used options are:
 Some commonly used options are:
@@ -113,7 +114,8 @@ Some commonly used options are:
 
 
     Don't call the
     Don't call the
     :meth:`~django.contrib.staticfiles.storage.StaticFilesStorage.post_process`
     :meth:`~django.contrib.staticfiles.storage.StaticFilesStorage.post_process`
-    method of the configured :setting:`STATICFILES_STORAGE` storage backend.
+    method of the configured ``staticfiles`` storage backend from
+    :setting:`STORAGES`.
 
 
 .. django-admin-option:: --no-default-ignore
 .. django-admin-option:: --no-default-ignore
 
 
@@ -360,7 +362,7 @@ attribute. It defaults to 5.
 To enable the ``ManifestStaticFilesStorage`` you have to make sure the
 To enable the ``ManifestStaticFilesStorage`` you have to make sure the
 following requirements are met:
 following requirements are met:
 
 
-* the :setting:`STATICFILES_STORAGE` setting is set to
+* the ``staticfiles`` storage backend in :setting:`STORAGES` setting is set to
   ``'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'``
   ``'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'``
 * the :setting:`DEBUG` setting is set to ``False``
 * the :setting:`DEBUG` setting is set to ``False``
 * you've collected all your static files by using the
 * you've collected all your static files by using the
@@ -381,9 +383,9 @@ If a file isn't found in the ``staticfiles.json`` manifest at runtime, a
 
 
 Due to the requirement of running :djadmin:`collectstatic`, this storage
 Due to the requirement of running :djadmin:`collectstatic`, this storage
 typically shouldn't be used when running tests as ``collectstatic`` isn't run
 typically shouldn't be used when running tests as ``collectstatic`` isn't run
-as part of the normal test setup. During testing, ensure that the
-:setting:`STATICFILES_STORAGE` setting is set to something else like
-``'django.contrib.staticfiles.storage.StaticFilesStorage'`` (the default).
+as part of the normal test setup. During testing, ensure that ``staticfiles``
+storage backend in the :setting:`STORAGES` setting is set to something else
+like ``'django.contrib.staticfiles.storage.StaticFilesStorage'`` (the default).
 
 
 .. method:: storage.ManifestStaticFilesStorage.file_hash(name, content=None)
 .. method:: storage.ManifestStaticFilesStorage.file_hash(name, content=None)
 
 
@@ -434,7 +436,8 @@ files:
 - The builtin template tag :ttag:`static` which takes a path and urljoins it
 - The builtin template tag :ttag:`static` which takes a path and urljoins it
   with the static prefix :setting:`STATIC_URL`. If
   with the static prefix :setting:`STATIC_URL`. If
   ``django.contrib.staticfiles`` is installed, the tag uses the ``url()``
   ``django.contrib.staticfiles`` is installed, the tag uses the ``url()``
-  method of the :setting:`STATICFILES_STORAGE` instead.
+  method of the ``staticfiles`` storage backend from :setting:`STORAGES`
+  instead.
 
 
 - The builtin template tag :ttag:`get_static_prefix` which populates a
 - The builtin template tag :ttag:`get_static_prefix` which populates a
   template variable with the static prefix :setting:`STATIC_URL` to be
   template variable with the static prefix :setting:`STATIC_URL` to be
@@ -443,6 +446,9 @@ files:
 - The similar template tag :ttag:`get_media_prefix` which works like
 - The similar template tag :ttag:`get_media_prefix` which works like
   :ttag:`get_static_prefix` but uses :setting:`MEDIA_URL`.
   :ttag:`get_static_prefix` but uses :setting:`MEDIA_URL`.
 
 
+- The ``staticfiles`` key in :data:`django.core.files.storage.storages`
+  contains a ready-to-use instance of the staticfiles storage backend.
+
 .. _staticfiles-development-view:
 .. _staticfiles-development-view:
 
 
 Static file development view
 Static file development view

+ 13 - 8
docs/ref/files/storage.txt

@@ -18,9 +18,9 @@ Django provides convenient ways to access the default storage class:
 .. class:: DefaultStorage
 .. class:: DefaultStorage
 
 
     :class:`~django.core.files.storage.DefaultStorage` provides
     :class:`~django.core.files.storage.DefaultStorage` provides
-    lazy access to the current default storage system as defined by
-    :setting:`DEFAULT_FILE_STORAGE`. :class:`DefaultStorage` uses
-    :func:`~django.core.files.storage.get_storage_class` internally.
+    lazy access to the default storage system as defined by ``default`` key in
+    :setting:`STORAGES`. :class:`DefaultStorage` uses
+    :data:`~django.core.files.storage.storages` internally.
 
 
 .. data:: default_storage
 .. data:: default_storage
 
 
@@ -32,11 +32,16 @@ Django provides convenient ways to access the default storage class:
     Returns a class or module which implements the storage API.
     Returns a class or module which implements the storage API.
 
 
     When called without the ``import_path`` parameter ``get_storage_class``
     When called without the ``import_path`` parameter ``get_storage_class``
-    will return the current default storage system as defined by
-    :setting:`DEFAULT_FILE_STORAGE`. If ``import_path`` is provided,
-    ``get_storage_class`` will attempt to import the class or module from the
-    given path and will return it if successful. An exception will be
-    raised if the import is unsuccessful.
+    will return the default storage system as defined by ``default`` key in
+    :setting:`STORAGES`. If ``import_path`` is provided, ``get_storage_class``
+    will attempt to import the class or module from the given path and will
+    return it if successful. An exception will be raised if the import is
+    unsuccessful.
+
+    .. deprecated:: 4.2
+
+        The ``get_storage_class()`` function is deprecated. Use
+        :data:`storages` instead
 
 
 The ``FileSystemStorage`` class
 The ``FileSystemStorage`` class
 ===============================
 ===============================

+ 32 - 5
docs/ref/settings.txt

@@ -1356,6 +1356,12 @@ Default: ``'``:class:`django.core.files.storage.FileSystemStorage`\ ``'``
 Default file storage class to be used for any file-related operations that don't
 Default file storage class to be used for any file-related operations that don't
 specify a particular storage system. See :doc:`/topics/files`.
 specify a particular storage system. See :doc:`/topics/files`.
 
 
+.. deprecated:: 4.2
+
+    This setting is deprecated. Starting with Django 4.2, default file storage
+    engine can be configured with the :setting:`STORAGES` setting under the
+    ``default`` key.
+
 .. setting:: DEFAULT_FROM_EMAIL
 .. setting:: DEFAULT_FROM_EMAIL
 
 
 ``DEFAULT_FROM_EMAIL``
 ``DEFAULT_FROM_EMAIL``
@@ -2615,13 +2621,28 @@ See also the :doc:`/ref/checks` documentation.
 
 
 Default::
 Default::
 
 
-    {}
+    {
+        "default": {
+            "BACKEND": "django.core.files.storage.FileSystemStorage",
+        },
+        "staticfiles": {
+            "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
+        },
+    }
 
 
 A dictionary containing the settings for all storages to be used with Django.
 A dictionary containing the settings for all storages to be used with Django.
 It is a nested dictionary whose contents map a storage alias to a dictionary
 It is a nested dictionary whose contents map a storage alias to a dictionary
 containing the options for an individual storage.
 containing the options for an individual storage.
 
 
-Storages can have any alias you choose.
+Storages can have any alias you choose. However, there are two aliases with
+special significance:
+
+* ``default`` for :doc:`managing files </topics/files>`.
+  ``'``:class:`django.core.files.storage.FileSystemStorage`\ ``'`` is the
+  default storage engine.
+* ``staticfiles`` for :doc:`managing static files </ref/contrib/staticfiles>`.
+  ``'``:class:`django.contrib.staticfiles.storage.StaticFilesStorage`\ ``'`` is
+  the default storage engine.
 
 
 The following is an example ``settings.py`` snippet defining a custom file
 The following is an example ``settings.py`` snippet defining a custom file
 storage called ``example``::
 storage called ``example``::
@@ -3598,10 +3619,16 @@ The file storage engine to use when collecting static files with the
 :djadmin:`collectstatic` management command.
 :djadmin:`collectstatic` management command.
 
 
 A ready-to-use instance of the storage backend defined in this setting
 A ready-to-use instance of the storage backend defined in this setting
-can be found at ``django.contrib.staticfiles.storage.staticfiles_storage``.
+can be found under ``staticfiles`` key in ``django.core.files.storage.storages``.
 
 
 For an example, see :ref:`staticfiles-from-cdn`.
 For an example, see :ref:`staticfiles-from-cdn`.
 
 
+.. deprecated:: 4.2
+
+    This setting is deprecated. Starting with Django 4.2, static files storage
+    engine can be configured with the :setting:`STORAGES` setting under the
+    ``staticfiles`` key.
+
 .. setting:: STATICFILES_FINDERS
 .. setting:: STATICFILES_FINDERS
 
 
 ``STATICFILES_FINDERS``
 ``STATICFILES_FINDERS``
@@ -3627,8 +3654,8 @@ used.
 One finder is disabled by default:
 One finder is disabled by default:
 ``django.contrib.staticfiles.finders.DefaultStorageFinder``. If added to
 ``django.contrib.staticfiles.finders.DefaultStorageFinder``. If added to
 your :setting:`STATICFILES_FINDERS` setting, it will look for static files in
 your :setting:`STATICFILES_FINDERS` setting, it will look for static files in
-the default file storage as defined by the :setting:`DEFAULT_FILE_STORAGE`
-setting.
+the default file storage as defined by the ``default`` key in the
+:setting:`STORAGES` setting.
 
 
 .. note::
 .. note::
 
 

+ 1 - 1
docs/ref/templates/builtins.txt

@@ -2629,7 +2629,7 @@ A set of Django template filters useful for adding a "human touch" to data. See
 To link to static files that are saved in :setting:`STATIC_ROOT` Django ships
 To link to static files that are saved in :setting:`STATIC_ROOT` Django ships
 with a :ttag:`static` template tag. If the :mod:`django.contrib.staticfiles`
 with a :ttag:`static` template tag. If the :mod:`django.contrib.staticfiles`
 app is installed, the tag will serve files using ``url()`` method of the
 app is installed, the tag will serve files using ``url()`` method of the
-storage specified by :setting:`STATICFILES_STORAGE`. For example::
+storage specified by ``staticfiles`` in :setting:`STORAGES`. For example::
 
 
     {% load static %}
     {% load static %}
     <img src="{% static 'images/hi.jpg' %}" alt="Hi!">
     <img src="{% static 'images/hi.jpg' %}" alt="Hi!">

+ 14 - 1
docs/releases/4.2.txt

@@ -95,7 +95,12 @@ Custom file storages
 --------------------
 --------------------
 
 
 The new :setting:`STORAGES` setting allows configuring multiple custom file
 The new :setting:`STORAGES` setting allows configuring multiple custom file
-storage backends.
+storage backends. It also controls storage engines for managing
+:doc:`files </topics/files>` (the ``"defaut"`` key) and :doc:`static files
+</ref/contrib/staticfiles>` (the ``"staticfiles"`` key).
+
+The old ``DEFAULT_FILE_STORAGE`` and ``STATICFILES_STORAGE`` settings are
+deprecated as of this release.
 
 
 Minor features
 Minor features
 --------------
 --------------
@@ -674,3 +679,11 @@ Miscellaneous
 
 
 * Passing positional arguments to ``Signer`` and ``TimestampSigner`` is
 * Passing positional arguments to ``Signer`` and ``TimestampSigner`` is
   deprecated in favor of keyword-only arguments.
   deprecated in favor of keyword-only arguments.
+
+* The ``DEFAULT_FILE_STORAGE`` setting is deprecated in favor of
+  ``STORAGES["default"]``.
+
+* The ``STATICFILES_STORAGE`` setting is deprecated in favor of
+  ``STORAGES["staticfiles"]``.
+
+* The ``django.core.files.storage.get_storage_class()`` function is deprecated.

+ 4 - 3
docs/topics/files.txt

@@ -156,9 +156,10 @@ Behind the scenes, Django delegates decisions about how and where to store files
 to a file storage system. This is the object that actually understands things
 to a file storage system. This is the object that actually understands things
 like file systems, opening and reading files, etc.
 like file systems, opening and reading files, etc.
 
 
-Django's default file storage is given by the :setting:`DEFAULT_FILE_STORAGE`
-setting; if you don't explicitly provide a storage system, this is the one that
-will be used.
+Django's default file storage is
+``'``:class:`django.core.files.storage.FileSystemStorage`\ ``'``. If you don't
+explicitly provide a storage system in the ``default`` key of the
+:setting:`STORAGES` setting, this is the one that will be used.
 
 
 See below for details of the built-in default file storage system, and see
 See below for details of the built-in default file storage system, and see
 :doc:`/howto/custom-file-storage` for information on writing your own file
 :doc:`/howto/custom-file-storage` for information on writing your own file

+ 9 - 10
docs/topics/testing/tools.txt

@@ -1441,16 +1441,15 @@ when settings are changed.
 
 
 Django itself uses this signal to reset various data:
 Django itself uses this signal to reset various data:
 
 
-================================= ========================
-Overridden settings               Data reset
-================================= ========================
-USE_TZ, TIME_ZONE                 Databases timezone
-TEMPLATES                         Template engines
-SERIALIZATION_MODULES             Serializers cache
-LOCALE_PATHS, LANGUAGE_CODE       Default translation and loaded translations
-MEDIA_ROOT, DEFAULT_FILE_STORAGE  Default file storage
-STATIC_ROOT, STATIC_URL, STORAGES Storages configuration
-================================= ========================
+============================================================================ ========================
+Overridden settings                                                          Data reset
+============================================================================ ========================
+USE_TZ, TIME_ZONE                                                            Databases timezone
+TEMPLATES                                                                    Template engines
+SERIALIZATION_MODULES                                                        Serializers cache
+LOCALE_PATHS, LANGUAGE_CODE                                                  Default translation and loaded translations
+DEFAULT_FILE_STORAGE, STATICFILES_STORAGE, STATIC_ROOT, STATIC_URL, STORAGES Storages configuration
+============================================================================ ========================
 
 
 Isolating apps
 Isolating apps
 --------------
 --------------

+ 165 - 0
tests/deprecation/test_storages.py

@@ -0,0 +1,165 @@
+import sys
+from types import ModuleType
+
+from django.conf import (
+    DEFAULT_FILE_STORAGE_DEPRECATED_MSG,
+    DEFAULT_STORAGE_ALIAS,
+    STATICFILES_STORAGE_ALIAS,
+    STATICFILES_STORAGE_DEPRECATED_MSG,
+    Settings,
+    settings,
+)
+from django.contrib.staticfiles.storage import (
+    ManifestStaticFilesStorage,
+    staticfiles_storage,
+)
+from django.core.exceptions import ImproperlyConfigured
+from django.core.files.storage import Storage, StorageHandler, default_storage, storages
+from django.test import TestCase, ignore_warnings
+from django.utils.deprecation import RemovedInDjango51Warning
+
+
+class StaticfilesStorageDeprecationTests(TestCase):
+    msg = STATICFILES_STORAGE_DEPRECATED_MSG
+
+    def test_override_settings_warning(self):
+        with self.assertRaisesMessage(RemovedInDjango51Warning, self.msg):
+            with self.settings(
+                STATICFILES_STORAGE=(
+                    "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+                )
+            ):
+                pass
+
+    def test_settings_init(self):
+        settings_module = ModuleType("fake_settings_module")
+        settings_module.USE_TZ = True
+        settings_module.STATICFILES_STORAGE = (
+            "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+        )
+        sys.modules["fake_settings_module"] = settings_module
+        try:
+            with self.assertRaisesMessage(RemovedInDjango51Warning, self.msg):
+                Settings("fake_settings_module")
+        finally:
+            del sys.modules["fake_settings_module"]
+
+    def test_access_warning(self):
+        with self.assertRaisesMessage(RemovedInDjango51Warning, self.msg):
+            settings.STATICFILES_STORAGE
+        # Works a second time.
+        with self.assertRaisesMessage(RemovedInDjango51Warning, self.msg):
+            settings.STATICFILES_STORAGE
+
+    @ignore_warnings(category=RemovedInDjango51Warning)
+    def test_access(self):
+        with self.settings(
+            STATICFILES_STORAGE=(
+                "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+            )
+        ):
+            self.assertEqual(
+                settings.STATICFILES_STORAGE,
+                "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
+            )
+            # Works a second time.
+            self.assertEqual(
+                settings.STATICFILES_STORAGE,
+                "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
+            )
+
+    def test_use_both_error(self):
+        msg = "STATICFILES_STORAGE/STORAGES are mutually exclusive."
+        settings_module = ModuleType("fake_settings_module")
+        settings_module.USE_TZ = True
+        settings_module.STATICFILES_STORAGE = (
+            "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+        )
+        settings_module.STORAGES = {}
+        sys.modules["fake_settings_module"] = settings_module
+        try:
+            with self.assertRaisesMessage(ImproperlyConfigured, msg):
+                Settings("fake_settings_module")
+        finally:
+            del sys.modules["fake_settings_module"]
+
+    @ignore_warnings(category=RemovedInDjango51Warning)
+    def test_storage(self):
+        empty_storages = StorageHandler()
+        with self.settings(
+            STATICFILES_STORAGE=(
+                "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+            )
+        ):
+            self.assertIsInstance(
+                storages[STATICFILES_STORAGE_ALIAS],
+                ManifestStaticFilesStorage,
+            )
+            self.assertIsInstance(
+                empty_storages[STATICFILES_STORAGE_ALIAS],
+                ManifestStaticFilesStorage,
+            )
+            self.assertIsInstance(staticfiles_storage, ManifestStaticFilesStorage)
+
+
+class DefaultStorageDeprecationTests(TestCase):
+    msg = DEFAULT_FILE_STORAGE_DEPRECATED_MSG
+
+    def test_override_settings_warning(self):
+        with self.assertRaisesMessage(RemovedInDjango51Warning, self.msg):
+            with self.settings(
+                DEFAULT_FILE_STORAGE=("django.core.files.storage.Storage")
+            ):
+                pass
+
+    def test_settings_init(self):
+        settings_module = ModuleType("fake_settings_module")
+        settings_module.USE_TZ = True
+        settings_module.DEFAULT_FILE_STORAGE = "django.core.files.storage.Storage"
+        sys.modules["fake_settings_module"] = settings_module
+        try:
+            with self.assertRaisesMessage(RemovedInDjango51Warning, self.msg):
+                Settings("fake_settings_module")
+        finally:
+            del sys.modules["fake_settings_module"]
+
+    def test_access_warning(self):
+        with self.assertRaisesMessage(RemovedInDjango51Warning, self.msg):
+            settings.DEFAULT_FILE_STORAGE
+        # Works a second time.
+        with self.assertRaisesMessage(RemovedInDjango51Warning, self.msg):
+            settings.DEFAULT_FILE_STORAGE
+
+    @ignore_warnings(category=RemovedInDjango51Warning)
+    def test_access(self):
+        with self.settings(DEFAULT_FILE_STORAGE="django.core.files.storage.Storage"):
+            self.assertEqual(
+                settings.DEFAULT_FILE_STORAGE,
+                "django.core.files.storage.Storage",
+            )
+            # Works a second time.
+            self.assertEqual(
+                settings.DEFAULT_FILE_STORAGE,
+                "django.core.files.storage.Storage",
+            )
+
+    def test_use_both_error(self):
+        msg = "DEFAULT_FILE_STORAGE/STORAGES are mutually exclusive."
+        settings_module = ModuleType("fake_settings_module")
+        settings_module.USE_TZ = True
+        settings_module.DEFAULT_FILE_STORAGE = "django.core.files.storage.Storage"
+        settings_module.STORAGES = {}
+        sys.modules["fake_settings_module"] = settings_module
+        try:
+            with self.assertRaisesMessage(ImproperlyConfigured, msg):
+                Settings("fake_settings_module")
+        finally:
+            del sys.modules["fake_settings_module"]
+
+    @ignore_warnings(category=RemovedInDjango51Warning)
+    def test_storage(self):
+        empty_storages = StorageHandler()
+        with self.settings(DEFAULT_FILE_STORAGE="django.core.files.storage.Storage"):
+            self.assertIsInstance(storages[DEFAULT_STORAGE_ALIAS], Storage)
+            self.assertIsInstance(empty_storages[DEFAULT_STORAGE_ALIAS], Storage)
+            self.assertIsInstance(default_storage, Storage)

+ 28 - 3
tests/file_storage/tests.py

@@ -11,10 +11,15 @@ from io import StringIO
 from pathlib import Path
 from pathlib import Path
 from urllib.request import urlopen
 from urllib.request import urlopen
 
 
+from django.conf import DEFAULT_STORAGE_ALIAS, STATICFILES_STORAGE_ALIAS
 from django.core.cache import cache
 from django.core.cache import cache
 from django.core.exceptions import SuspiciousFileOperation
 from django.core.exceptions import SuspiciousFileOperation
 from django.core.files.base import ContentFile, File
 from django.core.files.base import ContentFile, File
-from django.core.files.storage import FileSystemStorage, InvalidStorageError
+from django.core.files.storage import (
+    GET_STORAGE_CLASS_DEPRECATED_MSG,
+    FileSystemStorage,
+    InvalidStorageError,
+)
 from django.core.files.storage import Storage as BaseStorage
 from django.core.files.storage import Storage as BaseStorage
 from django.core.files.storage import (
 from django.core.files.storage import (
     StorageHandler,
     StorageHandler,
@@ -30,10 +35,11 @@ from django.core.files.uploadedfile import (
 from django.db.models import FileField
 from django.db.models import FileField
 from django.db.models.fields.files import FileDescriptor
 from django.db.models.fields.files import FileDescriptor
 from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings
 from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings
-from django.test.utils import requires_tz_support
+from django.test.utils import ignore_warnings, requires_tz_support
 from django.urls import NoReverseMatch, reverse_lazy
 from django.urls import NoReverseMatch, reverse_lazy
 from django.utils import timezone
 from django.utils import timezone
 from django.utils._os import symlinks_supported
 from django.utils._os import symlinks_supported
+from django.utils.deprecation import RemovedInDjango51Warning
 
 
 from .models import Storage, callable_storage, temp_storage, temp_storage_location
 from .models import Storage, callable_storage, temp_storage, temp_storage_location
 
 
@@ -41,6 +47,7 @@ FILE_SUFFIX_REGEX = "[A-Za-z0-9]{7}"
 
 
 
 
 class GetStorageClassTests(SimpleTestCase):
 class GetStorageClassTests(SimpleTestCase):
+    @ignore_warnings(category=RemovedInDjango51Warning)
     def test_get_filesystem_storage(self):
     def test_get_filesystem_storage(self):
         """
         """
         get_storage_class returns the class for a storage backend name/path.
         get_storage_class returns the class for a storage backend name/path.
@@ -50,6 +57,7 @@ class GetStorageClassTests(SimpleTestCase):
             FileSystemStorage,
             FileSystemStorage,
         )
         )
 
 
+    @ignore_warnings(category=RemovedInDjango51Warning)
     def test_get_invalid_storage_module(self):
     def test_get_invalid_storage_module(self):
         """
         """
         get_storage_class raises an error if the requested import don't exist.
         get_storage_class raises an error if the requested import don't exist.
@@ -57,6 +65,7 @@ class GetStorageClassTests(SimpleTestCase):
         with self.assertRaisesMessage(ImportError, "No module named 'storage'"):
         with self.assertRaisesMessage(ImportError, "No module named 'storage'"):
             get_storage_class("storage.NonexistentStorage")
             get_storage_class("storage.NonexistentStorage")
 
 
+    @ignore_warnings(category=RemovedInDjango51Warning)
     def test_get_nonexistent_storage_class(self):
     def test_get_nonexistent_storage_class(self):
         """
         """
         get_storage_class raises an error if the requested class don't exist.
         get_storage_class raises an error if the requested class don't exist.
@@ -64,6 +73,7 @@ class GetStorageClassTests(SimpleTestCase):
         with self.assertRaises(ImportError):
         with self.assertRaises(ImportError):
             get_storage_class("django.core.files.storage.NonexistentStorage")
             get_storage_class("django.core.files.storage.NonexistentStorage")
 
 
+    @ignore_warnings(category=RemovedInDjango51Warning)
     def test_get_nonexistent_storage_module(self):
     def test_get_nonexistent_storage_module(self):
         """
         """
         get_storage_class raises an error if the requested module don't exist.
         get_storage_class raises an error if the requested module don't exist.
@@ -75,6 +85,11 @@ class GetStorageClassTests(SimpleTestCase):
                 "django.core.files.nonexistent_storage.NonexistentStorage"
                 "django.core.files.nonexistent_storage.NonexistentStorage"
             )
             )
 
 
+    def test_deprecation_warning(self):
+        msg = GET_STORAGE_CLASS_DEPRECATED_MSG
+        with self.assertRaisesMessage(RemovedInDjango51Warning, msg):
+            get_storage_class("django.core.files.storage.FileSystemStorage"),
+
 
 
 class FileSystemStorageTests(unittest.TestCase):
 class FileSystemStorageTests(unittest.TestCase):
     def test_deconstruction(self):
     def test_deconstruction(self):
@@ -1179,7 +1194,17 @@ class StorageHandlerTests(SimpleTestCase):
 
 
     def test_defaults(self):
     def test_defaults(self):
         storages = StorageHandler()
         storages = StorageHandler()
-        self.assertEqual(storages.backends, {})
+        self.assertEqual(
+            storages.backends,
+            {
+                DEFAULT_STORAGE_ALIAS: {
+                    "BACKEND": "django.core.files.storage.FileSystemStorage",
+                },
+                STATICFILES_STORAGE_ALIAS: {
+                    "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
+                },
+            },
+        )
 
 
     def test_nonexistent_alias(self):
     def test_nonexistent_alias(self):
         msg = "Could not find config for 'nonexistent' in settings.STORAGES."
         msg = "Could not find config for 'nonexistent' in settings.STORAGES."

+ 6 - 1
tests/file_uploads/tests.py

@@ -9,6 +9,7 @@ from io import BytesIO, StringIO
 from unittest import mock
 from unittest import mock
 from urllib.parse import quote
 from urllib.parse import quote
 
 
+from django.conf import DEFAULT_STORAGE_ALIAS
 from django.core.exceptions import SuspiciousFileOperation
 from django.core.exceptions import SuspiciousFileOperation
 from django.core.files import temp as tempfile
 from django.core.files import temp as tempfile
 from django.core.files.storage import default_storage
 from django.core.files.storage import default_storage
@@ -806,7 +807,11 @@ class DirectoryCreationTests(SimpleTestCase):
         sys.platform == "win32", "Python on Windows doesn't have working os.chmod()."
         sys.platform == "win32", "Python on Windows doesn't have working os.chmod()."
     )
     )
     @override_settings(
     @override_settings(
-        DEFAULT_FILE_STORAGE="django.core.files.storage.FileSystemStorage"
+        STORAGES={
+            DEFAULT_STORAGE_ALIAS: {
+                "BACKEND": "django.core.files.storage.FileSystemStorage",
+            }
+        }
     )
     )
     def test_readonly_root(self):
     def test_readonly_root(self):
         """Permission errors are not swallowed"""
         """Permission errors are not swallowed"""

+ 7 - 2
tests/staticfiles_tests/test_forms.py

@@ -1,5 +1,6 @@
 from urllib.parse import urljoin
 from urllib.parse import urljoin
 
 
+from django.conf import STATICFILES_STORAGE_ALIAS
 from django.contrib.staticfiles import storage
 from django.contrib.staticfiles import storage
 from django.forms import Media
 from django.forms import Media
 from django.templatetags.static import static
 from django.templatetags.static import static
@@ -12,9 +13,13 @@ class StaticTestStorage(storage.StaticFilesStorage):
 
 
 
 
 @override_settings(
 @override_settings(
-    STATIC_URL="http://media.example.com/static/",
     INSTALLED_APPS=("django.contrib.staticfiles",),
     INSTALLED_APPS=("django.contrib.staticfiles",),
-    STATICFILES_STORAGE="staticfiles_tests.test_forms.StaticTestStorage",
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "staticfiles_tests.test_forms.StaticTestStorage",
+            "OPTIONS": {"location": "http://media.example.com/static/"},
+        }
+    },
 )
 )
 class StaticFilesFormsMediaTestCase(SimpleTestCase):
 class StaticFilesFormsMediaTestCase(SimpleTestCase):
     def test_absolute_url(self):
     def test_absolute_url(self):

+ 54 - 16
tests/staticfiles_tests/test_management.py

@@ -9,7 +9,7 @@ from unittest import mock
 
 
 from admin_scripts.tests import AdminScriptTestCase
 from admin_scripts.tests import AdminScriptTestCase
 
 
-from django.conf import settings
+from django.conf import STATICFILES_STORAGE_ALIAS, settings
 from django.contrib.staticfiles import storage
 from django.contrib.staticfiles import storage
 from django.contrib.staticfiles.management.commands import collectstatic, runserver
 from django.contrib.staticfiles.management.commands import collectstatic, runserver
 from django.core.exceptions import ImproperlyConfigured
 from django.core.exceptions import ImproperlyConfigured
@@ -141,16 +141,24 @@ class TestConfiguration(StaticFilesTestCase):
         try:
         try:
             storage.staticfiles_storage._wrapped = empty
             storage.staticfiles_storage._wrapped = empty
             with self.settings(
             with self.settings(
-                STATICFILES_STORAGE=(
-                    "django.contrib.staticfiles.storage.StaticFilesStorage"
-                )
+                STORAGES={
+                    STATICFILES_STORAGE_ALIAS: {
+                        "BACKEND": (
+                            "django.contrib.staticfiles.storage.StaticFilesStorage"
+                        )
+                    }
+                }
             ):
             ):
                 command = collectstatic.Command()
                 command = collectstatic.Command()
                 self.assertTrue(command.is_local_storage())
                 self.assertTrue(command.is_local_storage())
 
 
             storage.staticfiles_storage._wrapped = empty
             storage.staticfiles_storage._wrapped = empty
             with self.settings(
             with self.settings(
-                STATICFILES_STORAGE="staticfiles_tests.storage.DummyStorage"
+                STORAGES={
+                    STATICFILES_STORAGE_ALIAS: {
+                        "BACKEND": "staticfiles_tests.storage.DummyStorage"
+                    }
+                }
             ):
             ):
                 command = collectstatic.Command()
                 command = collectstatic.Command()
                 self.assertFalse(command.is_local_storage())
                 self.assertFalse(command.is_local_storage())
@@ -241,9 +249,13 @@ class TestCollectionVerbosity(CollectionTestCase):
         self.assertIn(self.copying_msg, output)
         self.assertIn(self.copying_msg, output)
 
 
     @override_settings(
     @override_settings(
-        STATICFILES_STORAGE=(
-            "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
-        )
+        STORAGES={
+            STATICFILES_STORAGE_ALIAS: {
+                "BACKEND": (
+                    "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+                )
+            },
+        }
     )
     )
     def test_verbosity_1_with_post_process(self):
     def test_verbosity_1_with_post_process(self):
         stdout = StringIO()
         stdout = StringIO()
@@ -251,9 +263,13 @@ class TestCollectionVerbosity(CollectionTestCase):
         self.assertNotIn(self.post_process_msg, stdout.getvalue())
         self.assertNotIn(self.post_process_msg, stdout.getvalue())
 
 
     @override_settings(
     @override_settings(
-        STATICFILES_STORAGE=(
-            "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
-        )
+        STORAGES={
+            STATICFILES_STORAGE_ALIAS: {
+                "BACKEND": (
+                    "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+                )
+            },
+        }
     )
     )
     def test_verbosity_2_with_post_process(self):
     def test_verbosity_2_with_post_process(self):
         stdout = StringIO()
         stdout = StringIO()
@@ -280,7 +296,11 @@ class TestCollectionClear(CollectionTestCase):
         super().run_collectstatic(clear=True)
         super().run_collectstatic(clear=True)
 
 
     @override_settings(
     @override_settings(
-        STATICFILES_STORAGE="staticfiles_tests.storage.PathNotImplementedStorage"
+        STORAGES={
+            STATICFILES_STORAGE_ALIAS: {
+                "BACKEND": "staticfiles_tests.storage.PathNotImplementedStorage"
+            },
+        }
     )
     )
     def test_handle_path_notimplemented(self):
     def test_handle_path_notimplemented(self):
         self.run_collectstatic()
         self.run_collectstatic()
@@ -395,7 +415,11 @@ class TestCollectionDryRun(TestNoFilesCreated, CollectionTestCase):
 
 
 
 
 @override_settings(
 @override_settings(
-    STATICFILES_STORAGE="django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
+        },
+    }
 )
 )
 class TestCollectionDryRunManifestStaticFilesStorage(TestCollectionDryRun):
 class TestCollectionDryRunManifestStaticFilesStorage(TestCollectionDryRun):
     pass
     pass
@@ -518,7 +542,13 @@ class TestCollectionOverwriteWarning(CollectionTestCase):
             self.assertNotIn(self.warning_string, output)
             self.assertNotIn(self.warning_string, output)
 
 
 
 
-@override_settings(STATICFILES_STORAGE="staticfiles_tests.storage.DummyStorage")
+@override_settings(
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "staticfiles_tests.storage.DummyStorage"
+        },
+    }
+)
 class TestCollectionNonLocalStorage(TestNoFilesCreated, CollectionTestCase):
 class TestCollectionNonLocalStorage(TestNoFilesCreated, CollectionTestCase):
     """
     """
     Tests for a Storage that implements get_modified_time() but not path()
     Tests for a Storage that implements get_modified_time() but not path()
@@ -540,7 +570,11 @@ class TestCollectionNonLocalStorage(TestNoFilesCreated, CollectionTestCase):
 
 
 class TestCollectionNeverCopyStorage(CollectionTestCase):
 class TestCollectionNeverCopyStorage(CollectionTestCase):
     @override_settings(
     @override_settings(
-        STATICFILES_STORAGE="staticfiles_tests.storage.NeverCopyRemoteStorage"
+        STORAGES={
+            STATICFILES_STORAGE_ALIAS: {
+                "BACKEND": "staticfiles_tests.storage.NeverCopyRemoteStorage"
+            },
+        }
     )
     )
     def test_skips_newer_files_in_remote_storage(self):
     def test_skips_newer_files_in_remote_storage(self):
         """
         """
@@ -607,7 +641,11 @@ class TestCollectionLinks(TestDefaults, CollectionTestCase):
         self.assertFalse(os.path.lexists(broken_symlink_path))
         self.assertFalse(os.path.lexists(broken_symlink_path))
 
 
     @override_settings(
     @override_settings(
-        STATICFILES_STORAGE="staticfiles_tests.storage.PathNotImplementedStorage"
+        STORAGES={
+            STATICFILES_STORAGE_ALIAS: {
+                "BACKEND": "staticfiles_tests.storage.PathNotImplementedStorage"
+            }
+        }
     )
     )
     def test_no_remote_link(self):
     def test_no_remote_link(self):
         with self.assertRaisesMessage(
         with self.assertRaisesMessage(

+ 42 - 8
tests/staticfiles_tests/test_storage.py

@@ -8,7 +8,7 @@ from io import StringIO
 from pathlib import Path
 from pathlib import Path
 from unittest import mock
 from unittest import mock
 
 
-from django.conf import settings
+from django.conf import STATICFILES_STORAGE_ALIAS, settings
 from django.contrib.staticfiles import finders, storage
 from django.contrib.staticfiles import finders, storage
 from django.contrib.staticfiles.management.commands.collectstatic import (
 from django.contrib.staticfiles.management.commands.collectstatic import (
     Command as CollectstaticCommand,
     Command as CollectstaticCommand,
@@ -369,7 +369,13 @@ class TestHashedFiles:
         self.assertPostCondition()
         self.assertPostCondition()
 
 
 
 
-@override_settings(STATICFILES_STORAGE="staticfiles_tests.storage.ExtraPatternsStorage")
+@override_settings(
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "staticfiles_tests.storage.ExtraPatternsStorage",
+        },
+    }
+)
 class TestExtraPatternsStorage(CollectionTestCase):
 class TestExtraPatternsStorage(CollectionTestCase):
     def setUp(self):
     def setUp(self):
         storage.staticfiles_storage.hashed_files.clear()  # avoid cache interference
         storage.staticfiles_storage.hashed_files.clear()  # avoid cache interference
@@ -399,7 +405,11 @@ class TestExtraPatternsStorage(CollectionTestCase):
 
 
 
 
 @override_settings(
 @override_settings(
-    STATICFILES_STORAGE="django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
+        },
+    }
 )
 )
 class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
 class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
     """
     """
@@ -559,7 +569,13 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
         self.assertEqual(manifest_content, {"dummy.txt": "dummy.txt"})
         self.assertEqual(manifest_content, {"dummy.txt": "dummy.txt"})
 
 
 
 
-@override_settings(STATICFILES_STORAGE="staticfiles_tests.storage.NoneHashStorage")
+@override_settings(
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "staticfiles_tests.storage.NoneHashStorage",
+        },
+    }
+)
 class TestCollectionNoneHashStorage(CollectionTestCase):
 class TestCollectionNoneHashStorage(CollectionTestCase):
     hashed_file_path = hashed_file_path
     hashed_file_path = hashed_file_path
 
 
@@ -569,7 +585,11 @@ class TestCollectionNoneHashStorage(CollectionTestCase):
 
 
 
 
 @override_settings(
 @override_settings(
-    STATICFILES_STORAGE="staticfiles_tests.storage.NoPostProcessReplacedPathStorage"
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "staticfiles_tests.storage.NoPostProcessReplacedPathStorage",
+        },
+    }
 )
 )
 class TestCollectionNoPostProcessReplacedPaths(CollectionTestCase):
 class TestCollectionNoPostProcessReplacedPaths(CollectionTestCase):
     run_collectstatic_in_setUp = False
     run_collectstatic_in_setUp = False
@@ -580,7 +600,13 @@ class TestCollectionNoPostProcessReplacedPaths(CollectionTestCase):
         self.assertIn("post-processed", stdout.getvalue())
         self.assertIn("post-processed", stdout.getvalue())
 
 
 
 
-@override_settings(STATICFILES_STORAGE="staticfiles_tests.storage.SimpleStorage")
+@override_settings(
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "staticfiles_tests.storage.SimpleStorage",
+        },
+    }
+)
 class TestCollectionSimpleStorage(CollectionTestCase):
 class TestCollectionSimpleStorage(CollectionTestCase):
     hashed_file_path = hashed_file_path
     hashed_file_path = hashed_file_path
 
 
@@ -733,7 +759,11 @@ class TestStaticFilePermissions(CollectionTestCase):
     @override_settings(
     @override_settings(
         FILE_UPLOAD_PERMISSIONS=0o655,
         FILE_UPLOAD_PERMISSIONS=0o655,
         FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765,
         FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765,
-        STATICFILES_STORAGE="staticfiles_tests.test_storage.CustomStaticFilesStorage",
+        STORAGES={
+            STATICFILES_STORAGE_ALIAS: {
+                "BACKEND": "staticfiles_tests.test_storage.CustomStaticFilesStorage",
+            },
+        },
     )
     )
     def test_collect_static_files_subclass_of_static_storage(self):
     def test_collect_static_files_subclass_of_static_storage(self):
         call_command("collectstatic", **self.command_params)
         call_command("collectstatic", **self.command_params)
@@ -753,7 +783,11 @@ class TestStaticFilePermissions(CollectionTestCase):
 
 
 
 
 @override_settings(
 @override_settings(
-    STATICFILES_STORAGE="django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
+    STORAGES={
+        STATICFILES_STORAGE_ALIAS: {
+            "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
+        },
+    }
 )
 )
 class TestCollectionHashedFilesCache(CollectionTestCase):
 class TestCollectionHashedFilesCache(CollectionTestCase):
     """
     """

+ 6 - 1
tests/staticfiles_tests/test_templatetags.py

@@ -1,3 +1,4 @@
+from django.conf import STATICFILES_STORAGE_ALIAS
 from django.test import override_settings
 from django.test import override_settings
 
 
 from .cases import StaticFilesTestCase
 from .cases import StaticFilesTestCase
@@ -12,7 +13,11 @@ class TestTemplateTag(StaticFilesTestCase):
         )
         )
 
 
     @override_settings(
     @override_settings(
-        STATICFILES_STORAGE="staticfiles_tests.storage.QueryStringStorage"
+        STORAGES={
+            STATICFILES_STORAGE_ALIAS: {
+                "BACKEND": "staticfiles_tests.storage.QueryStringStorage"
+            },
+        }
     )
     )
     def test_template_tag_escapes(self):
     def test_template_tag_escapes(self):
         """
         """

+ 5 - 3
tests/test_utils/tests.py

@@ -5,7 +5,7 @@ import warnings
 from io import StringIO
 from io import StringIO
 from unittest import mock
 from unittest import mock
 
 
-from django.conf import settings
+from django.conf import STATICFILES_STORAGE_ALIAS, settings
 from django.contrib.staticfiles.finders import get_finder, get_finders
 from django.contrib.staticfiles.finders import get_finder, get_finders
 from django.contrib.staticfiles.storage import staticfiles_storage
 from django.contrib.staticfiles.storage import staticfiles_storage
 from django.core.exceptions import ImproperlyConfigured
 from django.core.exceptions import ImproperlyConfigured
@@ -2106,12 +2106,14 @@ class OverrideSettingsTests(SimpleTestCase):
 
 
     def test_override_staticfiles_storage(self):
     def test_override_staticfiles_storage(self):
         """
         """
-        Overriding the STATICFILES_STORAGE setting should be reflected in
+        Overriding the STORAGES setting should be reflected in
         the value of django.contrib.staticfiles.storage.staticfiles_storage.
         the value of django.contrib.staticfiles.storage.staticfiles_storage.
         """
         """
         new_class = "ManifestStaticFilesStorage"
         new_class = "ManifestStaticFilesStorage"
         new_storage = "django.contrib.staticfiles.storage." + new_class
         new_storage = "django.contrib.staticfiles.storage." + new_class
-        with self.settings(STATICFILES_STORAGE=new_storage):
+        with self.settings(
+            STORAGES={STATICFILES_STORAGE_ALIAS: {"BACKEND": new_storage}}
+        ):
             self.assertEqual(staticfiles_storage.__class__.__name__, new_class)
             self.assertEqual(staticfiles_storage.__class__.__name__, new_class)
 
 
     def test_override_staticfiles_finders(self):
     def test_override_staticfiles_finders(self):