Merge remote branch 'muhuk/master'
Conflicts: compressor/__init__.py tests/core/tests.py
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
build
|
||||
*CACHE*
|
||||
dist
|
||||
MANIFEST
|
||||
MANIFEST
|
||||
*.pyc
|
||||
*.egg-info
|
10
README.rst
10
README.rst
@@ -19,7 +19,7 @@ Examples::
|
||||
|
||||
Which would be rendered something like::
|
||||
|
||||
<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css" media="all" charset="utf-8">
|
||||
<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css" charset="utf-8">
|
||||
|
||||
or::
|
||||
|
||||
@@ -50,8 +50,12 @@ starting with a '/') are left alone.
|
||||
Stylesheets that are @import'd are not compressed into the main file. They are
|
||||
left alone.
|
||||
|
||||
Set the media attribute as normal on your <style> and <link> elements and
|
||||
the combined CSS will be wrapped in @media blocks as necessary.
|
||||
If the media attribute is set on <style> and <link> elements, a separate
|
||||
compressed file is created and linked for each media value you specified.
|
||||
This allows the media attribute to remain on the generated link element,
|
||||
instead of wrapping your CSS with @media blocks (which can break your own
|
||||
@media queries or @font-face declarations). It also allows browsers to avoid
|
||||
downloading CSS for irrelevant media types.
|
||||
|
||||
**Recommendations:**
|
||||
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
|
||||
from django import template
|
||||
from django.conf import settings as django_settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.functional import curry
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import get_storage_class
|
||||
@@ -56,7 +58,7 @@ class Compressor(object):
|
||||
def cachekey(self):
|
||||
cachebits = [self.content]
|
||||
cachebits.extend([str(m) for m in self.mtimes])
|
||||
cachestr = "".join(cachebits)
|
||||
cachestr = "".join(cachebits).encode(django_settings.DEFAULT_CHARSET)
|
||||
return "django_compressor.%s" % get_hexdigest(cachestr)[:12]
|
||||
|
||||
@property
|
||||
@@ -73,21 +75,24 @@ class Compressor(object):
|
||||
input = v
|
||||
if self.filters:
|
||||
input = self.filter(input, 'input', elem=elem)
|
||||
self._hunks.append(input)
|
||||
# Let's cast BeautifulSoup element to unicode here since
|
||||
# it will try to encode using ascii internally later
|
||||
self._hunks.append(unicode(input))
|
||||
if kind == 'file':
|
||||
# TODO: wrap this in a try/except for IoErrors(?)
|
||||
fd = open(v, 'rb')
|
||||
input = fd.read()
|
||||
if self.filters:
|
||||
input = self.filter(input, 'input', filename=v, elem=elem)
|
||||
self._hunks.append(input)
|
||||
self._hunks.append(unicode(input, elem.get('charset', django_settings.DEFAULT_CHARSET)))
|
||||
fd.close()
|
||||
return self._hunks
|
||||
|
||||
def concat(self):
|
||||
# if any of the hunks are unicode, all of them will be coerced
|
||||
# this breaks any hunks with non-ASCII data in them
|
||||
return "\n".join([str(hunk) for hunk in self.hunks])
|
||||
# Design decision needed: either everything should be unicode up to
|
||||
# here or we encode strings as soon as we acquire them. Currently
|
||||
# concat() expects all hunks to be unicode and does the encoding
|
||||
return "\n".join([hunk.encode(django_settings.DEFAULT_CHARSET) for hunk in self.hunks])
|
||||
|
||||
def filter(self, content, method, **kwargs):
|
||||
content = content
|
||||
@@ -139,10 +144,7 @@ class CssCompressor(Compressor):
|
||||
def __init__(self, content, output_prefix="css"):
|
||||
self.extension = ".css"
|
||||
self.template_name = "compressor/css.html"
|
||||
self.filters = [
|
||||
'compressor.filters.css_default.CssAbsoluteFilter',
|
||||
'compressor.filters.css_default.CssMediaFilter',
|
||||
]
|
||||
self.filters = ['compressor.filters.css_default.CssAbsoluteFilter']
|
||||
self.filters.extend(settings.COMPRESS_CSS_FILTERS)
|
||||
self.type = 'css'
|
||||
super(CssCompressor, self).__init__(content, output_prefix)
|
||||
@@ -151,17 +153,34 @@ class CssCompressor(Compressor):
|
||||
if self.split_content:
|
||||
return self.split_content
|
||||
split = self.soup.findAll({'link' : True, 'style' : True})
|
||||
self.by_media = defaultdict(curry(CssCompressor, content=''))
|
||||
for elem in split:
|
||||
data = None
|
||||
if elem.name == 'link' and elem['rel'] == 'stylesheet':
|
||||
try:
|
||||
self.split_content.append(('file', self.get_filename(elem['href']), elem))
|
||||
data = ('file', self.get_filename(elem['href']), elem)
|
||||
except UncompressableFileError:
|
||||
if django_settings.DEBUG:
|
||||
raise
|
||||
if elem.name == 'style':
|
||||
self.split_content.append(('hunk', elem.string, elem))
|
||||
elif elem.name == 'style':
|
||||
data = ('hunk', elem.string, elem)
|
||||
if data:
|
||||
self.split_content.append(data)
|
||||
self.by_media[elem.get('media', None)].split_content.append(data)
|
||||
return self.split_content
|
||||
|
||||
def output(self):
|
||||
self.split_contents()
|
||||
if not hasattr(self, 'by_media'):
|
||||
return super(CssCompressor, self).output()
|
||||
if not settings.COMPRESS:
|
||||
return self.content
|
||||
ret = []
|
||||
for media, subnode in self.by_media.items():
|
||||
subnode.extra_context = {'media': media}
|
||||
ret.append(subnode.output())
|
||||
return ''.join(ret)
|
||||
|
||||
|
||||
class JsCompressor(Compressor):
|
||||
|
||||
|
@@ -8,7 +8,7 @@ OUTPUT_DIR = getattr(settings, 'COMPRESS_OUTPUT_DIR', 'CACHE')
|
||||
STORAGE = getattr(settings, 'COMPRESS_STORAGE', 'compressor.storage.CompressorFileStorage')
|
||||
|
||||
COMPRESS = getattr(settings, 'COMPRESS', not settings.DEBUG)
|
||||
COMPRESS_CSS_FILTERS = getattr(settings, 'COMPRESS_CSS_FILTERS', [])
|
||||
COMPRESS_CSS_FILTERS = getattr(settings, 'COMPRESS_CSS_FILTERS', ['compressor.filters.css_default.CssAbsoluteFilter'])
|
||||
COMPRESS_JS_FILTERS = getattr(settings, 'COMPRESS_JS_FILTERS', ['compressor.filters.jsmin.JSMinFilter'])
|
||||
|
||||
if COMPRESS_CSS_FILTERS is None:
|
||||
|
@@ -35,12 +35,3 @@ class CssAbsoluteFilter(FilterBase):
|
||||
if self.has_http:
|
||||
full_url = "%s%s" % (self.protocol,full_url)
|
||||
return "url('%s')" % full_url
|
||||
|
||||
|
||||
class CssMediaFilter(FilterBase):
|
||||
def input(self, elem=None, **kwargs):
|
||||
try:
|
||||
self.media = elem['media']
|
||||
except (TypeError, KeyError):
|
||||
return self.content
|
||||
return "@media %s {%s}" % (str(self.media), self.content)
|
||||
|
@@ -14,7 +14,7 @@ warnings.simplefilter('ignore', RuntimeWarning)
|
||||
class CSSTidyFilter(FilterBase):
|
||||
def output(self, **kwargs):
|
||||
tmp_file = tempfile.NamedTemporaryFile(mode='w+b')
|
||||
tmp_file.write(css)
|
||||
tmp_file.write(self.content)
|
||||
tmp_file.flush()
|
||||
|
||||
output_file = tempfile.NamedTemporaryFile(mode='w+b')
|
||||
|
@@ -1 +1 @@
|
||||
<link rel="stylesheet" href="{{ url }}" type="text/css" media="all" charset="utf-8">
|
||||
<link rel="stylesheet" href="{{ url }}" type="text/css" {% if media %}media="{{ media }}" {% endif %}charset="utf-8" />
|
||||
|
@@ -64,8 +64,8 @@ class CompressorTestCase(TestCase):
|
||||
self.assertEqual('f7c661b7a124', self.cssNode.hash)
|
||||
|
||||
def test_css_return_if_on(self):
|
||||
output = u'<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css" media="all" charset="utf-8">'
|
||||
self.assertEqual(output, self.cssNode.output())
|
||||
output = u'<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css" charset="utf-8" />'
|
||||
self.assertEqual(output, self.cssNode.output().strip())
|
||||
|
||||
|
||||
def test_js_split(self):
|
||||
@@ -144,12 +144,15 @@ class CssMediaTestCase(TestCase):
|
||||
<link rel="stylesheet" href="/media/css/one.css" type="text/css" media="screen" charset="utf-8">
|
||||
<style type="text/css" media="print">p { border:5px solid green;}</style>
|
||||
<link rel="stylesheet" href="/media/css/two.css" type="text/css" charset="utf-8" media="all">
|
||||
<style type="text/css">h1 { border:5px solid green;}</style>
|
||||
"""
|
||||
self.cssNode = CssCompressor(self.css)
|
||||
|
||||
def test_css_output(self):
|
||||
out = u'@media screen {body { background:#990; }}\n@media print {p { border:5px solid green;}}\n@media all {body { color:#fff; }}'
|
||||
self.assertEqual(out, self.cssNode.combined)
|
||||
links = BeautifulSoup(self.cssNode.output()).findAll('link')
|
||||
media = set([u'screen', u'print', u'all', None])
|
||||
self.assertEqual(len(links), 4)
|
||||
self.assertEqual(media, set([l.get('media', None) for l in links]))
|
||||
|
||||
def render(template_string, context_dict=None):
|
||||
"""A shortcut for testing template output."""
|
||||
@@ -163,7 +166,7 @@ def render(template_string, context_dict=None):
|
||||
class TemplatetagTestCase(TestCase):
|
||||
def setUp(self):
|
||||
settings.COMPRESS = True
|
||||
|
||||
|
||||
def test_css_tag(self):
|
||||
template = u"""{% load compress %}{% compress css %}
|
||||
<link rel="stylesheet" href="{{ MEDIA_URL }}css/one.css" type="text/css" charset="utf-8">
|
||||
@@ -172,17 +175,17 @@ class TemplatetagTestCase(TestCase):
|
||||
{% endcompress %}
|
||||
"""
|
||||
context = { 'MEDIA_URL': settings.MEDIA_URL }
|
||||
out = u'<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css" media="all" charset="utf-8">'
|
||||
out = u'<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css" charset="utf-8" />'
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
||||
def test_nonascii_css_tag(self):
|
||||
template = u"""{% load compress %}{% compress css %}
|
||||
<link rel="stylesheet" href="{{ MEDIA_URL }}css/nonasc.css" type="text/css" media="print" charset="utf-8">
|
||||
<link rel="stylesheet" href="{{ MEDIA_URL }}css/nonasc.css" type="text/css" charset="utf-8">
|
||||
<style type="text/css">p { border:5px solid green;}</style>
|
||||
{% endcompress %}
|
||||
"""
|
||||
context = { 'MEDIA_URL': settings.MEDIA_URL }
|
||||
out = '<link rel="stylesheet" href="/media/CACHE/css/68da639dbb24.css" type="text/css" media="all" charset="utf-8">'
|
||||
out = '<link rel="stylesheet" href="/media/CACHE/css/1c1c0855907b.css" type="text/css" charset="utf-8" />'
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
||||
def test_js_tag(self):
|
||||
@@ -195,6 +198,17 @@ class TemplatetagTestCase(TestCase):
|
||||
out = u'<script type="text/javascript" src="/media/CACHE/js/3f33b9146e12.js" charset="utf-8"></script>'
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
||||
def test_nonascii_js_tag(self):
|
||||
template = u"""{% load compress %}{% compress js %}
|
||||
<script src="{{ MEDIA_URL }}js/nonasc.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script type="text/javascript" charset="utf-8">var test_value = "\u2014";</script>
|
||||
{% endcompress %}
|
||||
"""
|
||||
context = { 'MEDIA_URL': settings.MEDIA_URL }
|
||||
out = u'<script type="text/javascript" src="/media/CACHE/js/5d5c0e1cb25f.js" charset="utf-8"></script>'
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
||||
|
||||
class TestStorage(CompressorFileStorage):
|
||||
"""
|
||||
Test compressor storage that gzips storage files
|
||||
@@ -225,5 +239,5 @@ class StorageTestCase(TestCase):
|
||||
{% endcompress %}
|
||||
"""
|
||||
context = { 'MEDIA_URL': settings.MEDIA_URL }
|
||||
out = u'<link rel="stylesheet" href="/media/CACHE/css/5b231a62e9a6.css.gz" type="text/css" media="all" charset="utf-8">'
|
||||
out = u'<link rel="stylesheet" href="/media/CACHE/css/5b231a62e9a6.css.gz" type="text/css" charset="utf-8" />'
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
1
tests/media/js/nonasc.js
Normal file
1
tests/media/js/nonasc.js
Normal file
@@ -0,0 +1 @@
|
||||
var test_value = "—";
|
Reference in New Issue
Block a user