Merge pull request #182 from dziegler/master

Offline compression with jinja2ext.py
This commit is contained in:
Jannis Leidel
2012-01-30 09:54:42 -08:00
2 changed files with 92 additions and 102 deletions

View File

@@ -1,27 +1,14 @@
from django.core.exceptions import ImproperlyConfigured
from jinja2 import nodes
from jinja2.ext import Extension
from jinja2.exceptions import TemplateSyntaxError
from compressor.conf import settings
from compressor.utils import get_class
from compressor.templatetags.compress import OUTPUT_FILE
from compressor.cache import (cache_get, cache_set,
get_templatetag_cachekey)
from compressor.templatetags.compress import OUTPUT_FILE, CompressorMixin
class CompressorExtension(Extension):
class CompressorExtension(CompressorMixin, Extension):
tags = set(['compress'])
@property
def compressors(self):
return {
'js': settings.COMPRESS_JS_COMPRESSOR,
'css': settings.COMPRESS_CSS_COMPRESSOR,
}
def parse(self, parser):
lineno = parser.stream.next().lineno
kindarg = parser.parse_expression()
@@ -46,40 +33,16 @@ class CompressorExtension(Extension):
body).set_lineno(lineno)
def _compress(self, kind, mode, caller):
mode = mode or OUTPUT_FILE
Compressor = get_class(self.compressors.get(kind),
exception=ImproperlyConfigured)
original_content = caller()
compressor = Compressor(original_content)
# This extension assumes that we won't force compression
forced = False
# Prepare the actual compressor and check cache
cache_key, cache_content = self.render_cached(kind, mode, compressor,
forced)
if cache_content is not None:
return cache_content
# call compressor output method and handle exceptions
try:
rendered_output = compressor.output(mode, forced)
if cache_key:
cache_set(cache_key, rendered_output)
return rendered_output
except Exception, e:
if settings.DEBUG:
raise e
# Or don't do anything in production
return original_content
def render_cached(self, kind, mode, compressor, forced):
"""
If enabled checks the cache for the given compressor's cache key
and return a tuple of cache key and output
"""
if settings.COMPRESS_ENABLED and not forced:
cache_key = get_templatetag_cachekey(compressor, mode, kind)
cache_content = cache_get(cache_key)
return cache_key, cache_content
return None, None
mode = mode or OUTPUT_FILE
original_content = caller()
context = {
'original_content': original_content
}
return self.render_compressed(context, kind, mode, forced=forced)
def get_original_content(self, context):
return context['original_content']

View File

@@ -14,41 +14,39 @@ OUTPUT_INLINE = 'inline'
OUTPUT_MODES = (OUTPUT_FILE, OUTPUT_INLINE)
class CompressorNode(template.Node):
def __init__(self, nodelist, kind=None, mode=OUTPUT_FILE, name=None):
self.nodelist = nodelist
self.kind = kind
self.mode = mode
self.name = name
def compressor_cls(self, *args, **kwargs):
compressors = {
"css": settings.COMPRESS_CSS_COMPRESSOR,
"js": settings.COMPRESS_JS_COMPRESSOR,
class CompressorMixin(object):
def get_original_content(self, context):
raise NotImplementedError
@property
def compressors(self):
return {
'js': settings.COMPRESS_JS_COMPRESSOR,
'css': settings.COMPRESS_CSS_COMPRESSOR,
}
if self.kind not in compressors.keys():
def compressor_cls(self, kind, *args, **kwargs):
if kind not in self.compressors.keys():
raise template.TemplateSyntaxError(
"The compress tag's argument must be 'js' or 'css'.")
return get_class(compressors.get(self.kind),
exception=ImproperlyConfigured)(*args, **kwargs)
return get_class(self.compressors.get(kind),
exception=ImproperlyConfigured)(*args, **kwargs)
def get_compressor(self, context, kind):
return self.compressor_cls(kind,
content=self.get_original_content(context),
context=context)
def debug_mode(self, context):
if settings.COMPRESS_DEBUG_TOGGLE:
# Only check for the debug parameter
# if a RequestContext was used
request = context.get('request', None)
if request is not None:
return settings.COMPRESS_DEBUG_TOGGLE in request.GET
def render_offline(self, context, forced):
def render_offline(self, context, forced=False):
"""
If enabled and in offline mode, and not forced or in debug mode
check the offline cache and return the result if given
"""
if (settings.COMPRESS_ENABLED and
settings.COMPRESS_OFFLINE) and not forced:
key = get_offline_hexdigest(self.nodelist.render(context))
key = get_offline_hexdigest(self.get_original_content(context))
offline_manifest = get_offline_manifest()
if key in offline_manifest:
return offline_manifest[key]
@@ -57,47 +55,76 @@ class CompressorNode(template.Node):
'enabled but key "%s" is missing from offline manifest. '
'You may need to run "python manage.py compress".' % key)
def render_cached(self, compressor, forced):
def render_cached(self, compressor, kind, mode, forced=False):
"""
If enabled checks the cache for the given compressor's cache key
and return a tuple of cache key and output
"""
if settings.COMPRESS_ENABLED and not forced:
cache_key = get_templatetag_cachekey(
compressor, self.mode, self.kind)
cache_key = get_templatetag_cachekey(compressor, mode, kind)
cache_content = cache_get(cache_key)
return cache_key, cache_content
return None, None
def render_compressed(self, context, kind, mode, forced=False):
# See if it has been rendered offline
cached_offline = self.render_offline(context, forced=forced)
if cached_offline:
return cached_offline
context['compressed'] = {'name': getattr(self, 'name', None)}
compressor = self.get_compressor(context, kind)
# Prepare the actual compressor and check cache
cache_key, cache_content = self.render_cached(compressor, kind, mode, forced=forced)
if cache_content is not None:
return cache_content
def render_output(self, compressor, forced=False):
return compressor.output(self.mode, forced=forced)
# call compressor output method and handle exceptions
try:
rendered_output = self.render_output(compressor, mode, forced=forced)
if cache_key:
cache_set(cache_key, rendered_output)
return rendered_output
except Exception, e:
if settings.DEBUG:
raise e
# Or don't do anything in production
return self.get_original_content(context)
def render_output(self, compressor, mode, forced=False):
return compressor.output(mode, forced=forced)
class CompressorNode(CompressorMixin, template.Node):
def __init__(self, nodelist, kind=None, mode=OUTPUT_FILE, name=None):
self.nodelist = nodelist
self.kind = kind
self.mode = mode
self.name = name
def get_original_content(self, context):
return self.nodelist.render(context)
def debug_mode(self, context):
if settings.COMPRESS_DEBUG_TOGGLE:
# Only check for the debug parameter
# if a RequestContext was used
request = context.get('request', None)
if request is not None:
return settings.COMPRESS_DEBUG_TOGGLE in request.GET
def render(self, context, forced=False):
# Check if in debug mode
if self.debug_mode(context):
return self.nodelist.render(context)
# See if it has been rendered offline
cached_offline = self.render_offline(context, forced)
if cached_offline:
return cached_offline
# Prepare the compressor
context['compressed'] = {'name': self.name}
compressor = self.compressor_cls(content=self.nodelist.render(context),
context=context)
# Check cache
cache_key, cache_content = self.render_cached(compressor, forced)
if cache_content is not None:
return cache_content
# call compressor output method and handle exceptions
rendered_output = self.render_output(compressor, forced)
if cache_key:
cache_set(cache_key, rendered_output)
return rendered_output
return self.get_original_content(context)
return self.render_compressed(context, self.kind, self.mode, forced=forced)
@register.tag
def compress(parser, token):