Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from Aug 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -423,6 +423,7 @@ answer newbie questions, and generally made Django that much better:
Jan Rademaker
Jarek Głowacki <jarekwg@gmail.com>
Jarek Zgoda <jarek.zgoda@gmail.com>
Jarosław Wygoda <jaroslaw@wygoda.me>
Jason Davies (Esaj) <https://www.jasondavies.com/>
Jason Huggins <http://www.jrandolph.com/blog/>
Jason McBrayer <http://www.carcosa.net/jason/>
Expand Down
13 changes: 8 additions & 5 deletions django/contrib/staticfiles/storage.py
Expand Up @@ -401,13 +401,16 @@ class ManifestFilesMixin(HashedFilesMixin):
manifest_strict = True
keep_intermediate_files = False

def __init__(self, *args, **kwargs):
def __init__(self, *args, manifest_storage=None, **kwargs):
super().__init__(*args, **kwargs)
if manifest_storage is None:
manifest_storage = self
self.manifest_storage = manifest_storage
self.hashed_files = self.load_manifest()

def read_manifest(self):
try:
with self.open(self.manifest_name) as manifest:
with self.manifest_storage.open(self.manifest_name) as manifest:
return manifest.read().decode()
except FileNotFoundError:
return None
Expand Down Expand Up @@ -435,10 +438,10 @@ def post_process(self, *args, **kwargs):

def save_manifest(self):
payload = {'paths': self.hashed_files, 'version': self.manifest_version}
if self.exists(self.manifest_name):
self.delete(self.manifest_name)
if self.manifest_storage.exists(self.manifest_name):
self.manifest_storage.delete(self.manifest_name)
contents = json.dumps(payload).encode()
felixxm marked this conversation as resolved.
Show resolved Hide resolved
self._save(self.manifest_name, ContentFile(contents))
self.manifest_storage._save(self.manifest_name, ContentFile(contents))

def stored_name(self, name):
parsed_name = urlsplit(unquote(name))
Expand Down
20 changes: 20 additions & 0 deletions docs/ref/contrib/staticfiles.txt
Expand Up @@ -313,13 +313,29 @@ For example, the ``'css/styles.css'`` file with this content:

@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

Support for finding paths in the source map comments was added.

Support for finding paths to JavaScript modules in ``import`` and
``export`` statements was added.

The ``manifest_storage`` argument was added.

.. attribute:: storage.ManifestStaticFilesStorage.max_post_process_passes

Since static files might reference other static files that need to have their
Expand Down Expand Up @@ -384,6 +400,10 @@ hashing algorithm.
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.

.. versionchanged:: 4.0

The ``manifest_storage`` argument was added.

Finders Module
==============

Expand Down
5 changes: 5 additions & 0 deletions docs/releases/4.0.txt
Expand Up @@ -174,6 +174,11 @@ Minor features
replaces paths to JavaScript modules in ``import`` and ``export`` statements
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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
52 changes: 51 additions & 1 deletion tests/staticfiles_tests/test_storage.py
@@ -1,3 +1,4 @@
import json
import os
import shutil
import sys
Expand All @@ -13,7 +14,7 @@
Command as CollectstaticCommand,
)
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 .settings import TEST_ROOT
Expand Down Expand Up @@ -499,6 +500,55 @@ def test_template_tag_simple_content(self):
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):
"""
Used in TestStaticFilePermissions
Expand Down