Merge remote branch 'muhuk/master'

Conflicts:
	compressor/__init__.py
	tests/core/tests.py
This commit is contained in:
Jannis Leidel
2010-02-23 13:39:55 +01:00
parent 1f9bd58344
commit 55acb2da06
9 changed files with 69 additions and 38 deletions

4
.gitignore vendored
View File

@@ -1,4 +1,6 @@
build
*CACHE*
dist
MANIFEST
MANIFEST
*.pyc
*.egg-info

View File

@@ -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:**

View File

@@ -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):

View File

@@ -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:

View File

@@ -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)

View File

@@ -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')

View File

@@ -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" />

View File

@@ -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
View File

@@ -0,0 +1 @@
var test_value = "—";