Made Compressor.concat a cached property instead of a method and use it in the hash and combined properties. Refactored Compressor.outline method to proxy to mode specific methods.

This commit is contained in:
Jannis Leidel
2011-04-07 16:00:34 +02:00
parent c8329c119b
commit fc50651260
10 changed files with 89 additions and 56 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
build
compressor/tests/media/CACHE
compressor/tests/media/custom
compressor/tests/media/js/3f33b9146e12.js
dist
MANIFEST
*.pyc

View File

@@ -9,7 +9,7 @@ from django.template.loader import render_to_string
from compressor.cache import get_hexdigest, get_mtime
from compressor.conf import settings
from compressor.exceptions import UncompressableFileError
from compressor.exceptions import CompressorError, UncompressableFileError
from compressor.filters import CompilerFilter
from compressor.storage import default_storage
from compressor.utils import get_class, cached_property
@@ -27,6 +27,7 @@ class Compressor(object):
self.output_prefix = output_prefix
self.charset = settings.DEFAULT_CHARSET
self.precompilers = settings.COMPRESS_PRECOMPILERS
self.storage = default_storage
self.split_content = []
self.extra_context = {}
@@ -73,10 +74,6 @@ class Compressor(object):
return "django_compressor.%s.%s" % (socket.gethostname(),
get_hexdigest(cachestr)[:12])
@cached_property
def storage(self):
return default_storage
@cached_property
def hunks(self):
for kind, value, elem in self.split_contents():
@@ -102,8 +99,9 @@ class Compressor(object):
charset = attribs.get("charset", self.charset)
yield unicode(content, charset)
@cached_property
def concat(self):
return "\n".join((hunk.encode(self.charset) for hunk in self.hunks))
return '\n'.join((hunk.encode(self.charset) for hunk in self.hunks))
def matches_patterns(self, path, patterns=[]):
"""
@@ -159,37 +157,65 @@ class Compressor(object):
@cached_property
def combined(self):
return self.filter(self.concat(), method="output")
return self.filter(self.concat, method="output")
@cached_property
def hash(self):
return get_hexdigest(self.concat())[:12]
def hash(self, content):
return get_hexdigest(content)[:12]
@cached_property
def new_filepath(self):
def filepath(self, content):
return os.path.join(settings.COMPRESS_OUTPUT_DIR.strip(os.sep),
self.output_prefix, "%s.%s" % (self.hash, self.type))
self.output_prefix, "%s.%s" % (self.hash(content), self.type))
def save_file(self):
if self.storage.exists(self.new_filepath):
return False
self.storage.save(self.new_filepath, ContentFile(self.combined))
return True
def output(self, forced=False):
if not settings.COMPRESS_ENABLED and not forced:
return self.content
context = {
"saved": self.save_file(),
"url": self.storage.url(self.new_filepath),
}
context.update(self.extra_context)
return render_to_string(self.template_name, context)
def output_inline(self):
if settings.COMPRESS_ENABLED:
def output(self, mode='file', forced=False):
"""
The general output method, override in subclass if you need to do
any custom modification. Calls other mode specific methods or simply
returns the content directly.
"""
# First check whether we should do the full compression,
# including precompilation (or if it's forced)
if settings.COMPRESS_ENABLED or forced:
content = self.combined
elif self.precompilers:
# or concatting it, if pre-compilation is enabled
content = self.concat
else:
content = self.concat()
context = dict(content=content, **self.extra_context)
return render_to_string(self.template_name_inline, context)
# or just doing nothing, when neither
# compression nor compilation is enabled
return self.content
# Then check for the appropriate output method and call it
output_func = getattr(self, "output_%s" % mode, None)
if callable(output_func):
return output_func(mode, content)
# Total failure, raise a general exception
raise CompressorError(
"Couldn't find output method for mode '%s'" % mode)
def output_file(self, mode, content):
"""
The output method that saves the content to a file and renders
the appropriate template with the file's URL.
"""
new_filepath = self.filepath(content)
if not self.storage.exists(new_filepath):
self.storage.save(new_filepath, ContentFile(content))
url = self.storage.url(new_filepath)
return self.render_output(mode, {"url": url})
def output_inline(self, mode, content):
"""
The output method that directly returns the content for inline
display.
"""
return self.render_output(mode, {"content": content})
def render_output(self, mode, context=None):
"""
Renders the compressor output with the appropriate template for
the given mode and template context.
"""
if context is None:
context = {}
context.update(self.extra_context)
return render_to_string(
"compressor/%s_%s.html" % (self.type, mode), context)

View File

@@ -42,14 +42,14 @@ class CssCompressor(Compressor):
self.media_nodes.append((media, node))
return self.split_content
def output(self, forced=False):
def output(self, *args, **kwargs):
self.split_contents()
if not hasattr(self, 'media_nodes'):
return super(CssCompressor, self).output(forced=forced)
if not settings.COMPRESS_ENABLED and not forced:
return self.content
ret = []
for media, subnode in self.media_nodes:
subnode.extra_context.update({'media': media})
ret.append(subnode.output(forced=forced))
return "".join(ret)
return super(CssCompressor, self).output(*args, **kwargs)
if settings.COMPRESS_ENABLED or kwargs.get('forced', False):
ret = []
for media, subnode in self.media_nodes:
subnode.extra_context.update({'media': media})
ret.append(subnode.output(*args, **kwargs))
return "".join(ret)
return self.content

View File

@@ -1,3 +1,9 @@
class CompressorError(Exception):
"""
A general error of the compressor
"""
pass
class UncompressableFileError(Exception):
"""
This exception is raised when a file cannot be compressed

