Skip to content

Commit dbaf995

Browse files
committed
Added cache busting ability to CssAbsoluteFilter filter which appends a hash to each image URL (except data: images).
1 parent c06e88e commit dbaf995

File tree

3 files changed

+36
-15
lines changed

3 files changed

+36
-15
lines changed

compressor/filters/css_default.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from compressor.filters import FilterBase, FilterError
66
from compressor.conf import settings
7+
from compressor import get_hexdigest
78

89
class CssAbsoluteFilter(FilterBase):
910
def input(self, filename=None, **kwargs):
@@ -15,6 +16,11 @@ def input(self, filename=None, **kwargs):
1516
self.media_path = filename[len(media_root):]
1617
self.media_path = self.media_path.lstrip('/')
1718
self.media_url = settings.MEDIA_URL.rstrip('/')
19+
try:
20+
mtime = os.path.getmtime(filename)
21+
self.mtime = get_hexdigest(str(int(mtime)))[:12]
22+
except OSError:
23+
self.mtime = None
1824
self.has_http = False
1925
if self.media_url.startswith('http://') or self.media_url.startswith('https://'):
2026
self.has_http = True
@@ -26,16 +32,27 @@ def input(self, filename=None, **kwargs):
2632
output = url_pattern.sub(self.url_converter, self.content)
2733
return output
2834

