diff --git a/compressor/conf/settings.py b/compressor/conf/settings.py index ce5decf..18ccecd 100644 --- a/compressor/conf/settings.py +++ b/compressor/conf/settings.py @@ -62,6 +62,12 @@ if CACHE_BACKEND is None: # fallback for people still using the old CACHE_BACKEND setting CACHE_BACKEND = settings.CACHE_BACKEND +# enables the offline cache -- a cache that is filled by the compress management command +OFFLINE = getattr(settings, 'COMPRESS_OFFLINE', False) + +# invalidates the offline cache after one year +OFFLINE_TIMEOUT = getattr(settings, 'COMPRESS_OFFLINE_TIMEOUT', 60 * 60 * 24 * 365) # 1 year + # The context to be used when compressing the files "offline" OFFLINE_CONTEXT = getattr(settings, 'COMPRESS_OFFLINE_CONTEXT', {}) if not OFFLINE_CONTEXT: diff --git a/compressor/management/commands/compress.py b/compressor/management/commands/compress.py index fffeae2..2710799 100644 --- a/compressor/management/commands/compress.py +++ b/compressor/management/commands/compress.py @@ -22,14 +22,14 @@ from compressor.utils import get_offline_cachekey, walk, any, import_module class Command(NoArgsCommand): - help = "Generate the compressor cache content" + help = "Generate the compressor content outside of the request/response cycle" option_list = NoArgsCommand.option_list + ( make_option('--extension', '-e', action='append', dest='extensions', help='The file extension(s) to examine (default: ".html", ' 'separate multiple extensions with commas, or use -e ' 'multiple times)'), make_option('-f', '--force', default=False, action='store_true', dest='force', - help="Force generation of offline cache even if " + help="Force generation of compressor content even if " "COMPRESS setting is not True."), make_option('--follow-links', default=False, action='store_true', dest='follow_links', help="Follow symlinks when traversing the COMPRESS_ROOT " @@ -127,8 +127,8 @@ class Command(NoArgsCommand): for nodes in compressor_nodes.values(): for node in nodes: key = get_offline_cachekey(node.nodelist) - result = node.render(context, compress=True, offline=True) - cache.set(key, result, settings.REBUILD_TIMEOUT) + result = node.render(context, compress=True, offline=False) + cache.set(key, result, settings.OFFLINE_TIMEOUT) results.append(result) count += 1 log.write("done\nCompressed %d block(s) from %d template(s).\n" @@ -174,4 +174,10 @@ class Command(NoArgsCommand): raise CommandError("Compressor is disabled. Set COMPRESS " "settting to True to enable it " "(Use -f/--force to override).") + if not settings.OFFLINE: + if not options.get("force"): + raise CommandError("Aborting; COMPRESS_OFFLINE is not set. " + "(Use -f/--force to override)") + warnings.warn("COMPRESS_OFFLINE is not set. Offline generated " + "cache will not be used.") self.compress(sys.stdout, **options) diff --git a/compressor/templatetags/compress.py b/compressor/templatetags/compress.py index e6459f0..348dfef 100644 --- a/compressor/templatetags/compress.py +++ b/compressor/templatetags/compress.py @@ -38,14 +38,14 @@ class CompressorNode(template.Node): packed_val = (val, refresh_time, refreshed) return cache.set(key, packed_val, real_timeout) - def render(self, context, compress=settings.COMPRESS, offline=False): - if compress and not offline: + def render(self, context, compress=settings.COMPRESS, offline=settings.OFFLINE): + if compress and offline: key = get_offline_cachekey(self.nodelist) content = cache.get(key) if content: return content content = self.nodelist.render(context) - if not compress or not len(content.strip()): + if offline or not compress or not len(content.strip()): return content if self.kind == 'css': compressor = CssCompressor(content) diff --git a/compressor/utils.py b/compressor/utils.py index feccb0b..11915d1 100644 --- a/compressor/utils.py +++ b/compressor/utils.py @@ -43,14 +43,11 @@ def get_hashed_mtime(filename, length=12): mtime = str(int(get_mtime(filename))) return get_hexdigest(mtime)[:length] - def get_class(class_string, exception=FilterError): """ Convert a string version of a function name to the callable object. """ - if not hasattr(class_string, '__bases__'): - try: class_string = class_string.encode('ascii') mod_name, class_name = get_mod_func(class_string) @@ -58,23 +55,19 @@ def get_class(class_string, exception=FilterError): cls = getattr(__import__(mod_name, {}, {}, ['']), class_name) except (ImportError, AttributeError): raise exception('Failed to import filter %s' % class_string) - return cls - def get_mod_func(callback): """ Converts 'django.views.news.stories.story_detail' to ('django.views.news.stories', 'story_detail') """ - try: dot = callback.rindex('.') except ValueError: return callback, '' return callback[:dot], callback[dot+1:] - def walk(root, topdown=True, onerror=None, followlinks=False): """ A version of os.walk that can follow symlinks for Python < 2.6 @@ -88,7 +81,6 @@ def walk(root, topdown=True, onerror=None, followlinks=False): for link_dirpath, link_dirnames, link_filenames in walk(p): yield (link_dirpath, link_dirnames, link_filenames) - # Taken from Django 1.3-beta1 and before that from Python 2.7 with permission from/by the original author. def _resolve_name(name, package, level): """Return the absolute name of the module to be imported.""" @@ -103,7 +95,6 @@ def _resolve_name(name, package, level): "package") return "%s.%s" % (package[:dot], name) - def import_module(name, package=None): """Import a module.