diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index c0cc100ca8c..1b2060de133 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -12,7 +12,7 @@ from django.core.cache import (get_cache, InvalidCacheBackendError, from django.core.exceptions import ImproperlyConfigured from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage, get_storage_class -from django.utils.encoding import force_unicode +from django.utils.encoding import force_unicode, smart_str from django.utils.functional import LazyObject from django.utils.importlib import import_module from django.utils.datastructures import SortedDict @@ -84,9 +84,14 @@ class CachedFilesMixin(object): 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)) + hashed_name = os.path.join(path, u"%s.%s%s" % + (root, md5sum, ext)) unparsed_name = list(parsed_name) unparsed_name[2] = hashed_name + # Special casing for a @font-face hack, like url(myfont.eot?#iefix") + # http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax + if '?#' in name and not unparsed_name[3]: + unparsed_name[2] += '?' return urlunsplit(unparsed_name) def cache_key(self, name): @@ -103,7 +108,8 @@ class CachedFilesMixin(object): hashed_name = self.cache.get(cache_key) if hashed_name is None: hashed_name = self.hashed_name(name).replace('\\', '/') - # 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) return unquote(super(CachedFilesMixin, self).url(hashed_name)) @@ -119,7 +125,7 @@ class CachedFilesMixin(object): """ matched, url = matchobj.groups() # Completely ignore http(s) prefixed URLs - if url.startswith(('http', 'https')): + if url.startswith(('#', 'http', 'https', 'data:')): return matched name_parts = name.split(os.sep) # Using posix normpath here to remove duplicates @@ -182,7 +188,8 @@ class CachedFilesMixin(object): if self.exists(hashed_name): self.delete(hashed_name) - saved_name = self._save(hashed_name, ContentFile(content)) + content_file = ContentFile(smart_str(content)) + saved_name = self._save(hashed_name, content_file) hashed_name = force_unicode(saved_name.replace('\\', '/')) processed_files.append(hashed_name) diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.eot b/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.eot new file mode 100644 index 00000000000..7c58b2e6226 --- /dev/null +++ b/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.eot @@ -0,0 +1 @@ +not really a EOT ;) \ No newline at end of file diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.svg b/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.svg new file mode 100644 index 00000000000..02823759153 --- /dev/null +++ b/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fonts/font.svg @@ -0,0 +1 @@ +not really a SVG ;) \ No newline at end of file diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fragments.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fragments.css new file mode 100644 index 00000000000..540d54b88db --- /dev/null +++ b/tests/regressiontests/staticfiles_tests/project/documents/cached/css/fragments.css @@ -0,0 +1,8 @@ +@font-face { + src: url('fonts/font.eot?#iefix') format('embedded-opentype'), + url('fonts/font.svg#webfontIyfZbseF') format('svg'); + url('data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA'); +} +div { + behavior: url("#default#VML"); +} diff --git a/tests/regressiontests/staticfiles_tests/storage.py b/tests/regressiontests/staticfiles_tests/storage.py index cfe1e13bdb0..8358f833c8f 100644 --- a/tests/regressiontests/staticfiles_tests/storage.py +++ b/tests/regressiontests/staticfiles_tests/storage.py @@ -4,7 +4,7 @@ from django.core.files import storage class DummyStorage(storage.Storage): """ A storage class that does implement modified_time() but raises - NotImplementedError when calling + NotImplementedError when calling """ def _save(self, name, content): return 'dummy' diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 5d411573515..bd22ea9f3e2 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -331,6 +331,16 @@ class TestCollectionCachedStorage(BaseCollectionTestCase, self.assertNotIn("cached/other.css", content) self.assertIn("/static/cached/other.d41d8cd98f00.css", content) + def test_path_with_querystring_and_fragment(self): + relpath = self.cached_file_path("cached/css/fragments.css") + self.assertEqual(relpath, "cached/css/fragments.75433540b096.css") + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + self.assertIn('/static/cached/css/fonts/font.a4b0478549d0.eot?#iefix', content) + self.assertIn('/static/cached/css/fonts/font.b8d603e42714.svg#webfontIyfZbseF', content) + self.assertIn('data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA', content) + self.assertIn('#default#VML', content) + def test_template_tag_absolute(self): relpath = self.cached_file_path("cached/absolute.css") self.assertEqual(relpath, "cached/absolute.cc80cb5e2eb1.css")