Permalink
Browse files

Merge branch 'master' into develop

  • Loading branch information...
2 parents f42b683 + 1a82db7 commit 8821d14a85d22498995b86f7095e74b7a70f8a83 @jonasvp committed Feb 8, 2012
View
@@ -10,8 +10,10 @@ Contributors:
Aaron Godfrey
Atamert Ölçgen
Aymeric Augustin
+Ben Firshman
Ben Spaulding
Benjamin Wohlwend
+Boris Shemigon
Brad Whittington
Bruno Renié
Chris Adams
@@ -20,16 +22,19 @@ David Medina
David Ziegler
Eugene Mirotin
Fenn Bailey
+Francisco Souza
Gert Van Gool
Harro van der Klauw
Jaap Roes
James Roe
Jason Davies
Jeremy Dunck
John-Scott Atlakson
+Jonas von Poser
Jonathan Lukens
Julien Phalip
Justin Lilly
+Lukasz Balcerzak
Luis Nell
Maciek Szczesniak
Maor Ben-Dayan
@@ -43,6 +48,7 @@ Philipp Bosch
Philipp Wollermann
Selwin Ong
Shabda Raaj
+Stefano Brentegani
Thom Linton
Ulrich Petri
Wilson Júnior
View
@@ -1,2 +1,2 @@
# following PEP 386, versiontools will pick it up
-__version__ = (1, 0, 1, "final", 0)
+__version__ = (1, 1, 2, "final", 0)
View
@@ -1,8 +1,10 @@
from __future__ import with_statement
import os
import codecs
+import urllib
from django.core.files.base import ContentFile
+from django.core.files.storage import get_storage_class
from django.template import Context
from django.template.loader import render_to_string
from django.utils.encoding import smart_unicode
@@ -12,10 +14,10 @@
from compressor.conf import settings
from compressor.exceptions import CompressorError, UncompressableFileError
from compressor.filters import CompilerFilter
-from compressor.storage import default_storage
+from compressor.storage import default_storage, compressor_file_storage
from compressor.signals import post_compress
from compressor.utils import get_class, staticfiles
-from compressor.utils.decorators import cached_property, memoize
+from compressor.utils.decorators import cached_property
# Some constants for nicer handling.
SOURCE_HUNK, SOURCE_FILE = 'inline', 'file'
@@ -66,13 +68,17 @@ def get_filepath(self, content):
return os.path.join(self.output_dir, self.output_prefix, filename)
def get_filename(self, basename):
- # first try to find it with staticfiles (in debug mode)
filename = None
+ # first try finding the file in the root
if self.storage.exists(basename):
- filename = self.storage.path(basename)
- # secondly try finding the file in the root
+ try:
+ filename = self.storage.path(basename)
+ except NotImplementedError:
+ # remote storages don't implement path, access the file locally
+ filename = compressor_file_storage.path(basename)
+ # secondly try to find it with staticfiles (in debug mode)
elif self.finders:
- filename = self.finders.find(basename)
+ filename = self.finders.find(urllib.url2pathname(basename))
if filename:
return filename
# or just raise an exception as the last resort
@@ -113,15 +119,14 @@ def cachekey(self):
return get_hexdigest(''.join(
[self.content] + self.mtimes).encode(self.charset), 12)
- @memoize
- def hunks(self, mode='file'):
+ def hunks(self, mode='file', forced=False):
"""
The heart of content parsing, iterates of the
list of split contents and looks at its kind
to decide what to do with it. Should yield a
bunch of precompiled and/or rendered hunks.
"""
- enabled = settings.COMPRESS_ENABLED
+ enabled = settings.COMPRESS_ENABLED or forced
for kind, value, basename, elem in self.split_contents():
precompiled = False
@@ -151,23 +156,21 @@ def hunks(self, mode='file'):
else:
yield mode, self.parser.elem_str(elem)
- @memoize
def filtered_output(self, content):
"""
Passes the concatenated content to the 'output' methods
of the compressor filters.
"""
return self.filter(content, method=METHOD_OUTPUT)
- @memoize
- def filtered_input(self, mode='file'):
+ def filtered_input(self, mode='file', forced=False):
"""
Passes each hunk (file or code) to the 'input' methods
of the compressor filters.
"""
verbatim_content = []
rendered_content = []
- for mode, hunk in self.hunks(mode):
+ for mode, hunk in self.hunks(mode, forced):
if mode == 'verbatim':
verbatim_content.append(hunk)
else:
@@ -208,7 +211,7 @@ def output(self, mode='file', forced=False):
any custom modification. Calls other mode specific methods or simply
returns the content directly.
"""
- verbatim_content, rendered_content = self.filtered_input(mode)
+ verbatim_content, rendered_content = self.filtered_input(mode, forced)
if not verbatim_content and not rendered_content:
return ''
@@ -258,9 +261,10 @@ def render_output(self, mode, context=None):
if context is None:
context = {}
final_context = Context()
- final_context.update(context)
final_context.update(self.context)
+ final_context.update(context)
final_context.update(self.extra_context)
- post_compress.send(sender='django-compressor', type=self.type, mode=mode, context=final_context)
+ post_compress.send(sender='django-compressor', type=self.type,
+ mode=mode, context=final_context)
return render_to_string("compressor/%s_%s.html" %
(self.type, mode), final_context)
View
@@ -3,12 +3,15 @@
import time
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.functional import SimpleLazyObject
from django.utils.hashcompat import md5_constructor
from django.utils.importlib import import_module
from compressor.conf import settings
+from compressor.storage import default_storage
from compressor.utils import get_mod_func
_cachekey_func = None
@@ -33,7 +36,8 @@ def get_cachekey(*args, **kwargs):
global _cachekey_func
if _cachekey_func is None:
try:
- mod_name, func_name = get_mod_func(settings.COMPRESS_CACHE_KEY_FUNCTION)
+ mod_name, func_name = get_mod_func(
+ settings.COMPRESS_CACHE_KEY_FUNCTION)
_cachekey_func = getattr(import_module(mod_name), func_name)
except (AttributeError, ImportError), e:
raise ImportError("Couldn't import cache key function %s: %s" %
@@ -45,9 +49,31 @@ def get_mtime_cachekey(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):
- to_hexdigest = [smart_str(getattr(s, 's', s)) for s in source]
- return get_cachekey("offline.%s" % get_hexdigest(to_hexdigest))
+ return get_cachekey("offline.%s" % get_offline_hexdigest(source))
+
+
+def get_offline_manifest_filename():
+ output_dir = settings.COMPRESS_OUTPUT_DIR.strip('/')
+ return os.path.join(output_dir, settings.COMPRESS_OFFLINE_MANIFEST)
+
+
+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, indent=2)))
def get_templatetag_cachekey(compressor, mode, kind):
View
@@ -60,6 +60,8 @@ class CompressorConf(AppConf):
OFFLINE_TIMEOUT = 60 * 60 * 24 * 365 # 1 year
# The context to be used when compressing the files "offline"
OFFLINE_CONTEXT = {}
+ # The name of the manifest file (e.g. filename.ext)
+ OFFLINE_MANIFEST = 'manifest.json'
class Meta:
prefix = 'compress'
View
@@ -21,7 +21,7 @@ def split_contents(self):
data = None
elem_name = self.parser.elem_name(elem)
elem_attribs = self.parser.elem_attribs(elem)
- if elem_name == 'link' and elem_attribs['rel'] == 'stylesheet':
+ if elem_name == 'link' and elem_attribs['rel'].lower() == 'stylesheet':
basename = self.get_basename(elem_attribs['href'])
filename = self.get_filename(basename)
data = (SOURCE_FILE, filename, basename, elem)
@@ -46,9 +46,11 @@ def find(self, basename):
def guess_filename(self, url):
local_path = url
if self.has_scheme:
- # COMPRESS_URL had a protocol, remove it and the hostname from our path.
+ # COMPRESS_URL had a protocol,
+ # remove it and the hostname from our path.
local_path = local_path.replace(self.protocol + self.host, "", 1)
- # Now, we just need to check if we can find the path from COMPRESS_URL in our url
+ # Now, we just need to check if we can find
+ # the path from COMPRESS_URL in our url
if local_path.startswith(self.url_path):
local_path = local_path.replace(self.url_path, "", 1)
# Re-build the local full path by adding root
@@ -62,7 +64,7 @@ def add_suffix(self, url):
if settings.COMPRESS_CSS_HASHING_METHOD == "mtime":
suffix = get_hashed_mtime(filename)
elif settings.COMPRESS_CSS_HASHING_METHOD == "hash":
- hash_file = open(filename)
+ hash_file = open(filename, 'rb')
try:
suffix = get_hexdigest(hash_file.read(), 12)
finally:
@@ -84,7 +86,8 @@ def url_converter(self, matchobj):
url = url.strip(' \'"')
if url.startswith(('http://', 'https://', '/', 'data:')):
return "url('%s')" % self.add_suffix(url)
- full_url = posixpath.normpath('/'.join([str(self.directory_name), url]))
+ full_url = posixpath.normpath('/'.join([str(self.directory_name),
+ url]))
if self.has_scheme:
full_url = "%s%s" % (self.protocol, full_url)
return "url('%s')" % self.add_suffix(full_url)
@@ -3,7 +3,7 @@
class YUICompressorFilter(CompilerFilter):
- command = "{binary} {args}"
+ command = "java -jar {binary} {args}"
def __init__(self, *args, **kwargs):
super(YUICompressorFilter, self).__init__(*args, **kwargs)
@@ -21,7 +21,7 @@
except ImportError:
CachedLoader = None
-from compressor.cache import cache, get_offline_cachekey
+from compressor.cache import get_offline_hexdigest, write_offline_manifest
from compressor.conf import settings
from compressor.exceptions import OfflineGenerationError
from compressor.templatetags.compress import CompressorNode
@@ -53,6 +53,8 @@ class Command(NoArgsCommand):
"directory of itself.", dest='follow_links'),
)
+ requires_model_validation = False
+
def get_loaders(self):
from django.template.loader import template_source_loaders
if template_source_loaders is None:
@@ -174,6 +176,7 @@ def compress(self, log=None, **options):
log.write("Compressing... ")
count = 0
results = []
+ offline_manifest = {}
for template, nodes in compressor_nodes.iteritems():
context = Context(settings.COMPRESS_OFFLINE_CONTEXT)
extra_context = {}
@@ -193,16 +196,19 @@ def compress(self, log=None, **options):
context['block'] = context.render_context[BLOCK_CONTEXT_KEY].pop(node._block_name)
if context['block']:
context['block'].context = context
- key = get_offline_cachekey(node.nodelist)
+ key = get_offline_hexdigest(node.nodelist)
try:
result = node.render(context, forced=True)
except Exception, e:
raise CommandError("An error occured during rendering: "
"%s" % e)
- cache.set(key, result, settings.COMPRESS_OFFLINE_TIMEOUT)
+ offline_manifest[key] = result
context.pop()
results.append(result)
count += 1
+
+ write_offline_manifest(offline_manifest)
+
log.write("done\nCompressed %d block(s) from %d template(s).\n" %
(count, len(compressor_nodes)))
return count, results
View
@@ -3,7 +3,7 @@
from datetime import datetime
from django.core.files.storage import FileSystemStorage, get_storage_class
-from django.utils.functional import LazyObject
+from django.utils.functional import LazyObject, SimpleLazyObject
from compressor.conf import settings
@@ -42,6 +42,10 @@ def get_available_name(self, name):
return name
+compressor_file_storage = SimpleLazyObject(
+ lambda: get_storage_class('compressor.storage.CompressorFileStorage')())
+
+
class GzipCompressorFileStorage(CompressorFileStorage):
"""
The standard compressor file system storage that gzips storage files
Oops, something went wrong.

0 comments on commit 8821d14

Please sign in to comment.