Fixed #27590 -- Allowed customizing a manifest file storage in ManifestFilesMixin.

This commit is contained in:
Jarosław Wygoda 2021-06-02 21:44:59 +02:00 committed by Mariusz Felisiak
parent 4fe3774c72
commit d3c4696596
5 changed files with 85 additions and 6 deletions

View File

@ -423,6 +423,7 @@ answer newbie questions, and generally made Django that much better:
Jan Rademaker Jan Rademaker
Jarek Głowacki <jarekwg@gmail.com> Jarek Głowacki <jarekwg@gmail.com>
Jarek Zgoda <jarek.zgoda@gmail.com> Jarek Zgoda <jarek.zgoda@gmail.com>
Jarosław Wygoda <jaroslaw@wygoda.me>
Jason Davies (Esaj) <https://www.jasondavies.com/> Jason Davies (Esaj) <https://www.jasondavies.com/>
Jason Huggins <http://www.jrandolph.com/blog/> Jason Huggins <http://www.jrandolph.com/blog/>
Jason McBrayer <http://www.carcosa.net/jason/> Jason McBrayer <http://www.carcosa.net/jason/>

View File

@ -401,13 +401,16 @@ class ManifestFilesMixin(HashedFilesMixin):
manifest_strict = True manifest_strict = True
keep_intermediate_files = False keep_intermediate_files = False
def __init__(self, *args, **kwargs): def __init__(self, *args, manifest_storage=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if manifest_storage is None:
manifest_storage = self
self.manifest_storage = manifest_storage
self.hashed_files = self.load_manifest() self.hashed_files = self.load_manifest()
def read_manifest(self): def read_manifest(self):
try: try:
with self.open(self.manifest_name) as manifest: with self.manifest_storage.open(self.manifest_name) as manifest:
return manifest.read().decode() return manifest.read().decode()
except FileNotFoundError: except FileNotFoundError:
return None return None
@ -435,10 +438,10 @@ class ManifestFilesMixin(HashedFilesMixin):
def save_manifest(self): def save_manifest(self):
payload = {'paths': self.hashed_files, 'version': self.manifest_version} payload = {'paths': self.hashed_files, 'version': self.manifest_version}
if self.exists(self.manifest_name): if self.manifest_storage.exists(self.manifest_name):
self.delete(self.manifest_name) self.manifest_storage.delete(self.manifest_name)
contents = json.dumps(payload).encode() contents = json.dumps(payload).encode()
self._save(self.manifest_name, ContentFile(contents)) self.manifest_storage._save(self.manifest_name, ContentFile(contents))
def stored_name(self, name): def stored_name(self, name):
parsed_name = urlsplit(unquote(name)) parsed_name = urlsplit(unquote(name))

View File

@ -313,6 +313,20 @@ For example, the ``'css/styles.css'`` file with this content:
@import url("../admin/css/base.27e20196a850.css"); @import url("../admin/css/base.27e20196a850.css");
You can change the location of the manifest file by using a custom
``ManifestStaticFilesStorage`` subclass that sets the ``manifest_storage``
argument. For example::
from django.conf import settings
from django.contrib.staticfiles.storage import (
ManifestStaticFilesStorage, StaticFilesStorage,
)
class MyManifestStaticFilesStorage(ManifestStaticFilesStorage):
def __init__(self, *args, **kwargs):
manifest_storage = StaticFilesStorage(location=settings.BASE_DIR)
super().__init__(*args, manifest_storage=manifest_storage, **kwargs)
.. versionchanged:: 4.0 .. versionchanged:: 4.0
Support for finding paths in the source map comments was added. Support for finding paths in the source map comments was added.
@ -320,6 +334,8 @@ For example, the ``'css/styles.css'`` file with this content:
Support for finding paths to JavaScript modules in ``import`` and Support for finding paths to JavaScript modules in ``import`` and
``export`` statements was added. ``export`` statements was added.
The ``manifest_storage`` argument was added.
.. attribute:: storage.ManifestStaticFilesStorage.max_post_process_passes .. attribute:: storage.ManifestStaticFilesStorage.max_post_process_passes
Since static files might reference other static files that need to have their Since static files might reference other static files that need to have their
@ -384,6 +400,10 @@ hashing algorithm.
Use this mixin with a custom storage to append the MD5 hash of the file's Use this mixin with a custom storage to append the MD5 hash of the file's
content to the filename as :class:`~storage.ManifestStaticFilesStorage` does. content to the filename as :class:`~storage.ManifestStaticFilesStorage` does.
.. versionchanged:: 4.0
The ``manifest_storage`` argument was added.
Finders Module Finders Module
============== ==============

View File

@ -174,6 +174,11 @@ Minor features
replaces paths to JavaScript modules in ``import`` and ``export`` statements replaces paths to JavaScript modules in ``import`` and ``export`` statements
with their hashed counterparts. with their hashed counterparts.
* The new ``manifest_storage`` argument of
:class:`~django.contrib.staticfiles.storage.ManifestFilesMixin` and
:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`
allows customizing the manifest file storage.
:mod:`django.contrib.syndication` :mod:`django.contrib.syndication`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,3 +1,4 @@
import json
import os import os
import shutil import shutil
import sys import sys
@ -13,7 +14,7 @@ from django.contrib.staticfiles.management.commands.collectstatic import (
Command as CollectstaticCommand, Command as CollectstaticCommand,
) )
from django.core.management import call_command from django.core.management import call_command
from django.test import override_settings from django.test import SimpleTestCase, override_settings
from .cases import CollectionTestCase from .cases import CollectionTestCase
from .settings import TEST_ROOT from .settings import TEST_ROOT
@ -499,6 +500,55 @@ class TestCollectionSimpleStorage(CollectionTestCase):
self.assertIn(b"other.deploy12345.css", content) self.assertIn(b"other.deploy12345.css", content)
class CustomManifestStorage(storage.ManifestStaticFilesStorage):
def __init__(self, *args, manifest_storage=None, **kwargs):
manifest_storage = storage.StaticFilesStorage(
location=kwargs.pop('manifest_location'),
)
super().__init__(*args, manifest_storage=manifest_storage, **kwargs)
class TestCustomManifestStorage(SimpleTestCase):
def setUp(self):
self.manifest_path = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, self.manifest_path)
self.staticfiles_storage = CustomManifestStorage(
manifest_location=self.manifest_path,
)
self.manifest_file = self.manifest_path / self.staticfiles_storage.manifest_name
# Manifest without paths.
self.manifest = {'version': self.staticfiles_storage.manifest_version}
with self.manifest_file.open('w') as manifest_file:
json.dump(self.manifest, manifest_file)
def test_read_manifest(self):
self.assertEqual(
self.staticfiles_storage.read_manifest(),
json.dumps(self.manifest),
)
def test_read_manifest_nonexistent(self):
os.remove(self.manifest_file)
self.assertIsNone(self.staticfiles_storage.read_manifest())
def test_save_manifest_override(self):
self.assertIs(self.manifest_file.exists(), True)
self.staticfiles_storage.save_manifest()
self.assertIs(self.manifest_file.exists(), True)
new_manifest = json.loads(self.staticfiles_storage.read_manifest())
self.assertIn('paths', new_manifest)
self.assertNotEqual(new_manifest, self.manifest)
def test_save_manifest_create(self):
os.remove(self.manifest_file)
self.staticfiles_storage.save_manifest()
self.assertIs(self.manifest_file.exists(), True)
new_manifest = json.loads(self.staticfiles_storage.read_manifest())
self.assertIn('paths', new_manifest)
self.assertNotEqual(new_manifest, self.manifest)
class CustomStaticFilesStorage(storage.StaticFilesStorage): class CustomStaticFilesStorage(storage.StaticFilesStorage):
""" """
Used in TestStaticFilePermissions Used in TestStaticFilePermissions