diff --git a/.gitignore b/.gitignore
index 9b59a6a..9d802fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
build
compressor/tests/media/CACHE
compressor/tests/media/custom
+compressor/tests/media/js/3f33b9146e12.js
dist
MANIFEST
*.pyc
diff --git a/compressor/base.py b/compressor/base.py
index 6cf04a0..d0213fe 100644
--- a/compressor/base.py
+++ b/compressor/base.py
@@ -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)
diff --git a/compressor/css.py b/compressor/css.py
index 3fab0cd..c88e473 100644
--- a/compressor/css.py
+++ b/compressor/css.py
@@ -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
diff --git a/compressor/exceptions.py b/compressor/exceptions.py
index 42ae7c4..407f525 100644
--- a/compressor/exceptions.py
+++ b/compressor/exceptions.py
@@ -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
diff --git a/compressor/templates/compressor/css.html b/compressor/templates/compressor/css.html
index 3d593f1..f4c5e38 100644
--- a/compressor/templates/compressor/css.html
+++ b/compressor/templates/compressor/css.html
@@ -1 +1 @@
-
+{# left fot backwards compatibility #}{% include "compressor/css_file.html" %}
\ No newline at end of file
diff --git a/compressor/templates/compressor/css_file.html b/compressor/templates/compressor/css_file.html
new file mode 100644
index 0000000..3d593f1
--- /dev/null
+++ b/compressor/templates/compressor/css_file.html
@@ -0,0 +1 @@
+
diff --git a/compressor/templates/compressor/js.html b/compressor/templates/compressor/js.html
index 8419c20..f1b198f 100644
--- a/compressor/templates/compressor/js.html
+++ b/compressor/templates/compressor/js.html
@@ -1 +1 @@
-
\ No newline at end of file
+{# left fot backwards compatibility #}{% include "compressor/js_file.html" %}
\ No newline at end of file
diff --git a/compressor/templates/compressor/js_file.html b/compressor/templates/compressor/js_file.html
new file mode 100644
index 0000000..8419c20
--- /dev/null
+++ b/compressor/templates/compressor/js_file.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/compressor/templatetags/compress.py b/compressor/templatetags/compress.py
index 6f74ca7..de4a23a 100644
--- a/compressor/templatetags/compress.py
+++ b/compressor/templatetags/compress.py
@@ -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:
diff --git a/compressor/tests/tests.py b/compressor/tests/tests.py
index e2c9e43..7b37a3c 100644
--- a/compressor/tests/tests.py
+++ b/compressor/tests/tests.py
@@ -25,6 +25,7 @@ class CompressorTestCase(TestCase):
def setUp(self):
settings.COMPRESS_ENABLED = True
+ settings.PRECOMPILERS = {}
self.css = """
@@ -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''
@@ -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'\n',
u'',
- ])
+ ], 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'\n',
u'',
- ])
+ ], result)
settings.COMPRESS_OFFLINE_CONTEXT = self._old_offline_context