Merge pull request #182 from dziegler/master
Offline compression with jinja2ext.py
This commit is contained in:
@@ -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
|
||||
mode = mode or OUTPUT_FILE
|
||||
original_content = caller()
|
||||
context = {
|
||||
'original_content': original_content
|
||||
}
|
||||
return self.render_compressed(context, kind, mode, forced=forced)
|
||||
|
||||
# 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
|
||||
def get_original_content(self, context):
|
||||
return context['original_content']
|
||||
|
||||
# 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
|
||||
|
||||
@@ -14,7 +14,91 @@ OUTPUT_INLINE = 'inline'
|
||||
OUTPUT_MODES = (OUTPUT_FILE, OUTPUT_INLINE)
|
||||
|
||||
|
||||
class CompressorNode(template.Node):
|
||||
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,
|
||||
}
|
||||
|
||||
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(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 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.get_original_content(context))
|
||||
offline_manifest = get_offline_manifest()
|
||||
if key in offline_manifest:
|
||||
return offline_manifest[key]
|
||||
else:
|
||||
raise OfflineGenerationError('You have offline compression '
|
||||
'enabled but key "%s" is missing from offline manifest. '
|
||||
'You may need to run "python manage.py compress".' % key)
|
||||
|
||||
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, 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
|
||||
|
||||
# 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
|
||||
@@ -22,16 +106,8 @@ class CompressorNode(template.Node):
|
||||
self.mode = mode
|
||||
self.name = name
|
||||
|
||||
def compressor_cls(self, *args, **kwargs):
|
||||
compressors = {
|
||||
"css": settings.COMPRESS_CSS_COMPRESSOR,
|
||||
"js": settings.COMPRESS_JS_COMPRESSOR,
|
||||
}
|
||||
if self.kind not in 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)
|
||||
def get_original_content(self, context):
|
||||
return self.nodelist.render(context)
|
||||
|
||||
def debug_mode(self, context):
|
||||
if settings.COMPRESS_DEBUG_TOGGLE:
|
||||
@@ -41,62 +117,13 @@ class CompressorNode(template.Node):
|
||||
if request is not None:
|
||||
return settings.COMPRESS_DEBUG_TOGGLE in request.GET
|
||||
|
||||
def render_offline(self, context, forced):
|
||||
"""
|
||||
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))
|
||||
offline_manifest = get_offline_manifest()
|
||||
if key in offline_manifest:
|
||||
return offline_manifest[key]
|
||||
else:
|
||||
raise OfflineGenerationError('You have offline compression '
|
||||
'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):
|
||||
"""
|
||||
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_content = cache_get(cache_key)
|
||||
return cache_key, cache_content
|
||||
return None, None
|
||||
|
||||
def render_output(self, compressor, forced=False):
|
||||
return compressor.output(self.mode, forced=forced)
|
||||
|
||||
def render(self, context, forced=False):
|
||||
|
||||
# Check if in debug mode
|
||||
if self.debug_mode(context):
|
||||
return self.nodelist.render(context)
|
||||
return self.get_original_content(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.render_compressed(context, self.kind, self.mode, forced=forced)
|
||||
|
||||
|
||||
@register.tag
|
||||
|
||||
Reference in New Issue
Block a user