0
0
mirror of https://github.com/django/django.git synced 2024-11-24 20:07:01 +01:00

Fixed #34235 -- Added ManifestFilesMixin.manifest_hash attribute.

This adds ManifestFilesMixin.manifest_hash attribute exposing a "hash"
of the full manifest. This allows applications to determine when their
static files have changed.
This commit is contained in:
Florian Apolloner 2022-12-29 16:52:56 +01:00 committed by Mariusz Felisiak
parent 75500feecd
commit afa2e28205
5 changed files with 58 additions and 10 deletions

View File

@ -439,7 +439,7 @@ class HashedFilesMixin:
class ManifestFilesMixin(HashedFilesMixin):
manifest_version = "1.0" # the manifest format standard
manifest_version = "1.1" # the manifest format standard
manifest_name = "staticfiles.json"
manifest_strict = True
keep_intermediate_files = False
@ -449,7 +449,7 @@ class ManifestFilesMixin(HashedFilesMixin):
if manifest_storage is None:
manifest_storage = self
self.manifest_storage = manifest_storage
self.hashed_files = self.load_manifest()
self.hashed_files, self.manifest_hash = self.load_manifest()
def read_manifest(self):
try:
@ -461,15 +461,15 @@ class ManifestFilesMixin(HashedFilesMixin):
def load_manifest(self):
content = self.read_manifest()
if content is None:
return {}
return {}, ""
try:
stored = json.loads(content)
except json.JSONDecodeError:
pass
else:
version = stored.get("version")
if version == "1.0":
return stored.get("paths", {})
if version in ("1.0", "1.1"):
return stored.get("paths", {}), stored.get("hash", "")
raise ValueError(
"Couldn't load manifest '%s' (version %s)"
% (self.manifest_name, self.manifest_version)
@ -482,7 +482,14 @@ class ManifestFilesMixin(HashedFilesMixin):
self.save_manifest()
def save_manifest(self):
payload = {"paths": self.hashed_files, "version": self.manifest_version}
self.manifest_hash = self.file_hash(
None, ContentFile(json.dumps(sorted(self.hashed_files.items())).encode())
)
payload = {
"paths": self.hashed_files,
"version": self.manifest_version,
"hash": self.manifest_hash,
}
if self.manifest_storage.exists(self.manifest_name):
self.manifest_storage.delete(self.manifest_name)
contents = json.dumps(payload).encode()

View File

@ -336,6 +336,14 @@ argument. For example::
Support for finding paths to JavaScript modules in ``import`` and
``export`` statements was added.
.. attribute:: storage.ManifestStaticFilesStorage.manifest_hash
.. versionadded:: 4.2
This attribute provides a single hash that changes whenever a file in the
manifest changes. This can be useful to communicate to SPAs that the assets on
the server have changed (due to a new deployment).
.. attribute:: storage.ManifestStaticFilesStorage.max_post_process_passes
Since static files might reference other static files that need to have their

View File

@ -201,6 +201,10 @@ Minor features
replaces paths to JavaScript modules in ``import`` and ``export`` statements
with their hashed counterparts.
* The new :attr:`.ManifestStaticFilesStorage.manifest_hash` attribute provides
a hash over all files in the manifest and changes whenever one of the files
changes.
:mod:`django.contrib.syndication`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,6 @@
{
"version": "1.0",
"paths": {
"dummy.txt": "dummy.txt"
}
}

View File

@ -436,7 +436,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
# The in-memory version of the manifest matches the one on disk
# since a properly created manifest should cover all filenames.
if hashed_files:
manifest = storage.staticfiles_storage.load_manifest()
manifest, _ = storage.staticfiles_storage.load_manifest()
self.assertEqual(hashed_files, manifest)
def test_manifest_exists(self):
@ -463,7 +463,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
def test_parse_cache(self):
hashed_files = storage.staticfiles_storage.hashed_files
manifest = storage.staticfiles_storage.load_manifest()
manifest, _ = storage.staticfiles_storage.load_manifest()
self.assertEqual(hashed_files, manifest)
def test_clear_empties_manifest(self):
@ -476,7 +476,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
hashed_files = storage.staticfiles_storage.hashed_files
self.assertIn(cleared_file_name, hashed_files)
manifest_content = storage.staticfiles_storage.load_manifest()
manifest_content, _ = storage.staticfiles_storage.load_manifest()
self.assertIn(cleared_file_name, manifest_content)
original_path = storage.staticfiles_storage.path(cleared_file_name)
@ -491,7 +491,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
hashed_files = storage.staticfiles_storage.hashed_files
self.assertNotIn(cleared_file_name, hashed_files)
manifest_content = storage.staticfiles_storage.load_manifest()
manifest_content, _ = storage.staticfiles_storage.load_manifest()
self.assertNotIn(cleared_file_name, manifest_content)
def test_missing_entry(self):
@ -535,6 +535,29 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
2,
)
def test_manifest_hash(self):
# Collect the additional file.
self.run_collectstatic()
_, manifest_hash_orig = storage.staticfiles_storage.load_manifest()
self.assertNotEqual(manifest_hash_orig, "")
self.assertEqual(storage.staticfiles_storage.manifest_hash, manifest_hash_orig)
# Saving doesn't change the hash.
storage.staticfiles_storage.save_manifest()
self.assertEqual(storage.staticfiles_storage.manifest_hash, manifest_hash_orig)
# Delete the original file from the app, collect with clear.
os.unlink(self._clear_filename)
self.run_collectstatic(clear=True)
# Hash is changed.
_, manifest_hash = storage.staticfiles_storage.load_manifest()
self.assertNotEqual(manifest_hash, manifest_hash_orig)
def test_manifest_hash_v1(self):
storage.staticfiles_storage.manifest_name = "staticfiles_v1.json"
manifest_content, manifest_hash = storage.staticfiles_storage.load_manifest()
self.assertEqual(manifest_hash, "")
self.assertEqual(manifest_content, {"dummy.txt": "dummy.txt"})
@override_settings(STATICFILES_STORAGE="staticfiles_tests.storage.NoneHashStorage")
class TestCollectionNoneHashStorage(CollectionTestCase):