Initial disk offline cache

This commit is contained in:
Ben Firshman
2011-09-21 17:00:22 +01:00
committed by Jannis Leidel
parent f0bca0d10f
commit ecdd5ecbc7
4 changed files with 58 additions and 16 deletions

View File

@@ -3,12 +3,15 @@ import socket
import time import time
from django.core.cache import get_cache from django.core.cache import get_cache
from django.core.files.base import ContentFile
from django.utils import simplejson
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.utils.hashcompat import md5_constructor from django.utils.hashcompat import md5_constructor
from django.utils.importlib import import_module from django.utils.importlib import import_module
from compressor.conf import settings from compressor.conf import settings
from compressor.storage import default_storage
from compressor.utils import get_mod_func from compressor.utils import get_mod_func
_cachekey_func = None _cachekey_func = None
@@ -44,12 +47,28 @@ def get_cachekey(*args, **kwargs):
def get_mtime_cachekey(filename): def get_mtime_cachekey(filename):
return get_cachekey("mtime.%s" % get_hexdigest(filename)) return get_cachekey("mtime.%s" % get_hexdigest(filename))
def get_offline_hexdigest(source):
return get_hexdigest([smart_str(getattr(s, 's', s)) for s in source])
def get_offline_cachekey(source): def get_offline_cachekey(source):
to_hexdigest = [smart_str(getattr(s, 's', s)) for s in source] return get_cachekey("offline.%s" % get_offline_hexdigest(source))
return get_cachekey("offline.%s" % get_hexdigest(to_hexdigest))
def get_offline_manifest_filename():
output_dir = settings.COMPRESS_OUTPUT_DIR.strip('/')
return os.path.join(output_dir, 'manifest.json')
def get_offline_manifest():
filename = get_offline_manifest_filename()
if default_storage.exists(filename):
return simplejson.load(default_storage.open(filename))
else:
return {}
def write_offline_manifest(manifest):
filename = get_offline_manifest_filename()
default_storage.save(filename, ContentFile(simplejson.dumps(manifest)))
def get_templatetag_cachekey(compressor, mode, kind): def get_templatetag_cachekey(compressor, mode, kind):
return get_cachekey( return get_cachekey(
"templatetag.%s.%s.%s" % (compressor.cachekey, mode, kind)) "templatetag.%s.%s.%s" % (compressor.cachekey, mode, kind))

View File

@@ -21,7 +21,7 @@ try:
except ImportError: except ImportError:
CachedLoader = None CachedLoader = None
from compressor.cache import cache, get_offline_cachekey from compressor.cache import cache, get_offline_hexdigest, write_offline_manifest
from compressor.conf import settings from compressor.conf import settings
from compressor.exceptions import OfflineGenerationError from compressor.exceptions import OfflineGenerationError
from compressor.templatetags.compress import CompressorNode from compressor.templatetags.compress import CompressorNode
@@ -176,6 +176,7 @@ class Command(NoArgsCommand):
log.write("Compressing... ") log.write("Compressing... ")
count = 0 count = 0
results = [] results = []
offline_manifest = {}
for template, nodes in compressor_nodes.iteritems(): for template, nodes in compressor_nodes.iteritems():
context = Context(settings.COMPRESS_OFFLINE_CONTEXT) context = Context(settings.COMPRESS_OFFLINE_CONTEXT)
extra_context = {} extra_context = {}
@@ -195,16 +196,19 @@ class Command(NoArgsCommand):
context['block'] = context.render_context[BLOCK_CONTEXT_KEY].pop(node._block_name) context['block'] = context.render_context[BLOCK_CONTEXT_KEY].pop(node._block_name)
if context['block']: if context['block']:
context['block'].context = context context['block'].context = context
key = get_offline_cachekey(node.nodelist) key = get_offline_hexdigest(node.nodelist)
try: try:
result = node.render(context, forced=True) result = node.render(context, forced=True)
except Exception, e: except Exception, e:
raise CommandError("An error occured during rendering: " raise CommandError("An error occured during rendering: "
"%s" % e) "%s" % e)
cache.set(key, result, settings.COMPRESS_OFFLINE_TIMEOUT) offline_manifest[key] = result
context.pop() context.pop()
results.append(result) results.append(result)
count += 1 count += 1
write_offline_manifest(offline_manifest)
log.write("done\nCompressed %d block(s) from %d template(s).\n" % log.write("done\nCompressed %d block(s) from %d template(s).\n" %
(count, len(compressor_nodes))) (count, len(compressor_nodes)))
return count, results return count, results
@@ -252,3 +256,4 @@ class Command(NoArgsCommand):
"Offline compressiong is disabled. Set " "Offline compressiong is disabled. Set "
"COMPRESS_OFFLINE or use the --force to override.") "COMPRESS_OFFLINE or use the --force to override.")
self.compress(sys.stdout, **options) self.compress(sys.stdout, **options)

