Fixed #17896 -- Added file_hash method to CachedStaticFilesStorage to be able to customize the way the hashed name of a file is created. Thanks to mkai for the initial patch.
This commit is contained in:
parent
085c03e08b
commit
5f75ac91df
|
@ -64,6 +64,17 @@ class CachedFilesMixin(object):
|
||||||
compiled = re.compile(pattern)
|
compiled = re.compile(pattern)
|
||||||
self._patterns.setdefault(extension, []).append(compiled)
|
self._patterns.setdefault(extension, []).append(compiled)
|
||||||
|
|
||||||
|
def file_hash(self, name, content=None):
|
||||||
|
"""
|
||||||
|
Retuns a hash of the file with the given name and optional content.
|
||||||
|
"""
|
||||||
|
if content is None:
|
||||||
|
return None
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
for chunk in content.chunks():
|
||||||
|
md5.update(chunk)
|
||||||
|
return md5.hexdigest()[:12]
|
||||||
|
|
||||||
def hashed_name(self, name, content=None):
|
def hashed_name(self, name, content=None):
|
||||||
parsed_name = urlsplit(unquote(name))
|
parsed_name = urlsplit(unquote(name))
|
||||||
clean_name = parsed_name.path.strip()
|
clean_name = parsed_name.path.strip()
|
||||||
|
@ -78,13 +89,11 @@ class CachedFilesMixin(object):
|
||||||
return name
|
return name
|
||||||
path, filename = os.path.split(clean_name)
|
path, filename = os.path.split(clean_name)
|
||||||
root, ext = os.path.splitext(filename)
|
root, ext = os.path.splitext(filename)
|
||||||
# Get the MD5 hash of the file
|
file_hash = self.file_hash(clean_name, content)
|
||||||
md5 = hashlib.md5()
|
if file_hash is not None:
|
||||||
for chunk in content.chunks():
|
file_hash = u".%s" % file_hash
|
||||||
md5.update(chunk)
|
hashed_name = os.path.join(path, u"%s%s%s" %
|
||||||
md5sum = md5.hexdigest()[:12]
|
(root, file_hash, ext))
|
||||||
hashed_name = os.path.join(path, u"%s.%s%s" %
|
|
||||||
(root, md5sum, ext))
|
|
||||||
unparsed_name = list(parsed_name)
|
unparsed_name = list(parsed_name)
|
||||||
unparsed_name[2] = hashed_name
|
unparsed_name[2] = hashed_name
|
||||||
# Special casing for a @font-face hack, like url(myfont.eot?#iefix")
|
# Special casing for a @font-face hack, like url(myfont.eot?#iefix")
|
||||||
|
|
|
@ -348,6 +348,15 @@ CachedStaticFilesStorage
|
||||||
:setting:`CACHES` setting named ``'staticfiles'``. It falls back to using
|
:setting:`CACHES` setting named ``'staticfiles'``. It falls back to using
|
||||||
the ``'default'`` cache backend.
|
the ``'default'`` cache backend.
|
||||||
|
|
||||||
|
.. method:: file_hash(name, content=None)
|
||||||
|
|
||||||
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
|
The method that is used when creating the hashed name of a file.
|
||||||
|
Needs to return a hash for the given file name and content.
|
||||||
|
By default it calculates a MD5 hash from the content's chunks as
|
||||||
|
mentioned above.
|
||||||
|
|
||||||
.. _`far future Expires headers`: http://developer.yahoo.com/performance/rules.html#expires
|
.. _`far future Expires headers`: http://developer.yahoo.com/performance/rules.html#expires
|
||||||
.. _`@import`: http://www.w3.org/TR/CSS2/cascade.html#at-import
|
.. _`@import`: http://www.w3.org/TR/CSS2/cascade.html#at-import
|
||||||
.. _`url()`: http://www.w3.org/TR/CSS2/syndata.html#uri
|
.. _`url()`: http://www.w3.org/TR/CSS2/syndata.html#uri
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.core.files import storage
|
from django.core.files import storage
|
||||||
|
from django.contrib.staticfiles.storage import CachedStaticFilesStorage
|
||||||
|
|
||||||
class DummyStorage(storage.Storage):
|
class DummyStorage(storage.Storage):
|
||||||
"""
|
"""
|
||||||
|
@ -17,3 +18,9 @@ class DummyStorage(storage.Storage):
|
||||||
|
|
||||||
def modified_time(self, name):
|
def modified_time(self, name):
|
||||||
return datetime.date(1970, 1, 1)
|
return datetime.date(1970, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleCachedStaticFilesStorage(CachedStaticFilesStorage):
|
||||||
|
|
||||||
|
def file_hash(self, name, content=None):
|
||||||
|
return 'deploy12345'
|
||||||
|
|
|
@ -10,7 +10,7 @@ from StringIO import StringIO
|
||||||
|
|
||||||
from django.template import loader, Context
|
from django.template import loader, Context
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache.backends.base import BaseCache, CacheKeyWarning
|
from django.core.cache.backends.base import BaseCache
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
@ -515,6 +515,44 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
|
||||||
self.assertEqual(cache_key, 'staticfiles:e95bbc36387084582df2a70750d7b351')
|
self.assertEqual(cache_key, 'staticfiles:e95bbc36387084582df2a70750d7b351')
|
||||||
|
|
||||||
|
|
||||||
|
# we set DEBUG to False here since the template tag wouldn't work otherwise
|
||||||
|
@override_settings(**dict(TEST_SETTINGS,
|
||||||
|
STATICFILES_STORAGE='regressiontests.staticfiles_tests.storage.SimpleCachedStaticFilesStorage',
|
||||||
|
DEBUG=False,
|
||||||
|
))
|
||||||
|
class TestCollectionSimpleCachedStorage(BaseCollectionTestCase,
|
||||||
|
BaseStaticFilesTestCase, TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the Cache busting storage
|
||||||
|
"""
|
||||||
|
def cached_file_path(self, path):
|
||||||
|
fullpath = self.render_template(self.static_template_snippet(path))
|
||||||
|
return fullpath.replace(settings.STATIC_URL, '')
|
||||||
|
|
||||||
|
def test_template_tag_return(self):
|
||||||
|
"""
|
||||||
|
Test the CachedStaticFilesStorage backend.
|
||||||
|
"""
|
||||||
|
self.assertStaticRaises(ValueError,
|
||||||
|
"does/not/exist.png",
|
||||||
|
"/static/does/not/exist.png")
|
||||||
|
self.assertStaticRenders("test/file.txt",
|
||||||
|
"/static/test/file.deploy12345.txt")
|
||||||
|
self.assertStaticRenders("cached/styles.css",
|
||||||
|
"/static/cached/styles.deploy12345.css")
|
||||||
|
self.assertStaticRenders("path/",
|
||||||
|
"/static/path/")
|
||||||
|
self.assertStaticRenders("path/?query",
|
||||||
|
"/static/path/?query")
|
||||||
|
|
||||||
|
def test_template_tag_simple_content(self):
|
||||||
|
relpath = self.cached_file_path("cached/styles.css")
|
||||||
|
self.assertEqual(relpath, "cached/styles.deploy12345.css")
|
||||||
|
with storage.staticfiles_storage.open(relpath) as relfile:
|
||||||
|
content = relfile.read()
|
||||||
|
self.assertNotIn("cached/other.css", content)
|
||||||
|
self.assertIn("other.deploy12345.css", content)
|
||||||
|
|
||||||
if sys.platform != 'win32':
|
if sys.platform != 'win32':
|
||||||
|
|
||||||
class TestCollectionLinks(CollectionTestCase, TestDefaults):
|
class TestCollectionLinks(CollectionTestCase, TestDefaults):
|
||||||
|
|
Loading…
Reference in New Issue