View File

@@ -1 +1 @@
<link rel="stylesheet" href="{{ url }}" type="text/css"{% if media %} media="{{ media }}"{% endif %}>
{# left fot backwards compatibility #}{% include "compressor/css_file.html" %}

View File

@@ -0,0 +1 @@
<link rel="stylesheet" href="{{ url }}" type="text/css"{% if media %} media="{{ media }}"{% endif %}>

View File

@@ -1 +1 @@
<script type="text/javascript" src="{{ url }}" charset="utf-8"></script>
{# left fot backwards compatibility #}{% include "compressor/js_file.html" %}

View File

@@ -0,0 +1 @@
<script type="text/javascript" src="{{ url }}" charset="utf-8"></script>

View File

@@ -51,8 +51,7 @@ class CompressorNode(template.Node):
def render(self, context, forced=False):
if (settings.COMPRESS_ENABLED and
settings.COMPRESS_OFFLINE) and not forced:
key = get_offline_cachekey(self.nodelist)
content = cache.get(key)
content = cache.get(get_offline_cachekey(self.nodelist))
if content:
return content
content = self.nodelist.render(context)
@@ -64,9 +63,7 @@ class CompressorNode(template.Node):
output = self.cache_get(cachekey)
if output is None or forced:
try:
if self.mode == OUTPUT_INLINE:
return compressor.output_inline()
output = compressor.output(forced=forced)
output = compressor.output(self.mode, forced=forced)
self.cache_set(cachekey, output)
except:
if settings.DEBUG:

View File

@@ -25,6 +25,7 @@ class CompressorTestCase(TestCase):
def setUp(self):
settings.COMPRESS_ENABLED = True
settings.PRECOMPILERS = {}
self.css = """
<link rel="stylesheet" href="/media/css/one.css" type="text/css" charset="utf-8">
<style type="text/css">p { border:5px solid green;}</style>
@@ -71,7 +72,7 @@ class CompressorTestCase(TestCase):
self.assert_(is_cachekey.match(self.css_node.cachekey), "cachekey is returning something that doesn't look like r'django_compressor\.%s\.\w{12}'" % host_name)
def test_css_hash(self):
self.assertEqual('f7c661b7a124', self.css_node.hash)
self.assertEqual('f7c661b7a124', self.css_node.hash(self.css_node.concat))
def test_css_return_if_on(self):
output = u'<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css">'
@@ -91,7 +92,7 @@ class CompressorTestCase(TestCase):
def test_js_concat(self):
out = u'obj = {};\nobj.value = "value";'
self.assertEqual(out, self.js_node.concat())
self.assertEqual(out, self.js_node.concat)
def test_js_output(self):
out = u'obj={};obj.value="value";'
@@ -384,10 +385,10 @@ class OfflineGenerationTestCase(TestCase):
def test_offline(self):
count, result = CompressCommand().compress()
self.assertEqual(2, count)
self.assertEqual(result, [
self.assertEqual([
u'<link rel="stylesheet" href="/media/CACHE/css/a55e1cf95000.css" type="text/css">\n',
u'<script type="text/javascript" src="/media/CACHE/js/bf53fa5b13e2.js" charset="utf-8"></script>',
])
], result)
def test_offline_with_context(self):
self._old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
@@ -396,8 +397,8 @@ class OfflineGenerationTestCase(TestCase):
}
count, result = CompressCommand().compress()
self.assertEqual(2, count)
self.assertEqual(result, [
self.assertEqual([
u'<link rel="stylesheet" href="/media/CACHE/css/8a2405e029de.css" type="text/css">\n',
u'<script type="text/javascript" src="/media/CACHE/js/bf53fa5b13e2.js" charset="utf-8"></script>',
])
], result)
settings.COMPRESS_OFFLINE_CONTEXT = self._old_offline_context