mirror of
https://github.com/django/django.git
synced 2025-04-01 03:56:42 +00: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:
parent
75500feecd
commit
afa2e28205
@ -439,7 +439,7 @@ class HashedFilesMixin:
|
|||||||
|
|
||||||
|
|
||||||
class ManifestFilesMixin(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_name = "staticfiles.json"
|
||||||
manifest_strict = True
|
manifest_strict = True
|
||||||
keep_intermediate_files = False
|
keep_intermediate_files = False
|
||||||
@ -449,7 +449,7 @@ class ManifestFilesMixin(HashedFilesMixin):
|
|||||||
if manifest_storage is None:
|
if manifest_storage is None:
|
||||||
manifest_storage = self
|
manifest_storage = self
|
||||||
self.manifest_storage = manifest_storage
|
self.manifest_storage = manifest_storage
|
||||||
self.hashed_files = self.load_manifest()
|
self.hashed_files, self.manifest_hash = self.load_manifest()
|
||||||
|
|
||||||
def read_manifest(self):
|
def read_manifest(self):
|
||||||
try:
|
try:
|
||||||
@ -461,15 +461,15 @@ class ManifestFilesMixin(HashedFilesMixin):
|
|||||||
def load_manifest(self):
|
def load_manifest(self):
|
||||||
content = self.read_manifest()
|
content = self.read_manifest()
|
||||||
if content is None:
|
if content is None:
|
||||||
return {}
|
return {}, ""
|
||||||
try:
|
try:
|
||||||
stored = json.loads(content)
|
stored = json.loads(content)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
version = stored.get("version")
|
version = stored.get("version")
|
||||||
if version == "1.0":
|
if version in ("1.0", "1.1"):
|
||||||
return stored.get("paths", {})
|
return stored.get("paths", {}), stored.get("hash", "")
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Couldn't load manifest '%s' (version %s)"
|
"Couldn't load manifest '%s' (version %s)"
|
||||||
% (self.manifest_name, self.manifest_version)
|
% (self.manifest_name, self.manifest_version)
|
||||||
@ -482,7 +482,14 @@ class ManifestFilesMixin(HashedFilesMixin):
|
|||||||
self.save_manifest()
|
self.save_manifest()
|
||||||
|
|
||||||
def save_manifest(self):
|
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):
|
if self.manifest_storage.exists(self.manifest_name):
|
||||||
self.manifest_storage.delete(self.manifest_name)
|
self.manifest_storage.delete(self.manifest_name)
|
||||||
contents = json.dumps(payload).encode()
|
contents = json.dumps(payload).encode()
|
||||||
|
@ -336,6 +336,14 @@ argument. For example::
|
|||||||
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.
|
||||||
|
|
||||||
|
.. 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
|
.. 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
|
||||||
|
@ -201,6 +201,10 @@ 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 :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`
|
:mod:`django.contrib.syndication`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"paths": {
|
||||||
|
"dummy.txt": "dummy.txt"
|
||||||
|
}
|
||||||
|
}
|
@ -436,7 +436,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
|
|||||||
# The in-memory version of the manifest matches the one on disk
|
# The in-memory version of the manifest matches the one on disk
|
||||||
# since a properly created manifest should cover all filenames.
|
# since a properly created manifest should cover all filenames.
|
||||||
if hashed_files:
|
if hashed_files:
|
||||||
manifest = storage.staticfiles_storage.load_manifest()
|
manifest, _ = storage.staticfiles_storage.load_manifest()
|
||||||
self.assertEqual(hashed_files, manifest)
|
self.assertEqual(hashed_files, manifest)
|
||||||
|
|
||||||
def test_manifest_exists(self):
|
def test_manifest_exists(self):
|
||||||
@ -463,7 +463,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
|
|||||||
|
|
||||||
def test_parse_cache(self):
|
def test_parse_cache(self):
|
||||||
hashed_files = storage.staticfiles_storage.hashed_files
|
hashed_files = storage.staticfiles_storage.hashed_files
|
||||||
manifest = storage.staticfiles_storage.load_manifest()
|
manifest, _ = storage.staticfiles_storage.load_manifest()
|
||||||
self.assertEqual(hashed_files, manifest)
|
self.assertEqual(hashed_files, manifest)
|
||||||
|
|
||||||
def test_clear_empties_manifest(self):
|
def test_clear_empties_manifest(self):
|
||||||
@ -476,7 +476,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
|
|||||||
hashed_files = storage.staticfiles_storage.hashed_files
|
hashed_files = storage.staticfiles_storage.hashed_files
|
||||||
self.assertIn(cleared_file_name, 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)
|
self.assertIn(cleared_file_name, manifest_content)
|
||||||
|
|
||||||
original_path = storage.staticfiles_storage.path(cleared_file_name)
|
original_path = storage.staticfiles_storage.path(cleared_file_name)
|
||||||
@ -491,7 +491,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
|
|||||||
hashed_files = storage.staticfiles_storage.hashed_files
|
hashed_files = storage.staticfiles_storage.hashed_files
|
||||||
self.assertNotIn(cleared_file_name, 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)
|
self.assertNotIn(cleared_file_name, manifest_content)
|
||||||
|
|
||||||
def test_missing_entry(self):
|
def test_missing_entry(self):
|
||||||
@ -535,6 +535,29 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
|
|||||||
2,
|
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")
|
@override_settings(STATICFILES_STORAGE="staticfiles_tests.storage.NoneHashStorage")
|
||||||
class TestCollectionNoneHashStorage(CollectionTestCase):
|
class TestCollectionNoneHashStorage(CollectionTestCase):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user