Commit 5f75ac91 authored by Jannis Leidel's avatar Jannis Leidel
Browse files

Fixed #17896 -- Added file_hash method to CachedStaticFilesStorage to be able...

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.
parent 085c03e0
Loading
Loading
Loading
Loading
+16 −7
Original line number Diff line number Diff line
@@ -64,6 +64,17 @@ class CachedFilesMixin(object):
                compiled = re.compile(pattern)
                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):
        parsed_name = urlsplit(unquote(name))
        clean_name = parsed_name.path.strip()
@@ -78,13 +89,11 @@ class CachedFilesMixin(object):
                return name
        path, filename = os.path.split(clean_name)
        root, ext = os.path.splitext(filename)
        # Get the MD5 hash of the file
        md5 = hashlib.md5()
        for chunk in content.chunks():
            md5.update(chunk)
        md5sum = md5.hexdigest()[:12]
        hashed_name = os.path.join(path, u"%s.%s%s" %
                                   (root, md5sum, ext))
        file_hash = self.file_hash(clean_name, content)
        if file_hash is not None:
            file_hash = u".%s" % file_hash
        hashed_name = os.path.join(path, u"%s%s%s" %
                                   (root, file_hash, ext))
        unparsed_name = list(parsed_name)
        unparsed_name[2] = hashed_name
        # Special casing for a @font-face hack, like url(myfont.eot?#iefix")
+9 −0
Original line number Diff line number Diff line
@@ -348,6 +348,15 @@ CachedStaticFilesStorage
    :setting:`CACHES` setting named ``'staticfiles'``. It falls back to using
    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
.. _`@import`: http://www.w3.org/TR/CSS2/cascade.html#at-import
.. _`url()`: http://www.w3.org/TR/CSS2/syndata.html#uri
+7 −0
Original line number Diff line number Diff line
from datetime import datetime
from django.core.files import storage
from django.contrib.staticfiles.storage import CachedStaticFilesStorage

class DummyStorage(storage.Storage):
    """
@@ -17,3 +18,9 @@ class DummyStorage(storage.Storage):

    def modified_time(self, name):
        return datetime.date(1970, 1, 1)


class SimpleCachedStaticFilesStorage(CachedStaticFilesStorage):

    def file_hash(self, name, content=None):
        return 'deploy12345'
+39 −1
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ from StringIO import StringIO

from django.template import loader, Context
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.files.storage import default_storage
from django.core.management import call_command
@@ -515,6 +515,44 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
        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':

    class TestCollectionLinks(CollectionTestCase, TestDefaults):