Introduce new way of adding a suffix to URLs in CssAbsoluteFilter, by hashing the content of the corresponding filenames.

Set COMPRESS_CSS_HASHING_METHOD to 'hash' to use. Fixes #105.
This commit is contained in:
Mathieu Pillard
2011-08-11 17:17:09 +02:00
committed by Jannis Leidel
parent a32c7e779d
commit df4ea4b4fa
7 changed files with 77 additions and 31 deletions

View File

@@ -24,6 +24,8 @@ class CompressorSettings(AppSettings):
ROOT = None
CSS_FILTERS = ['compressor.filters.css_default.CssAbsoluteFilter']
CSS_HASHING_METHOD = 'mtime'
JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter']
PRECOMPILERS = (
# ('text/coffeescript', 'coffee --compile --stdio'),

View File

@@ -1 +1,2 @@
from compressor.filters.base import (FilterBase, CallbackOutputFilter, CompilerFilter, FilterError)
from compressor.filters.base import (FilterBase, CallbackOutputFilter,
CompilerFilter, FilterError)

View File

@@ -2,7 +2,7 @@ import os
import re
import posixpath
from compressor.cache import get_hashed_mtime
from compressor.cache import get_hexdigest, get_hashed_mtime
from compressor.conf import settings
from compressor.filters import FilterBase
from compressor.utils import staticfiles
@@ -27,7 +27,6 @@ class CssAbsoluteFilter(FilterBase):
return self.content
self.path = basename.replace(os.sep, '/')
self.path = self.path.lstrip('/')
self.mtime = get_hashed_mtime(filename)
if self.url.startswith(('http://', 'https://')):
self.has_scheme = True
parts = self.url.split('/')
@@ -54,24 +53,36 @@ class CssAbsoluteFilter(FilterBase):
filename = os.path.join(self.root, local_path.lstrip(os.sep))
return os.path.exists(filename) and filename
def add_mtime(self, url):
def add_suffix(self, url):
filename = self.guess_filename(url)
mtime = filename and get_hashed_mtime(filename) or self.mtime
if mtime is None:
suffix = None
if filename:
if settings.COMPRESS_CSS_HASHING_METHOD == "mtime":
suffix = get_hashed_mtime(filename)
elif settings.COMPRESS_CSS_HASHING_METHOD == "hash":
hash_file = open(filename)
try:
suffix = get_hexdigest(hash_file.read(), 12)
finally:
hash_file.close()
else:
raise FilterError('COMPRESS_CSS_HASHING_METHOD is configured '
'with an unknown method (%s).')
if suffix is None:
return url
if url.startswith(('http://', 'https://', '/')):
if "?" in url:
url = "%s&%s" % (url, mtime)
url = "%s&%s" % (url, suffix)
else:
url = "%s?%s" % (url, mtime)
url = "%s?%s" % (url, suffix)
return url
def url_converter(self, matchobj):
url = matchobj.group(1)
url = url.strip(' \'"')
if url.startswith(('http://', 'https://', '/', 'data:')):
return "url('%s')" % self.add_mtime(url)
return "url('%s')" % self.add_suffix(url)
full_url = posixpath.normpath('/'.join([str(self.directory_name), url]))
if self.has_scheme:
full_url = "%s%s" % (self.protocol, full_url)
return "url('%s')" % self.add_mtime(full_url)
return "url('%s')" % self.add_suffix(full_url)

View File

@@ -70,6 +70,11 @@ Possible options are:
A filter that normalizes the URLs used in ``url()`` CSS statements.
- ``COMPRESS_CSS_HASHING_METHOD`` -- The method to use when calculating
the hash to append to processed URLs. Either ``'mtime'`` (default) or
``'hash'``. Use the latter in case you're using multiple server to serve
your static files.
``compressor.filters.csstidy.CSSTidyFilter``
""""""""""""""""""""""""""""""""""""""""""""

View File

@@ -1,4 +1,4 @@
p { background: url('../../../images/test.png'); }
p { background: url(../../../images/test.png); }
p { background: url( ../../../images/test.png ); }
p { background: url( '../../../images/test.png' ); }
p { background: url('../../../img/add.png'); }
p { background: url(../../../img/add.png); }
p { background: url( ../../../img/add.png ); }
p { background: url( '../../../img/add.png' ); }

View File

@@ -1,4 +1,4 @@
p { background: url('../../images/test.png'); }
p { background: url(../../images/test.png); }
p { background: url( ../../images/test.png ); }
p { background: url( '../../images/test.png' ); }
p { background: url('../../img/python.png'); }
p { background: url(../../img/python.png); }
p { background: url( ../../img/python.png ); }
p { background: url( '../../img/python.png' ); }

View File

@@ -86,62 +86,88 @@ class CssAbsolutizingTestCase(TestCase):
def setUp(self):
settings.COMPRESS_ENABLED = True
settings.COMPRESS_URL = '/media/'
settings.COMPRESS_CSS_HASHING_METHOD = 'mtime'
self.css = """
<link rel="stylesheet" href="/media/css/url/url1.css" type="text/css">
<link rel="stylesheet" href="/media/css/url/2/url2.css" type="text/css">
"""
self.css_node = CssCompressor(self.css)
def suffix_method(self, filename):
return get_hashed_mtime(filename)
def test_css_absolute_filter(self):
from compressor.filters.css_default import CssAbsoluteFilter
filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
content = "p { background: url('../../images/image.gif') }"
output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename))
imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
content = "p { background: url('../../img/python.png') }"
output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename))
filter = CssAbsoluteFilter(content)
self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
settings.COMPRESS_URL = 'http://media.example.com/'
filter = CssAbsoluteFilter(content)
filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename))
output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename))
self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
def test_css_absolute_filter_https(self):
from compressor.filters.css_default import CssAbsoluteFilter
filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
content = "p { background: url('../../images/image.gif') }"
output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename))
imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
content = "p { background: url('../../img/python.png') }"
output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename))
filter = CssAbsoluteFilter(content)
self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
settings.COMPRESS_URL = 'https://media.example.com/'
filter = CssAbsoluteFilter(content)
filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename))
output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename))
self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
def test_css_absolute_filter_relative_path(self):
from compressor.filters.css_default import CssAbsoluteFilter
filename = os.path.join(settings.TEST_DIR, 'whatever', '..', 'media', 'whatever/../css/url/test.css')
content = "p { background: url('../../images/image.gif') }"
output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename))
imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
content = "p { background: url('../../img/python.png') }"
output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename))
filter = CssAbsoluteFilter(content)
self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
settings.COMPRESS_URL = 'https://media.example.com/'
filter = CssAbsoluteFilter(content)
output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename))
output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename))
self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
def test_css_hunks(self):
hash_dict = {
'hash1': get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'css/url/url1.css')),
'hash2': get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'css/url/2/url2.css')),
'hash1': self.suffix_method(os.path.join(settings.COMPRESS_ROOT, 'img/python.png')),
'hash2': self.suffix_method(os.path.join(settings.COMPRESS_ROOT, 'img/add.png')),
}
out = [u"p { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\n" % hash_dict,
u"p { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\n" % hash_dict]
out = [u"p { background: url('/media/img/python.png?%(hash1)s'); }\np { background: url('/media/img/python.png?%(hash1)s'); }\np { background: url('/media/img/python.png?%(hash1)s'); }\np { background: url('/media/img/python.png?%(hash1)s'); }\n" % hash_dict,
u"p { background: url('/media/img/add.png?%(hash2)s'); }\np { background: url('/media/img/add.png?%(hash2)s'); }\np { background: url('/media/img/add.png?%(hash2)s'); }\np { background: url('/media/img/add.png?%(hash2)s'); }\n" % hash_dict]
hunks = [h for m, h in self.css_node.hunks()]
self.assertEqual(out, hunks)
class CssAbsolutizingTestCaseWithHash(CssAbsolutizingTestCase):
def setUp(self):
settings.COMPRESS_ENABLED = True
settings.COMPRESS_URL = '/media/'
settings.COMPRESS_CSS_HASHING_METHOD = 'hash'
self.css = """
<link rel="stylesheet" href="/media/css/url/url1.css" type="text/css" charset="utf-8">
<link rel="stylesheet" href="/media/css/url/2/url2.css" type="text/css" charset="utf-8">
"""
self.css_node = CssCompressor(self.css)
def suffix_method(self, filename):
f = open(filename)
suffix = "H%s" % (get_hexdigest(f.read(), 12), )
f.close()
return suffix
class CssDataUriTestCase(TestCase):
def setUp(self):
settings.COMPRESS_ENABLED = True
@@ -150,6 +176,7 @@ class CssDataUriTestCase(TestCase):
'compressor.filters.datauri.CssDataUriFilter',
]
settings.COMPRESS_URL = '/media/'
settings.COMPRESS_CSS_HASHING_METHOD = 'mtime'
self.css = """
<link rel="stylesheet" href="/media/css/datauri.css" type="text/css">
"""