Fixed #16966 -- Stopped CachedStaticFilesStorage from choking on querystrings and path fragments.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17068 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
0e2c543979
commit
a82488bf4b
|
@ -3,6 +3,8 @@ import hashlib
|
||||||
import os
|
import os
|
||||||
import posixpath
|
import posixpath
|
||||||
import re
|
import re
|
||||||
|
from urllib import unquote
|
||||||
|
from urlparse import urlsplit, urlunsplit
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import (get_cache, InvalidCacheBackendError,
|
from django.core.cache import (get_cache, InvalidCacheBackendError,
|
||||||
|
@ -64,23 +66,28 @@ class CachedFilesMixin(object):
|
||||||
self._patterns.setdefault(extension, []).append(compiled)
|
self._patterns.setdefault(extension, []).append(compiled)
|
||||||
|
|
||||||
def hashed_name(self, name, content=None):
|
def hashed_name(self, name, content=None):
|
||||||
|
parsed_name = urlsplit(unquote(name))
|
||||||
|
clean_name = parsed_name.path
|
||||||
if content is None:
|
if content is None:
|
||||||
if not self.exists(name):
|
if not self.exists(clean_name):
|
||||||
raise ValueError("The file '%s' could not be found with %r." %
|
raise ValueError("The file '%s' could not be found with %r." %
|
||||||
(name, self))
|
(clean_name, self))
|
||||||
try:
|
try:
|
||||||
content = self.open(name)
|
content = self.open(clean_name)
|
||||||
except IOError:
|
except IOError:
|
||||||
# Handle directory paths
|
# Handle directory paths
|
||||||
return name
|
return name
|
||||||
path, filename = os.path.split(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
|
# Get the MD5 hash of the file
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
for chunk in content.chunks():
|
for chunk in content.chunks():
|
||||||
md5.update(chunk)
|
md5.update(chunk)
|
||||||
md5sum = md5.hexdigest()[:12]
|
md5sum = md5.hexdigest()[:12]
|
||||||
return os.path.join(path, u"%s.%s%s" % (root, md5sum, ext))
|
hashed_name = os.path.join(path, u"%s.%s%s" % (root, md5sum, ext))
|
||||||
|
unparsed_name = list(parsed_name)
|
||||||
|
unparsed_name[2] = hashed_name
|
||||||
|
return urlunsplit(unparsed_name)
|
||||||
|
|
||||||
def cache_key(self, name):
|
def cache_key(self, name):
|
||||||
return u'staticfiles:cache:%s' % name
|
return u'staticfiles:cache:%s' % name
|
||||||
|
@ -98,7 +105,7 @@ class CachedFilesMixin(object):
|
||||||
hashed_name = self.hashed_name(name)
|
hashed_name = self.hashed_name(name)
|
||||||
# set the cache if there was a miss (e.g. if cache server goes down)
|
# set the cache if there was a miss (e.g. if cache server goes down)
|
||||||
self.cache.set(cache_key, hashed_name)
|
self.cache.set(cache_key, hashed_name)
|
||||||
return super(CachedFilesMixin, self).url(hashed_name)
|
return unquote(super(CachedFilesMixin, self).url(hashed_name))
|
||||||
|
|
||||||
def url_converter(self, name):
|
def url_converter(self, name):
|
||||||
"""
|
"""
|
||||||
|
@ -132,9 +139,9 @@ class CachedFilesMixin(object):
|
||||||
else:
|
else:
|
||||||
start, end = 1, sub_level - 1
|
start, end = 1, sub_level - 1
|
||||||
joined_result = '/'.join(name_parts[:-start] + url_parts[end:])
|
joined_result = '/'.join(name_parts[:-start] + url_parts[end:])
|
||||||
hashed_url = self.url(joined_result, force=True)
|
hashed_url = self.url(unquote(joined_result), force=True)
|
||||||
# Return the hashed and normalized version to the file
|
# Return the hashed and normalized version to the file
|
||||||
return 'url("%s")' % hashed_url
|
return 'url("%s")' % unquote(hashed_url)
|
||||||
return converter
|
return converter
|
||||||
|
|
||||||
def post_process(self, paths, dry_run=False, **options):
|
def post_process(self, paths, dry_run=False, **options):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
@import url("../cached/styles.css");
|
@import url("../cached/styles.css");
|
||||||
@import url("absolute.css");
|
@import url("absolute.css");
|
||||||
|
@import url("absolute.css#eggs");
|
||||||
body {
|
body {
|
||||||
background: #d3d6d8 url(img/relative.png);
|
background: #d3d6d8 url(img/relative.png);
|
||||||
}
|
}
|
|
@ -61,7 +61,7 @@ class BaseStaticFilesTestCase(object):
|
||||||
self.addCleanup(os.unlink, _nonascii_filepath)
|
self.addCleanup(os.unlink, _nonascii_filepath)
|
||||||
|
|
||||||
def assertFileContains(self, filepath, text):
|
def assertFileContains(self, filepath, text):
|
||||||
self.assertTrue(text in self._get_file(smart_unicode(filepath)),
|
self.assertIn(text, self._get_file(smart_unicode(filepath)),
|
||||||
u"'%s' not in '%s'" % (text, filepath))
|
u"'%s' not in '%s'" % (text, filepath))
|
||||||
|
|
||||||
def assertFileNotFound(self, filepath):
|
def assertFileNotFound(self, filepath):
|
||||||
|
@ -72,11 +72,15 @@ class BaseStaticFilesTestCase(object):
|
||||||
template = loader.get_template_from_string(template)
|
template = loader.get_template_from_string(template)
|
||||||
return template.render(Context(kwargs)).strip()
|
return template.render(Context(kwargs)).strip()
|
||||||
|
|
||||||
def assertTemplateRenders(self, template, result, **kwargs):
|
def static_template_snippet(self, path):
|
||||||
|
return "{%% load static from staticfiles %%}{%% static '%s' %%}" % path
|
||||||
|
|
||||||
|
def assertStaticRenders(self, path, result, **kwargs):
|
||||||
|
template = self.static_template_snippet(path)
|
||||||
self.assertEqual(self.render_template(template, **kwargs), result)
|
self.assertEqual(self.render_template(template, **kwargs), result)
|
||||||
|
|
||||||
def assertTemplateRaises(self, exc, template, result, **kwargs):
|
def assertStaticRaises(self, exc, path, result, **kwargs):
|
||||||
self.assertRaises(exc, self.assertTemplateRenders, template, result, **kwargs)
|
self.assertRaises(exc, self.assertStaticRenders, path, result, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class StaticFilesTestCase(BaseStaticFilesTestCase, TestCase):
|
class StaticFilesTestCase(BaseStaticFilesTestCase, TestCase):
|
||||||
|
@ -99,8 +103,8 @@ class BaseCollectionTestCase(BaseStaticFilesTestCase):
|
||||||
settings.STATIC_ROOT = tempfile.mkdtemp()
|
settings.STATIC_ROOT = tempfile.mkdtemp()
|
||||||
self.run_collectstatic()
|
self.run_collectstatic()
|
||||||
# Use our own error handler that can handle .svn dirs on Windows
|
# Use our own error handler that can handle .svn dirs on Windows
|
||||||
self.addCleanup(shutil.rmtree, settings.STATIC_ROOT,
|
#self.addCleanup(shutil.rmtree, settings.STATIC_ROOT,
|
||||||
ignore_errors=True, onerror=rmtree_errorhandler)
|
# ignore_errors=True, onerror=rmtree_errorhandler)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
settings.STATIC_ROOT = self.old_root
|
settings.STATIC_ROOT = self.old_root
|
||||||
|
@ -194,8 +198,8 @@ class TestFindStatic(CollectionTestCase, TestDefaults):
|
||||||
finally:
|
finally:
|
||||||
sys.stdout = _stdout
|
sys.stdout = _stdout
|
||||||
self.assertEqual(len(lines), 3) # three because there is also the "Found <file> here" line
|
self.assertEqual(len(lines), 3) # three because there is also the "Found <file> here" line
|
||||||
self.assertTrue('project' in lines[1])
|
self.assertIn('project', lines[1])
|
||||||
self.assertTrue('apps' in lines[2])
|
self.assertIn('apps', lines[2])
|
||||||
|
|
||||||
|
|
||||||
class TestCollection(CollectionTestCase, TestDefaults):
|
class TestCollection(CollectionTestCase, TestDefaults):
|
||||||
|
@ -284,73 +288,90 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
|
||||||
"""
|
"""
|
||||||
Tests for the Cache busting storage
|
Tests for the Cache busting storage
|
||||||
"""
|
"""
|
||||||
def cached_file_path(self, relpath):
|
def cached_file_path(self, path):
|
||||||
template = "{%% load static from staticfiles %%}{%% static '%s' %%}"
|
fullpath = self.render_template(self.static_template_snippet(path))
|
||||||
fullpath = self.render_template(template % relpath)
|
|
||||||
return fullpath.replace(settings.STATIC_URL, '')
|
return fullpath.replace(settings.STATIC_URL, '')
|
||||||
|
|
||||||
def test_template_tag_return(self):
|
def test_template_tag_return(self):
|
||||||
"""
|
"""
|
||||||
Test the CachedStaticFilesStorage backend.
|
Test the CachedStaticFilesStorage backend.
|
||||||
"""
|
"""
|
||||||
self.assertTemplateRaises(ValueError, """
|
self.assertStaticRaises(ValueError,
|
||||||
{% load static from staticfiles %}{% static "does/not/exist.png" %}
|
"does/not/exist.png",
|
||||||
""", "/static/does/not/exist.png")
|
"/static/does/not/exist.png")
|
||||||
self.assertTemplateRenders("""
|
self.assertStaticRenders("test/file.txt",
|
||||||
{% load static from staticfiles %}{% static "test/file.txt" %}
|
"/static/test/file.dad0999e4f8f.txt")
|
||||||
""", "/static/test/file.dad0999e4f8f.txt")
|
self.assertStaticRenders("cached/styles.css",
|
||||||
self.assertTemplateRenders("""
|
"/static/cached/styles.93b1147e8552.css")
|
||||||
{% load static from staticfiles %}{% static "cached/styles.css" %}
|
|
||||||
""", "/static/cached/styles.93b1147e8552.css")
|
|
||||||
|
|
||||||
def test_template_tag_simple_content(self):
|
def test_template_tag_simple_content(self):
|
||||||
relpath = self.cached_file_path("cached/styles.css")
|
relpath = self.cached_file_path("cached/styles.css")
|
||||||
self.assertEqual(relpath, "cached/styles.93b1147e8552.css")
|
self.assertEqual(relpath, "cached/styles.93b1147e8552.css")
|
||||||
with storage.staticfiles_storage.open(relpath) as relfile:
|
with storage.staticfiles_storage.open(relpath) as relfile:
|
||||||
content = relfile.read()
|
content = relfile.read()
|
||||||
self.assertFalse("cached/other.css" in content, content)
|
self.assertNotIn("cached/other.css", content)
|
||||||
self.assertTrue("/static/cached/other.d41d8cd98f00.css" in content)
|
self.assertIn("/static/cached/other.d41d8cd98f00.css", content)
|
||||||
|
|
||||||
|
def test_path_with_querystring(self):
|
||||||
|
relpath = self.cached_file_path("cached/styles.css?spam=eggs")
|
||||||
|
self.assertEqual(relpath,
|
||||||
|
"cached/styles.93b1147e8552.css?spam=eggs")
|
||||||
|
with storage.staticfiles_storage.open(
|
||||||
|
"cached/styles.93b1147e8552.css") as relfile:
|
||||||
|
content = relfile.read()
|
||||||
|
self.assertNotIn("cached/other.css", content)
|
||||||
|
self.assertIn("/static/cached/other.d41d8cd98f00.css", content)
|
||||||
|
|
||||||
|
def test_path_with_fragment(self):
|
||||||
|
relpath = self.cached_file_path("cached/styles.css#eggs")
|
||||||
|
self.assertEqual(relpath, "cached/styles.93b1147e8552.css#eggs")
|
||||||
|
with storage.staticfiles_storage.open(
|
||||||
|
"cached/styles.93b1147e8552.css") as relfile:
|
||||||
|
content = relfile.read()
|
||||||
|
self.assertNotIn("cached/other.css", content)
|
||||||
|
self.assertIn("/static/cached/other.d41d8cd98f00.css", content)
|
||||||
|
|
||||||
def test_template_tag_absolute(self):
|
def test_template_tag_absolute(self):
|
||||||
relpath = self.cached_file_path("cached/absolute.css")
|
relpath = self.cached_file_path("cached/absolute.css")
|
||||||
self.assertEqual(relpath, "cached/absolute.cc80cb5e2eb1.css")
|
self.assertEqual(relpath, "cached/absolute.cc80cb5e2eb1.css")
|
||||||
with storage.staticfiles_storage.open(relpath) as relfile:
|
with storage.staticfiles_storage.open(relpath) as relfile:
|
||||||
content = relfile.read()
|
content = relfile.read()
|
||||||
self.assertFalse("/static/cached/styles.css" in content)
|
self.assertNotIn("/static/cached/styles.css", content)
|
||||||
self.assertTrue("/static/cached/styles.93b1147e8552.css" in content)
|
self.assertIn("/static/cached/styles.93b1147e8552.css", content)
|
||||||
|
|
||||||
def test_template_tag_denorm(self):
|
def test_template_tag_denorm(self):
|
||||||
relpath = self.cached_file_path("cached/denorm.css")
|
relpath = self.cached_file_path("cached/denorm.css")
|
||||||
self.assertEqual(relpath, "cached/denorm.363de96e9b4b.css")
|
self.assertEqual(relpath, "cached/denorm.363de96e9b4b.css")
|
||||||
with storage.staticfiles_storage.open(relpath) as relfile:
|
with storage.staticfiles_storage.open(relpath) as relfile:
|
||||||
content = relfile.read()
|
content = relfile.read()
|
||||||
self.assertFalse("..//cached///styles.css" in content)
|
self.assertNotIn("..//cached///styles.css", content)
|
||||||
self.assertTrue("/static/cached/styles.93b1147e8552.css" in content)
|
self.assertIn("/static/cached/styles.93b1147e8552.css", content)
|
||||||
|
|
||||||
def test_template_tag_relative(self):
|
def test_template_tag_relative(self):
|
||||||
relpath = self.cached_file_path("cached/relative.css")
|
relpath = self.cached_file_path("cached/relative.css")
|
||||||
self.assertEqual(relpath, "cached/relative.8dffb45d91f5.css")
|
self.assertEqual(relpath, "cached/relative.2217ea7273c2.css")
|
||||||
with storage.staticfiles_storage.open(relpath) as relfile:
|
with storage.staticfiles_storage.open(relpath) as relfile:
|
||||||
content = relfile.read()
|
content = relfile.read()
|
||||||
self.assertFalse("../cached/styles.css" in content)
|
self.assertNotIn("../cached/styles.css", content)
|
||||||
self.assertFalse('@import "styles.css"' in content)
|
self.assertNotIn('@import "styles.css"', content)
|
||||||
self.assertTrue("/static/cached/styles.93b1147e8552.css" in content)
|
self.assertIn("/static/cached/styles.93b1147e8552.css", content)
|
||||||
self.assertFalse("url(img/relative.png)" in content)
|
self.assertNotIn("url(img/relative.png)", content)
|
||||||
self.assertTrue("/static/cached/img/relative.acae32e4532b.png" in content)
|
self.assertIn("/static/cached/img/relative.acae32e4532b.png", content)
|
||||||
|
self.assertIn("/static/cached/absolute.cc80cb5e2eb1.css#eggs", content)
|
||||||
|
|
||||||
def test_template_tag_deep_relative(self):
|
def test_template_tag_deep_relative(self):
|
||||||
relpath = self.cached_file_path("cached/css/window.css")
|
relpath = self.cached_file_path("cached/css/window.css")
|
||||||
self.assertEqual(relpath, "cached/css/window.9db38d5169f3.css")
|
self.assertEqual(relpath, "cached/css/window.9db38d5169f3.css")
|
||||||
with storage.staticfiles_storage.open(relpath) as relfile:
|
with storage.staticfiles_storage.open(relpath) as relfile:
|
||||||
content = relfile.read()
|
content = relfile.read()
|
||||||
self.assertFalse('url(img/window.png)' in content)
|
self.assertNotIn('url(img/window.png)', content)
|
||||||
self.assertTrue('url("/static/cached/css/img/window.acae32e4532b.png")' in content)
|
self.assertIn('url("/static/cached/css/img/window.acae32e4532b.png")', content)
|
||||||
|
|
||||||
def test_template_tag_url(self):
|
def test_template_tag_url(self):
|
||||||
relpath = self.cached_file_path("cached/url.css")
|
relpath = self.cached_file_path("cached/url.css")
|
||||||
self.assertEqual(relpath, "cached/url.615e21601e4b.css")
|
self.assertEqual(relpath, "cached/url.615e21601e4b.css")
|
||||||
with storage.staticfiles_storage.open(relpath) as relfile:
|
with storage.staticfiles_storage.open(relpath) as relfile:
|
||||||
self.assertTrue("https://" in relfile.read())
|
self.assertIn("https://", relfile.read())
|
||||||
|
|
||||||
def test_cache_invalidation(self):
|
def test_cache_invalidation(self):
|
||||||
name = "cached/styles.css"
|
name = "cached/styles.css"
|
||||||
|
@ -367,7 +388,6 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
|
||||||
cached_name = storage.staticfiles_storage.cache.get(cache_key)
|
cached_name = storage.staticfiles_storage.cache.get(cache_key)
|
||||||
self.assertEqual(cached_name, hashed_name)
|
self.assertEqual(cached_name, hashed_name)
|
||||||
|
|
||||||
|
|
||||||
# we set DEBUG to False here since the template tag wouldn't work otherwise
|
# we set DEBUG to False here since the template tag wouldn't work otherwise
|
||||||
TestCollectionCachedStorage = override_settings(**dict(TEST_SETTINGS,
|
TestCollectionCachedStorage = override_settings(**dict(TEST_SETTINGS,
|
||||||
STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage',
|
STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage',
|
||||||
|
@ -517,9 +537,9 @@ class TestMiscFinder(TestCase):
|
||||||
default_storage._wrapped = empty
|
default_storage._wrapped = empty
|
||||||
|
|
||||||
def test_get_finder(self):
|
def test_get_finder(self):
|
||||||
self.assertTrue(isinstance(finders.get_finder(
|
self.assertIsInstance(finders.get_finder(
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder'),
|
'django.contrib.staticfiles.finders.FileSystemFinder'),
|
||||||
finders.FileSystemFinder))
|
finders.FileSystemFinder)
|
||||||
|
|
||||||
def test_get_finder_bad_classname(self):
|
def test_get_finder_bad_classname(self):
|
||||||
self.assertRaises(ImproperlyConfigured, finders.get_finder,
|
self.assertRaises(ImproperlyConfigured, finders.get_finder,
|
||||||
|
@ -545,9 +565,6 @@ class TestMiscFinder(TestCase):
|
||||||
class TestTemplateTag(StaticFilesTestCase):
|
class TestTemplateTag(StaticFilesTestCase):
|
||||||
|
|
||||||
def test_template_tag(self):
|
def test_template_tag(self):
|
||||||
self.assertTemplateRenders("""
|
self.assertStaticRenders("does/not/exist.png",
|
||||||
{% load static from staticfiles %}{% static "does/not/exist.png" %}
|
"/static/does/not/exist.png")
|
||||||
""", "/static/does/not/exist.png")
|
self.assertStaticRenders("testfile.txt", "/static/testfile.txt")
|
||||||
self.assertTemplateRenders("""
|
|
||||||
{% load static from staticfiles %}{% static "testfile.txt" %}
|
|
||||||
""", "/static/testfile.txt")
|
|
||||||
|
|
Loading…
Reference in New Issue