View File

@@ -2,8 +2,10 @@ from django import template
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from compressor.cache import (cache, cache_get, cache_set, from compressor.cache import (cache, cache_get, cache_set,
get_offline_cachekey, get_templatetag_cachekey) get_offline_hexdigest, get_offline_manifest,
get_templatetag_cachekey)
from compressor.conf import settings from compressor.conf import settings
from compressor.exceptions import OfflineGenerationError
from compressor.utils import get_class from compressor.utils import get_class
register = template.Library() register = template.Library()
@@ -39,14 +41,20 @@ class CompressorNode(template.Node):
if request is not None: if request is not None:
return settings.COMPRESS_DEBUG_TOGGLE in request.GET return settings.COMPRESS_DEBUG_TOGGLE in request.GET
def render_offline(self, forced): def render_offline(self, compressor, forced):
""" """
If enabled and in offline mode, and not forced or in debug mode If enabled and in offline mode, and not forced or in debug mode
check the offline cache and return the result if given check the offline cache and return the result if given
""" """
if (settings.COMPRESS_ENABLED and if (settings.COMPRESS_ENABLED and
settings.COMPRESS_OFFLINE) and not forced: settings.COMPRESS_OFFLINE) and not forced:
return cache.get(get_offline_cachekey(self.nodelist)) key = get_offline_hexdigest(self.nodelist)
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): def render_cached(self, compressor, forced):
""" """
@@ -61,24 +69,26 @@ class CompressorNode(template.Node):
return None, None return None, None
def render(self, context, forced=False): def render(self, context, forced=False):
# 1. Check if in debug mode # Check if in debug mode
if self.debug_mode(context): if self.debug_mode(context):
return self.nodelist.render(context) return self.nodelist.render(context)
# 2. Try offline cache. # Prepare the compressor
cached_offline = self.render_offline(forced)
if cached_offline:
return cached_offline
# 3. Prepare the actual compressor and check cache
context.update({'name': self.name}) context.update({'name': self.name})
compressor = self.compressor_cls(content=self.nodelist.render(context), compressor = self.compressor_cls(content=self.nodelist.render(context),
context=context) context=context)
# See if it has been rendered offline
cached_offline = self.render_offline(compressor, forced)
if cached_offline:
return cached_offline
# Check cache
cache_key, cache_content = self.render_cached(compressor, forced) cache_key, cache_content = self.render_cached(compressor, forced)
if cache_content is not None: if cache_content is not None:
return cache_content return cache_content
# 4. call compressor output method and handle exceptions # call compressor output method and handle exceptions
rendered_output = compressor.output(self.mode, forced=forced) rendered_output = compressor.output(self.mode, forced=forced)
if cache_key: if cache_key:
cache_set(cache_key, rendered_output) cache_set(cache_key, rendered_output)

View File

@@ -5,7 +5,9 @@ from django.template import Template, Context
from django.test import TestCase from django.test import TestCase
from compressor.conf import settings from compressor.conf import settings
from compressor.exceptions import OfflineGenerationError
from compressor.management.commands.compress import Command as CompressCommand from compressor.management.commands.compress import Command as CompressCommand
from compressor.storage import default_storage
from .base import test_dir, css_tag from .base import test_dir, css_tag
@@ -25,6 +27,12 @@ class OfflineGenerationTestCase(TestCase):
settings.COMPRESS_ENABLED = self._old_compress settings.COMPRESS_ENABLED = self._old_compress
settings.COMPRESS_OFFLINE = self._old_compress_offline settings.COMPRESS_OFFLINE = self._old_compress_offline
self.template_file.close() self.template_file.close()
if default_storage.exists('CACHE/manifest.json'):
default_storage.delete('CACHE/manifest.json')
def test_rendering_without_compressing_raises_exception(self):
with self.assertRaises(OfflineGenerationError):
self.template.render(Context({}))
def test_requires_model_validation(self): def test_requires_model_validation(self):
self.assertFalse(CompressCommand.requires_model_validation) self.assertFalse(CompressCommand.requires_model_validation)