35+
def add_mtime(self, url):
36+
if self.mtime is None:
37+
return url
38+
if (url.startswith('http://') or
39+
url.startswith('https://') or
40+
url.startswith('/')):
41+
if "?" in url:
42+
return "%s&%s" % (url, self.mtime)
43+
return "%s?%s" % (url, self.mtime)
44+
return url
45+
2946
def url_converter(self, matchobj):
3047
url = matchobj.group(1)
3148
url = url.strip(' \'"')
32-
if (url.startswith('http://') or
33-
url.startswith('https://') or
34-
url.startswith('/') or
49+
if (url.startswith('http://') or
50+
url.startswith('https://') or
51+
url.startswith('/') or
3552
url.startswith('data:')):
36-
return "url('http://github.com/%s')" % url
53+
return "url('http://github.com/%s')" % self.add_mtime(url)
3754
full_url = '/'.join([str(self.directory_name), url])
3855
full_url = posixpath.normpath(full_url)
3956
if self.has_http:
40-
full_url = "%s%s" % (self.protocol,full_url)
41-
return "url('http://github.com/%s')" % full_url
57+
full_url = "%s%s" % (self.protocol, full_url)
58+
return "url('http://github.com/%s')" % self.add_mtime(full_url)

compressor/filters/datauri.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import re
33
import mimetypes
4+
import urlparse
45
from base64 import b64encode
56
from compressor.filters import FilterBase
67
from compressor.conf import settings
@@ -25,6 +26,9 @@ def input(self, filename=None, **kwargs):
2526
return output
2627

2728
def get_file_path(self, url):
29+
# strip query string of file paths
30+
if "?" in url:
31+
url = url.split("?")[0]
2832
return os.path.join(settings.MEDIA_ROOT, url[len(settings.MEDIA_URL):])
2933

3034
def data_uri_converter(self, matchobj):

tests/core/tests.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,41 +122,41 @@ def test_css_absolute_filter(self):
122122
from compressor.filters.css_default import CssAbsoluteFilter
123123
filename = os.path.join(settings.MEDIA_ROOT, 'css/url/test.css')
124124
content = "p { background: url(http://github.com/'../../images/image.gif') }"
125-
output = "p { background: url('http://github.com/%simages/image.gif') }" % settings.MEDIA_URL
125+
output = "p { background: url('http://github.com/%simages/image.gif%3Cspan%20class=%22x%20x-first%20x-last%22%3E?275088b9bcf0%3C/span%3E') }" % settings.MEDIA_URL
126126
filter = CssAbsoluteFilter(content)
127127
self.assertEqual(output, filter.input(filename=filename))
128128
settings.MEDIA_URL = 'http://media.example.com/'
129129
filename = os.path.join(settings.MEDIA_ROOT, 'css/url/test.css')
130-
output = "p { background: url('http://github.com/%simages/image.gif') }" % settings.MEDIA_URL
130+
output = "p { background: url('http://github.com/%simages/image.gif%3Cspan%20class=%22x%20x-first%20x-last%22%3E?275088b9bcf0%3C/span%3E') }" % settings.MEDIA_URL
131131
self.assertEqual(output, filter.input(filename=filename))
132132

133133
def test_css_absolute_filter_https(self):
134134
from compressor.filters.css_default import CssAbsoluteFilter
135135
filename = os.path.join(settings.MEDIA_ROOT, 'css/url/test.css')
136136
content = "p { background: url(http://github.com/'../../images/image.gif') }"
137-
output = "p { background: url('http://github.com/%simages/image.gif') }" % settings.MEDIA_URL
137+
output = "p { background: url('http://github.com/%simages/image.gif%3Cspan%20class=%22x%20x-first%20x-last%22%3E?275088b9bcf0%3C/span%3E') }" % settings.MEDIA_URL
138138
filter = CssAbsoluteFilter(content)
139139
self.assertEqual(output, filter.input(filename=filename))
140140
settings.MEDIA_URL = 'https://media.example.com/'
141141
filename = os.path.join(settings.MEDIA_ROOT, 'css/url/test.css')
142-
output = "p { background: url('http://github.com/%simages/image.gif') }" % settings.MEDIA_URL
142+
output = "p { background: url('http://github.com/%simages/image.gif%3Cspan%20class=%22x%20x-first%20x-last%22%3E?275088b9bcf0%3C/span%3E') }" % settings.MEDIA_URL
143143
self.assertEqual(output, filter.input(filename=filename))
144144

145145
def test_css_absolute_filter_relative_path(self):
146146
from compressor.filters.css_default import CssAbsoluteFilter
147147
filename = os.path.join(django_settings.TEST_DIR, 'whatever', '..', 'media', 'whatever/../css/url/test.css')
148148
content = "p { background: url(http://github.com/'../../images/image.gif') }"
149-
output = "p { background: url('http://github.com/%simages/image.gif') }" % settings.MEDIA_URL
149+
output = "p { background: url('http://github.com/%simages/image.gif%3Cspan%20class=%22x%20x-first%20x-last%22%3E?275088b9bcf0%3C/span%3E') }" % settings.MEDIA_URL
150150
filter = CssAbsoluteFilter(content)
151151
self.assertEqual(output, filter.input(filename=filename))
152152
settings.MEDIA_URL = 'https://media.example.com/'
153-
output = "p { background: url('http://github.com/%simages/image.gif') }" % settings.MEDIA_URL
153+
output = "p { background: url('http://github.com/%simages/image.gif%3Cspan%20class=%22x%20x-first%20x-last%22%3E?275088b9bcf0%3C/span%3E') }" % settings.MEDIA_URL
154154
self.assertEqual(output, filter.input(filename=filename))
155155

156156

157157
def test_css_hunks(self):
158-
out = [u"p { background: url('http://github.com/media/images/test.png'); }\np { background: url('http://github.com/media/images/test.png'); }\np { background: url('http://github.com/media/images/test.png'); }\np { background: url('http://github.com/media/images/test.png'); }\n",
159-
u"p { background: url('http://github.com/media/images/test.png'); }\np { background: url('http://github.com/media/images/test.png'); }\np { background: url('http://github.com/media/images/test.png'); }\np { background: url('http://github.com/media/images/test.png'); }\n",
158+
out = [u"p { background: url('http://github.com/media/images/test.png%3Cspan%20class=%22x%20x-first%20x-last%22%3E?86ea855a37ed%3C/span%3E'); }\np { background: url('http://github.com/media/images/test.png%3Cspan%20class=%22x%20x-first%20x-last%22%3E?86ea855a37ed%3C/span%3E'); }\np { background: url('http://github.com/media/images/test.png%3Cspan%20class=%22x%20x-first%20x-last%22%3E?86ea855a37ed%3C/span%3E'); }\np { background: url('http://github.com/media/images/test.png%3Cspan%20class=%22x%20x-first%20x-last%22%3E?86ea855a37ed%3C/span%3E'); }\n",
159+
u"p { background: url('http://github.com/media/images/test.png%3Cspan%20class=%22x%20x-first%20x-last%22%3E?86ea855a37ed%3C/span%3E'); }\np { background: url('http://github.com/media/images/test.png%3Cspan%20class=%22x%20x-first%20x-last%22%3E?86ea855a37ed%3C/span%3E'); }\np { background: url('http://github.com/media/images/test.png%3Cspan%20class=%22x%20x-first%20x-last%22%3E?86ea855a37ed%3C/span%3E'); }\np { background: url('http://github.com/media/images/test.png%3Cspan%20class=%22x%20x-first%20x-last%22%3E?86ea855a37ed%3C/span%3E'); }\n",
160160
]
161161
self.assertEqual(out, self.cssNode.hunks)
162162

@@ -172,7 +172,7 @@ def setUp(self):
172172
self.cssNode = CssCompressor(self.css)
173173

174174
def test_data_uris(self):
175-
out = [u'.add { background-image: url(http://github.com/"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }\n.python { background-image: url(http://github.com/"/media/img/python.png"); }\n.datauri { background-image: url(http://github.com/"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0%20vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); }\n']
175+
out = [u'.add { background-image: url(http://github.com/"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }\n.python { background-image: url(http://github.com/"/media/img/python.png?f59031cef54b"); }\n.datauri { background-image: url(http://github.com/"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0%20vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); }\n']
176176
self.assertEqual(out, self.cssNode.hunks)
177177

178178

0 commit comments

Comments
 (0)