Applied a few PEP8 style modifications.
This commit is contained in:
@@ -13,4 +13,4 @@ def get_version():
|
||||
return version
|
||||
|
||||
|
||||
__version__ = get_version()
|
||||
__version__ = get_version()
|
||||
|
@@ -26,8 +26,11 @@ class Compressor(object):
|
||||
self.precompilers = settings.COMPRESS_PRECOMPILERS
|
||||
|
||||
def split_contents(self):
|
||||
raise NotImplementedError(
|
||||
"split_contents must be defined in a subclass")
|
||||
"""
|
||||
To be implemented in a subclass, should return an
|
||||
iterable with three values: kind, value, element
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_filename(self, url):
|
||||
try:
|
||||
@@ -76,7 +79,8 @@ class Compressor(object):
|
||||
if kind == "hunk":
|
||||
# Let's cast BeautifulSoup element to unicode here since
|
||||
# it will try to encode using ascii internally later
|
||||
yield unicode(self.filter(value, "input", elem=elem, kind=kind))
|
||||
yield unicode(
|
||||
self.filter(value, method="input", elem=elem, kind=kind))
|
||||
elif kind == "file":
|
||||
content = ""
|
||||
try:
|
||||
@@ -142,7 +146,6 @@ class Compressor(object):
|
||||
source_file.close()
|
||||
return content
|
||||
|
||||
|
||||
def filter(self, content, method, **kwargs):
|
||||
# run compiler
|
||||
if method == "input":
|
||||
@@ -160,7 +163,7 @@ class Compressor(object):
|
||||
|
||||
@cached_property
|
||||
def combined(self):
|
||||
return self.filter(self.concat(), 'output')
|
||||
return self.filter(self.concat(), method="output")
|
||||
|
||||
@cached_property
|
||||
def hash(self):
|
||||
@@ -169,7 +172,7 @@ class Compressor(object):
|
||||
@cached_property
|
||||
def new_filepath(self):
|
||||
return os.path.join(settings.COMPRESS_OUTPUT_DIR.strip(os.sep),
|
||||
self.output_prefix, "%s.%s" % (self.hash, self.type))
|
||||
self.output_prefix, "%s.%s" % (self.hash, self.type))
|
||||
|
||||
def save_file(self):
|
||||
if self.storage.exists(self.new_filepath):
|
||||
|
@@ -7,18 +7,22 @@ from django.utils.hashcompat import sha_constructor
|
||||
|
||||
from compressor.conf import settings
|
||||
|
||||
|
||||
def get_hexdigest(plaintext):
|
||||
return sha_constructor(plaintext).hexdigest()
|
||||
|
||||
|
||||
def get_mtime_cachekey(filename):
|
||||
return "django_compressor.mtime.%s.%s" % (socket.gethostname(),
|
||||
get_hexdigest(filename))
|
||||
|
||||
|
||||
def get_offline_cachekey(source):
|
||||
return ("django_compressor.offline.%s.%s" %
|
||||
(socket.gethostname(),
|
||||
get_hexdigest("".join(smart_str(s) for s in source))))
|
||||
|
||||
|
||||
def get_mtime(filename):
|
||||
if settings.COMPRESS_MTIME_DELAY:
|
||||
key = get_mtime_cachekey(filename)
|
||||
@@ -29,9 +33,11 @@ def get_mtime(filename):
|
||||
return mtime
|
||||
return os.path.getmtime(filename)
|
||||
|
||||
|
||||
def get_hashed_mtime(filename, length=12):
|
||||
filename = os.path.realpath(filename)
|
||||
mtime = str(int(get_mtime(filename)))
|
||||
return get_hexdigest(mtime)[:length]
|
||||
|
||||
|
||||
cache = get_cache(settings.COMPRESS_CACHE_BACKEND)
|
||||
|
@@ -2,12 +2,13 @@ from compressor.conf import settings
|
||||
from compressor.base import Compressor
|
||||
from compressor.exceptions import UncompressableFileError
|
||||
|
||||
|
||||
class CssCompressor(Compressor):
|
||||
template_name = "compressor/css.html"
|
||||
template_name_inline = "compressor/css_inline.html"
|
||||
|
||||
def __init__(self, content=None, output_prefix="css"):
|
||||
super(CssCompressor, self).__init__(content, output_prefix)
|
||||
self.template_name = "compressor/css.html"
|
||||
self.template_name_inline = "compressor/css_inline.html"
|
||||
self.filters = list(settings.COMPRESS_CSS_FILTERS)
|
||||
self.type = 'css'
|
||||
|
||||
@@ -21,8 +22,8 @@ class CssCompressor(Compressor):
|
||||
elem_attribs = self.parser.elem_attribs(elem)
|
||||
if elem_name == 'link' and elem_attribs['rel'] == 'stylesheet':
|
||||
try:
|
||||
content = self.parser.elem_content(elem)
|
||||
data = ('file', self.get_filename(elem_attribs['href']), elem)
|
||||
data = (
|
||||
'file', self.get_filename(elem_attribs['href']), elem)
|
||||
except UncompressableFileError:
|
||||
if settings.DEBUG:
|
||||
raise
|
||||
|
@@ -17,12 +17,14 @@ class FilterError(Exception):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ParserError(Exception):
|
||||
"""
|
||||
This exception is raised when the parser fails
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OfflineGenerationError(Exception):
|
||||
"""
|
||||
Offline compression generation related exceptions
|
||||
|
@@ -8,6 +8,7 @@ from compressor.utils import cmd_split
|
||||
|
||||
logger = logging.getLogger("compressor.filters")
|
||||
|
||||
|
||||
class FilterBase(object):
|
||||
|
||||
def __init__(self, content, filter_type=None, verbose=0):
|
||||
|
@@ -2,9 +2,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# `cssmin.py` - A Python port of the YUI CSS compressor.
|
||||
#
|
||||
#
|
||||
# Copyright (c) 2010 Zachary Voase
|
||||
#
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation
|
||||
# files (the "Software"), to deal in the Software without
|
||||
@@ -13,10 +13,10 @@
|
||||
# copies of the Software, and to permit persons to whom the
|
||||
# Software is furnished to do so, subject to the following
|
||||
# conditions:
|
||||
#
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
@@ -25,7 +25,7 @@
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
#
|
||||
"""`cssmin` - A Python port of the YUI CSS compressor."""
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ __version__ = '0.1.4'
|
||||
|
||||
def remove_comments(css):
|
||||
"""Remove all CSS comment blocks."""
|
||||
|
||||
|
||||
iemac = False
|
||||
preserve = False
|
||||
comment_start = css.find("/*")
|
||||
@@ -49,7 +49,7 @@ def remove_comments(css):
|
||||
# Preserve comments that look like `/*!...*/`.
|
||||
# Slicing is used to make sure we don"t get an IndexError.
|
||||
preserve = css[comment_start + 2:comment_start + 3] == "!"
|
||||
|
||||
|
||||
comment_end = css.find("*/", comment_start + 2)
|
||||
if comment_end < 0:
|
||||
if not preserve:
|
||||
@@ -69,22 +69,22 @@ def remove_comments(css):
|
||||
else:
|
||||
comment_start = comment_end + 2
|
||||
comment_start = css.find("/*", comment_start)
|
||||
|
||||
|
||||
return css
|
||||
|
||||
|
||||
def remove_unnecessary_whitespace(css):
|
||||
"""Remove unnecessary whitespace characters."""
|
||||
|
||||
|
||||
def pseudoclasscolon(css):
|
||||
|
||||
|
||||
"""
|
||||
Prevents 'p :link' from becoming 'p:link'.
|
||||
|
||||
|
||||
Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is
|
||||
translated back again later.
|
||||
"""
|
||||
|
||||
|
||||
regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)")
|
||||
match = regex.search(css)
|
||||
while match:
|
||||
@@ -94,43 +94,43 @@ def remove_unnecessary_whitespace(css):
|
||||
css[match.end():]])
|
||||
match = regex.search(css)
|
||||
return css
|
||||
|
||||
|
||||
css = pseudoclasscolon(css)
|
||||
# Remove spaces from before things.
|
||||
css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css)
|
||||
|
||||
|
||||
# If there is a `@charset`, then only allow one, and move to the beginning.
|
||||
css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css)
|
||||
css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css)
|
||||
|
||||
|
||||
# Put the space back in for a few cases, such as `@media screen` and
|
||||
# `(-webkit-min-device-pixel-ratio:0)`.
|
||||
css = re.sub(r"\band\(", "and (", css)
|
||||
|
||||
|
||||
# Put the colons back.
|
||||
css = css.replace('___PSEUDOCLASSCOLON___', ':')
|
||||
|
||||
|
||||
# Remove spaces from after things.
|
||||
css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css)
|
||||
|
||||
|
||||
return css
|
||||
|
||||
|
||||
def remove_unnecessary_semicolons(css):
|
||||
"""Remove unnecessary semicolons."""
|
||||
|
||||
|
||||
return re.sub(r";+\}", "}", css)
|
||||
|
||||
|
||||
def remove_empty_rules(css):
|
||||
"""Remove empty rules."""
|
||||
|
||||
|
||||
return re.sub(r"[^\}\{]+\{\}", "", css)
|
||||
|
||||
|
||||
def normalize_rgb_colors_to_hex(css):
|
||||
"""Convert `rgb(51,102,153)` to `#336699`."""
|
||||
|
||||
|
||||
regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)")
|
||||
match = regex.search(css)
|
||||
while match:
|
||||
@@ -143,32 +143,32 @@ def normalize_rgb_colors_to_hex(css):
|
||||
|
||||
def condense_zero_units(css):
|
||||
"""Replace `0(px, em, %, etc)` with `0`."""
|
||||
|
||||
|
||||
return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css)
|
||||
|
||||
|
||||
def condense_multidimensional_zeros(css):
|
||||
"""Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`."""
|
||||
|
||||
|
||||
css = css.replace(":0 0 0 0;", ":0;")
|
||||
css = css.replace(":0 0 0;", ":0;")
|
||||
css = css.replace(":0 0;", ":0;")
|
||||
|
||||
|
||||
# Revert `background-position:0;` to the valid `background-position:0 0;`.
|
||||
css = css.replace("background-position:0;", "background-position:0 0;")
|
||||
|
||||
|
||||
return css
|
||||
|
||||
|
||||
def condense_floating_points(css):
|
||||
"""Replace `0.6` with `.6` where possible."""
|
||||
|
||||
|
||||
return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css)
|
||||
|
||||
|
||||
def condense_hex_colors(css):
|
||||
"""Shorten colors from #AABBCC to #ABC where possible."""
|
||||
|
||||
|
||||
regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])")
|
||||
match = regex.search(css)
|
||||
while match:
|
||||
@@ -184,19 +184,19 @@ def condense_hex_colors(css):
|
||||
|
||||
def condense_whitespace(css):
|
||||
"""Condense multiple adjacent whitespace characters into one."""
|
||||
|
||||
|
||||
return re.sub(r"\s+", " ", css)
|
||||
|
||||
|
||||
def condense_semicolons(css):
|
||||
"""Condense multiple adjacent semicolon characters into one."""
|
||||
|
||||
|
||||
return re.sub(r";;+", ";", css)
|
||||
|
||||
|
||||
def wrap_css_lines(css, line_length):
|
||||
"""Wrap the lines of the given CSS to an approximate length."""
|
||||
|
||||
|
||||
lines = []
|
||||
line_start = 0
|
||||
for i, char in enumerate(css):
|
||||
@@ -204,7 +204,7 @@ def wrap_css_lines(css, line_length):
|
||||
if char == '}' and (i - line_start >= line_length):
|
||||
lines.append(css[line_start:i + 1])
|
||||
line_start = i + 1
|
||||
|
||||
|
||||
if line_start < len(css):
|
||||
lines.append(css[line_start:])
|
||||
return '\n'.join(lines)
|
||||
@@ -233,16 +233,16 @@ def cssmin(css, wrap=None):
|
||||
def main():
|
||||
import optparse
|
||||
import sys
|
||||
|
||||
|
||||
p = optparse.OptionParser(
|
||||
prog="cssmin", version=__version__,
|
||||
usage="%prog [--wrap N]",
|
||||
description="""Reads raw CSS from stdin, and writes compressed CSS to stdout.""")
|
||||
|
||||
|
||||
p.add_option(
|
||||
'-w', '--wrap', type='int', default=None, metavar='N',
|
||||
help="Wrap output to approximately N chars per line.")
|
||||
|
||||
|
||||
options, args = p.parse_args()
|
||||
sys.stdout.write(cssmin(sys.stdin.read(), wrap=options.wrap))
|
||||
|
||||
|
@@ -6,6 +6,7 @@ from base64 import b64encode
|
||||
from compressor.conf import settings
|
||||
from compressor.filters import FilterBase
|
||||
|
||||
|
||||
class DataUriFilter(FilterBase):
|
||||
"""Filter for embedding media as data: URIs.
|
||||
|
||||
@@ -28,7 +29,8 @@ class DataUriFilter(FilterBase):
|
||||
# strip query string of file paths
|
||||
if "?" in url:
|
||||
url = url.split("?")[0]
|
||||
return os.path.join(settings.COMPRESS_ROOT, url[len(settings.COMPRESS_URL):])
|
||||
return os.path.join(
|
||||
settings.COMPRESS_ROOT, url[len(settings.COMPRESS_URL):])
|
||||
|
||||
def data_uri_converter(self, matchobj):
|
||||
url = matchobj.group(1).strip(' \'"')
|
||||
@@ -36,7 +38,8 @@ class DataUriFilter(FilterBase):
|
||||
path = self.get_file_path(url)
|
||||
if os.stat(path).st_size <= settings.COMPRESS_DATA_URI_MIN_SIZE:
|
||||
data = b64encode(open(path, 'rb').read())
|
||||
return 'url("data:%s;base64,%s")' % (mimetypes.guess_type(path)[0], data)
|
||||
return 'url("data:%s;base64,%s")' % (
|
||||
mimetypes.guess_type(path)[0], data)
|
||||
return 'url("%s")' % url
|
||||
|
||||
|
||||
|
@@ -13,6 +13,7 @@ else:
|
||||
"standalone version django-staticfiles needs "
|
||||
"to be installed.")
|
||||
|
||||
|
||||
class CompressorFinder(BaseStorageFinder):
|
||||
"""
|
||||
A staticfiles finder that looks in COMPRESS_ROOT
|
||||
|
@@ -4,11 +4,11 @@ from compressor.exceptions import UncompressableFileError
|
||||
|
||||
|
||||
class JsCompressor(Compressor):
|
||||
template_name = "compressor/js.html"
|
||||
template_name_inline = "compressor/js_inline.html"
|
||||
|
||||
def __init__(self, content=None, output_prefix="js"):
|
||||
super(JsCompressor, self).__init__(content, output_prefix)
|
||||
self.template_name = "compressor/js.html"
|
||||
self.template_name_inline = "compressor/js_inline.html"
|
||||
self.filters = list(settings.COMPRESS_JS_FILTERS)
|
||||
self.type = 'js'
|
||||
|
||||
@@ -19,7 +19,8 @@ class JsCompressor(Compressor):
|
||||
attribs = self.parser.elem_attribs(elem)
|
||||
if 'src' in attribs:
|
||||
try:
|
||||
self.split_content.append(('file', self.get_filename(attribs['src']), elem))
|
||||
self.split_content.append(
|
||||
('file', self.get_filename(attribs['src']), elem))
|
||||
except UncompressableFileError:
|
||||
if settings.DEBUG:
|
||||
raise
|
||||
|
@@ -21,28 +21,30 @@ from compressor.utils import walk, any, import_module
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = "Generate the compressor content outside of the request/response cycle"
|
||||
help = "Compress 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',
|
||||
make_option('-f', '--force', default=False, action='store_true',
|
||||
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',
|
||||
"COMPRESS setting is not True.", dest='force'),
|
||||
make_option('--follow-links', default=False, action='store_true',
|
||||
help="Follow symlinks when traversing the COMPRESS_ROOT "
|
||||
"(which defaults to MEDIA_ROOT). Be aware that using this "
|
||||
"can lead to infinite recursion if a link points to a parent "
|
||||
"directory of itself."),
|
||||
"directory of itself.", dest='follow_links'),
|
||||
)
|
||||
def get_loaders(self):
|
||||
from django.template.loader import template_source_loaders
|
||||
if template_source_loaders is None:
|
||||
try:
|
||||
from django.template.loader import find_template as finder_func
|
||||
from django.template.loader import (
|
||||
find_template as finder_func)
|
||||
except ImportError:
|
||||
from django.template.loader import find_template_source as finder_func
|
||||
from django.template.loader import (
|
||||
find_template_source as finder_func)
|
||||
try:
|
||||
source, name = finder_func('test')
|
||||
except TemplateDoesNotExist:
|
||||
@@ -71,7 +73,8 @@ class Command(NoArgsCommand):
|
||||
for loader in self.get_loaders():
|
||||
try:
|
||||
module = import_module(loader.__module__)
|
||||
get_template_sources = getattr(module, 'get_template_sources', None)
|
||||
get_template_sources = getattr(module,
|
||||
'get_template_sources', None)
|
||||
if get_template_sources is None:
|
||||
get_template_sources = loader.get_template_sources
|
||||
paths.update(list(get_template_sources('')))
|
||||
@@ -89,7 +92,8 @@ class Command(NoArgsCommand):
|
||||
log.write("Considering paths:\n\t" + "\n\t".join(paths) + "\n")
|
||||
templates = set()
|
||||
for path in paths:
|
||||
for root, dirs, files in walk(path, followlinks=options.get('followlinks', False)):
|
||||
for root, dirs, files in walk(path,
|
||||
followlinks=options.get('followlinks', False)):
|
||||
templates.update(os.path.join(root, name)
|
||||
for name in files if any(fnmatch(name, "*%s" % glob)
|
||||
for glob in extensions))
|
||||
@@ -126,7 +130,8 @@ class Command(NoArgsCommand):
|
||||
compressor_nodes.setdefault(template_name, []).extend(nodes)
|
||||
|
||||
if not compressor_nodes:
|
||||
raise OfflineGenerationError("No 'compress' template tags found in templates.")
|
||||
raise OfflineGenerationError(
|
||||
"No 'compress' template tags found in templates.")
|
||||
|
||||
if verbosity > 0:
|
||||
log.write("Found 'compress' tags in:\n\t" +
|
||||
@@ -175,18 +180,19 @@ class Command(NoArgsCommand):
|
||||
for i, ext in enumerate(ext_list):
|
||||
if not ext.startswith('.'):
|
||||
ext_list[i] = '.%s' % ext_list[i]
|
||||
|
||||
# we don't want *.py files here because of the way non-*.py files
|
||||
# are handled in make_messages() (they are copied to file.ext.py files to
|
||||
# trick xgettext to parse them as Python files)
|
||||
return set([x for x in ext_list if x != '.py'])
|
||||
return set(ext_list)
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
if not settings.COMPRESS_ENABLED and not options.get("force"):
|
||||
raise CommandError("Compressor is disabled. Set COMPRESS settting or use --force to override.")
|
||||
raise CommandError(
|
||||
"Compressor is disabled. Set COMPRESS "
|
||||
"settting or use --force to override.")
|
||||
if not settings.COMPRESS_OFFLINE:
|
||||
if not options.get("force"):
|
||||
raise CommandError("Offline compressiong is disabled. Set COMPRESS_OFFLINE or use the --force to override.")
|
||||
warnings.warn("COMPRESS_OFFLINE is not set. Offline generated "
|
||||
"cache will not be used.")
|
||||
raise CommandError(
|
||||
"Offline compressiong is disabled. Set "
|
||||
"COMPRESS_OFFLINE or use the --force to override.")
|
||||
warnings.warn(
|
||||
"COMPRESS_OFFLINE is not set to True. "
|
||||
"Offline generated cache will not be used.")
|
||||
self.compress(sys.stdout, **options)
|
||||
|
@@ -2,8 +2,11 @@ from django.utils.encoding import smart_unicode
|
||||
|
||||
from compressor.exceptions import ParserError
|
||||
|
||||
class ParserBase(object):
|
||||
|
||||
class ParserBase(object):
|
||||
"""
|
||||
Base parser to be subclassed when creating an own parser.
|
||||
"""
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
|
||||
@@ -43,6 +46,7 @@ class ParserBase(object):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class BeautifulSoupParser(ParserBase):
|
||||
_soup = None
|
||||
|
||||
@@ -57,7 +61,7 @@ class BeautifulSoupParser(ParserBase):
|
||||
return self._soup
|
||||
|
||||
def css_elems(self):
|
||||
return self.soup.findAll({'link' : True, 'style' : True})
|
||||
return self.soup.findAll({'link': True, 'style': True})
|
||||
|
||||
def js_elems(self):
|
||||
return self.soup.findAll('script')
|
||||
@@ -74,6 +78,7 @@ class BeautifulSoupParser(ParserBase):
|
||||
def elem_str(self, elem):
|
||||
return smart_unicode(elem)
|
||||
|
||||
|
||||
class LxmlParser(ParserBase):
|
||||
_tree = None
|
||||
|
||||
@@ -110,4 +115,5 @@ class LxmlParser(ParserBase):
|
||||
|
||||
def elem_str(self, elem):
|
||||
from lxml import etree
|
||||
return smart_unicode(etree.tostring(elem, method='html', encoding=unicode))
|
||||
return smart_unicode(
|
||||
etree.tostring(elem, method='html', encoding=unicode))
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import os
|
||||
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
@@ -38,16 +36,16 @@ class CompressorSettings(AppSettings):
|
||||
# the cache backend to use
|
||||
CACHE_BACKEND = None
|
||||
# rebuilds the cache every 30 days if nothing has changed.
|
||||
REBUILD_TIMEOUT = 60 * 60 * 24 * 30 # 30 days
|
||||
REBUILD_TIMEOUT = 60 * 60 * 24 * 30 # 30 days
|
||||
# the upper bound on how long any compression should take to be generated
|
||||
# (used against dog piling, should be a lot smaller than REBUILD_TIMEOUT
|
||||
MINT_DELAY = 30 # seconds
|
||||
MINT_DELAY = 30 # seconds
|
||||
# check for file changes only after a delay
|
||||
MTIME_DELAY = 10 # seconds
|
||||
# enables the offline cache -- a cache that is filled by the compress management command
|
||||
MTIME_DELAY = 10 # seconds
|
||||
# enables the offline cache -- also filled by the compress command
|
||||
OFFLINE = False
|
||||
# invalidates the offline cache after one year
|
||||
OFFLINE_TIMEOUT = 60 * 60 * 24 * 365 # 1 year
|
||||
OFFLINE_TIMEOUT = 60 * 60 * 24 * 365 # 1 year
|
||||
# The context to be used when compressing the files "offline"
|
||||
OFFLINE_CONTEXT = {}
|
||||
|
||||
@@ -60,7 +58,8 @@ class CompressorSettings(AppSettings):
|
||||
if not value:
|
||||
value = settings.MEDIA_ROOT
|
||||
if not value:
|
||||
raise ImproperlyConfigured("The COMPRESS_ROOT setting must be set.")
|
||||
raise ImproperlyConfigured(
|
||||
"The COMPRESS_ROOT setting must be set.")
|
||||
# In case staticfiles is used, make sure the FileSystemFinder is
|
||||
# installed, and if it is, check if COMPRESS_ROOT is listed in
|
||||
# STATICFILES_DIRS to allow finding compressed files
|
||||
@@ -79,14 +78,14 @@ class CompressorSettings(AppSettings):
|
||||
return value
|
||||
|
||||
def configure_url(self, value):
|
||||
# Falls back to the 1.3 STATIC_URL setting by default or falls back to MEDIA_URL
|
||||
# Uses Django 1.3's STATIC_URL by default or falls back to MEDIA_URL
|
||||
if value is None:
|
||||
value = getattr(settings, 'STATIC_URL', None)
|
||||
value = getattr(settings, "STATIC_URL", None)
|
||||
if not value:
|
||||
value = settings.MEDIA_URL
|
||||
if not value.endswith('/'):
|
||||
raise ImproperlyConfigured('The URL settings (e.g. COMPRESS_URL) '
|
||||
'must have a trailing slash.')
|
||||
if not value.endswith("/"):
|
||||
raise ImproperlyConfigured("The URL settings (e.g. COMPRESS_URL) "
|
||||
"must have a trailing slash.")
|
||||
return value
|
||||
|
||||
def configure_cache_backend(self, value):
|
||||
@@ -105,9 +104,9 @@ class CompressorSettings(AppSettings):
|
||||
def configure_offline_context(self, value):
|
||||
if not value:
|
||||
value = {
|
||||
'MEDIA_URL': settings.MEDIA_URL,
|
||||
"MEDIA_URL": settings.MEDIA_URL,
|
||||
}
|
||||
# Adds the 1.3 STATIC_URL setting to the context if available
|
||||
if getattr(settings, 'STATIC_URL', None):
|
||||
value['STATIC_URL'] = settings.STATIC_URL
|
||||
if getattr(settings, "STATIC_URL", None):
|
||||
value["STATIC_URL"] = settings.STATIC_URL
|
||||
return value
|
||||
|
@@ -7,6 +7,7 @@ from django.utils.functional import LazyObject
|
||||
|
||||
from compressor.conf import settings
|
||||
|
||||
|
||||
class CompressorFileStorage(FileSystemStorage):
|
||||
"""
|
||||
Standard file system storage for files handled by django-compressor.
|
||||
@@ -40,6 +41,7 @@ class CompressorFileStorage(FileSystemStorage):
|
||||
self.delete(name)
|
||||
return name
|
||||
|
||||
|
||||
class GzipCompressorFileStorage(CompressorFileStorage):
|
||||
"""
|
||||
The standard compressor file system storage that gzips storage files
|
||||
|
@@ -33,11 +33,13 @@ class CompressorNode(template.Node):
|
||||
if (time.time() > refresh_time) and not refreshed:
|
||||
# Store the stale value while the cache
|
||||
# revalidates for another MINT_DELAY seconds.
|
||||
self.cache_set(key, val, timeout=settings.COMPRESS_MINT_DELAY, refreshed=True)
|
||||
self.cache_set(key, val, refreshed=True,
|
||||
timeout=settings.COMPRESS_MINT_DELAY)
|
||||
return None
|
||||
return val
|
||||
|
||||
def cache_set(self, key, val, timeout=settings.COMPRESS_REBUILD_TIMEOUT, refreshed=False):
|
||||
def cache_set(self, key, val, refreshed=False,
|
||||
timeout=settings.COMPRESS_REBUILD_TIMEOUT):
|
||||
refresh_time = timeout + time.time()
|
||||
real_timeout = timeout + settings.COMPRESS_MINT_DELAY
|
||||
packed_val = (val, refresh_time, refreshed)
|
||||
@@ -47,13 +49,15 @@ class CompressorNode(template.Node):
|
||||
return "%s.%s.%s" % (compressor.cachekey, self.mode, self.kind)
|
||||
|
||||
def render(self, context, forced=False):
|
||||
if (settings.COMPRESS_ENABLED and settings.COMPRESS_OFFLINE) and not forced:
|
||||
if (settings.COMPRESS_ENABLED and
|
||||
settings.COMPRESS_OFFLINE) and not forced:
|
||||
key = get_offline_cachekey(self.nodelist)
|
||||
content = cache.get(key)
|
||||
if content:
|
||||
return content
|
||||
content = self.nodelist.render(context)
|
||||
if (not settings.COMPRESS_ENABLED or not len(content.strip())) and not forced:
|
||||
if (not settings.COMPRESS_ENABLED or
|
||||
not len(content.strip())) and not forced:
|
||||
return content
|
||||
compressor = self.compressor_cls(content)
|
||||
cachekey = self.cache_key(compressor)
|
||||
|
@@ -8,14 +8,17 @@ from django.conf import settings
|
||||
from compressor.exceptions import FilterError
|
||||
|
||||
try:
|
||||
any = any
|
||||
any
|
||||
|
||||
except NameError:
|
||||
|
||||
def any(seq):
|
||||
for item in seq:
|
||||
if item:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_class(class_string, exception=FilterError):
|
||||
"""
|
||||
Convert a string version of a function name to the callable object.
|
||||
@@ -32,6 +35,7 @@ def get_class(class_string, exception=FilterError):
|
||||
return cls
|
||||
raise exception('Failed to import %s' % class_string)
|
||||
|
||||
|
||||
def get_mod_func(callback):
|
||||
"""
|
||||
Converts 'django.views.news.stories.story_detail' to
|
||||
@@ -41,7 +45,8 @@ def get_mod_func(callback):
|
||||
dot = callback.rindex('.')
|
||||
except ValueError:
|
||||
return callback, ''
|
||||
return callback[:dot], callback[dot+1:]
|
||||
return callback[:dot], callback[dot + 1:]
|
||||
|
||||
|
||||
def walk(root, topdown=True, onerror=None, followlinks=False):
|
||||
"""
|
||||
@@ -56,208 +61,210 @@ 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.
|
||||
|
||||
# Taken from Django 1.3 and before that from Python 2.7
|
||||
# with permission from the original author.
|
||||
def _resolve_name(name, package, level):
|
||||
"""Return the absolute name of the module to be imported."""
|
||||
if not hasattr(package, 'rindex'):
|
||||
raise ValueError("'package' not set to a string")
|
||||
dot = len(package)
|
||||
for x in xrange(level, 1, -1):
|
||||
try:
|
||||
dot = package.rindex('.', 0, dot)
|
||||
except ValueError:
|
||||
raise ValueError("attempted relative import beyond top-level "
|
||||
"package")
|
||||
return "%s.%s" % (package[:dot], name)
|
||||
"""Return the absolute name of the module to be imported."""
|
||||
if not hasattr(package, 'rindex'):
|
||||
raise ValueError("'package' not set to a string")
|
||||
dot = len(package)
|
||||
for x in xrange(level, 1, -1):
|
||||
try:
|
||||
dot = package.rindex('.', 0, dot)
|
||||
except ValueError:
|
||||
raise ValueError("attempted relative import beyond top-level "
|
||||
"package")
|
||||
return "%s.%s" % (package[:dot], name)
|
||||
|
||||
|
||||
def import_module(name, package=None):
|
||||
"""Import a module.
|
||||
"""Import a module.
|
||||
|
||||
The 'package' argument is required when performing a relative import. It
|
||||
specifies the package to use as the anchor point from which to resolve the
|
||||
relative import to an absolute import.
|
||||
The 'package' argument is required when performing a relative import. It
|
||||
specifies the package to use as the anchor point from which to resolve the
|
||||
relative import to an absolute import.
|
||||
|
||||
"""
|
||||
if name.startswith('.'):
|
||||
if not package:
|
||||
raise TypeError("relative imports require the 'package' argument")
|
||||
level = 0
|
||||
for character in name:
|
||||
if character != '.':
|
||||
break
|
||||
level += 1
|
||||
name = _resolve_name(name[level:], package, level)
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
"""
|
||||
if name.startswith('.'):
|
||||
if not package:
|
||||
raise TypeError("relative imports require the 'package' argument")
|
||||
level = 0
|
||||
for character in name:
|
||||
if character != '.':
|
||||
break
|
||||
level += 1
|
||||
name = _resolve_name(name[level:], package, level)
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
class AppSettings(object):
|
||||
"""
|
||||
An app setting object to be used for handling app setting defaults
|
||||
gracefully and providing a nice API for them. Say you have an app
|
||||
called ``myapp`` and want to define a few defaults, and refer to the
|
||||
defaults easily in the apps code. Add a ``settings.py`` to your app::
|
||||
"""
|
||||
An app setting object to be used for handling app setting defaults
|
||||
gracefully and providing a nice API for them. Say you have an app
|
||||
called ``myapp`` and want to define a few defaults, and refer to the
|
||||
defaults easily in the apps code. Add a ``settings.py`` to your app::
|
||||
|
||||
from path.to.utils import AppSettings
|
||||
from path.to.utils import AppSettings
|
||||
|
||||
class MyAppSettings(AppSettings):
|
||||
SETTING_1 = "one"
|
||||
SETTING_2 = (
|
||||
"two",
|
||||
)
|
||||
class MyAppSettings(AppSettings):
|
||||
SETTING_1 = "one"
|
||||
SETTING_2 = (
|
||||
"two",
|
||||
)
|
||||
|
||||
Then initialize the setting with the correct prefix in the location of
|
||||
of your choice, e.g. ``conf.py`` of the app module::
|
||||
Then initialize the setting with the correct prefix in the location of
|
||||
of your choice, e.g. ``conf.py`` of the app module::
|
||||
|
||||
settings = MyAppSettings(prefix="MYAPP")
|
||||
settings = MyAppSettings(prefix="MYAPP")
|
||||
|
||||
The ``MyAppSettings`` instance will automatically look at Django's
|
||||
global setting to determine each of the settings and respect the
|
||||
provided ``prefix``. E.g. adding this to your site's ``settings.py``
|
||||
will set the ``SETTING_1`` setting accordingly::
|
||||
The ``MyAppSettings`` instance will automatically look at Django's
|
||||
global setting to determine each of the settings and respect the
|
||||
provided ``prefix``. E.g. adding this to your site's ``settings.py``
|
||||
will set the ``SETTING_1`` setting accordingly::
|
||||
|
||||
MYAPP_SETTING_1 = "uno"
|
||||
MYAPP_SETTING_1 = "uno"
|
||||
|
||||
Usage
|
||||
-----
|
||||
Usage
|
||||
-----
|
||||
|
||||
Instead of using ``from django.conf import settings`` as you would
|
||||
usually do, you can switch to using your apps own settings module
|
||||
to access the app settings::
|
||||
Instead of using ``from django.conf import settings`` as you would
|
||||
usually do, you can switch to using your apps own settings module
|
||||
to access the app settings::
|
||||
|
||||
from myapp.conf import settings
|
||||
from myapp.conf import settings
|
||||
|
||||
print myapp_settings.MYAPP_SETTING_1
|
||||
print myapp_settings.MYAPP_SETTING_1
|
||||
|
||||
``AppSettings`` instances also work as pass-throughs for other
|
||||
global settings that aren't related to the app. For example the
|
||||
following code is perfectly valid::
|
||||
``AppSettings`` instances also work as pass-throughs for other
|
||||
global settings that aren't related to the app. For example the
|
||||
following code is perfectly valid::
|
||||
|
||||
from myapp.conf import settings
|
||||
from myapp.conf import settings
|
||||
|
||||
if "myapp" in settings.INSTALLED_APPS:
|
||||
print "yay, myapp is installed!"
|
||||
if "myapp" in settings.INSTALLED_APPS:
|
||||
print "yay, myapp is installed!"
|
||||
|
||||
Custom handling
|
||||
---------------
|
||||
Custom handling
|
||||
---------------
|
||||
|
||||
Each of the settings can be individually configured with callbacks.
|
||||
For example, in case a value of a setting depends on other settings
|
||||
or other dependencies. The following example sets one setting to a
|
||||
different value depending on a global setting::
|
||||
Each of the settings can be individually configured with callbacks.
|
||||
For example, in case a value of a setting depends on other settings
|
||||
or other dependencies. The following example sets one setting to a
|
||||
different value depending on a global setting::
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf import settings
|
||||
|
||||
class MyCustomAppSettings(AppSettings):
|
||||
ENABLED = True
|
||||
class MyCustomAppSettings(AppSettings):
|
||||
ENABLED = True
|
||||
|
||||
def configure_enabled(self, value):
|
||||
return value and not self.DEBUG
|
||||
def configure_enabled(self, value):
|
||||
return value and not self.DEBUG
|
||||
|
||||
custom_settings = MyCustomAppSettings("MYAPP")
|
||||
custom_settings = MyCustomAppSettings("MYAPP")
|
||||
|
||||
The value of ``custom_settings.MYAPP_ENABLED`` will vary depending on the
|
||||
value of the global ``DEBUG`` setting.
|
||||
The value of ``custom_settings.MYAPP_ENABLED`` will vary depending on the
|
||||
value of the global ``DEBUG`` setting.
|
||||
|
||||
Each of the app settings can be customized by providing
|
||||
a method ``configure_<lower_setting_name>`` that takes the default
|
||||
value as defined in the class attributes as the only parameter.
|
||||
The method needs to return the value to be use for the setting in
|
||||
question.
|
||||
"""
|
||||
def __dir__(self):
|
||||
return sorted(list(set(self.__dict__.keys() + dir(settings))))
|
||||
Each of the app settings can be customized by providing
|
||||
a method ``configure_<lower_setting_name>`` that takes the default
|
||||
value as defined in the class attributes as the only parameter.
|
||||
The method needs to return the value to be use for the setting in
|
||||
question.
|
||||
"""
|
||||
def __dir__(self):
|
||||
return sorted(list(set(self.__dict__.keys() + dir(settings))))
|
||||
|
||||
__members__ = lambda self: self.__dir__()
|
||||
__members__ = lambda self: self.__dir__()
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name.startswith(self._prefix):
|
||||
raise AttributeError("%r object has no attribute %r" %
|
||||
(self.__class__.__name__, name))
|
||||
return getattr(settings, name)
|
||||
def __getattr__(self, name):
|
||||
if name.startswith(self._prefix):
|
||||
raise AttributeError("%r object has no attribute %r" %
|
||||
(self.__class__.__name__, name))
|
||||
return getattr(settings, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
super(AppSettings, self).__setattr__(name, value)
|
||||
if name in dir(settings):
|
||||
setattr(settings, name, value)
|
||||
def __setattr__(self, name, value):
|
||||
super(AppSettings, self).__setattr__(name, value)
|
||||
if name in dir(settings):
|
||||
setattr(settings, name, value)
|
||||
|
||||
def __init__(self, prefix):
|
||||
super(AppSettings, self).__setattr__('_prefix', prefix)
|
||||
for name, value in filter(self.issetting, getmembers(self.__class__)):
|
||||
prefixed_name = "%s_%s" % (prefix.upper(), name.upper())
|
||||
value = getattr(settings, prefixed_name, value)
|
||||
callback = getattr(self, "configure_%s" % name.lower(), None)
|
||||
if callable(callback):
|
||||
value = callback(value)
|
||||
delattr(self.__class__, name)
|
||||
setattr(self, prefixed_name, value)
|
||||
def __init__(self, prefix):
|
||||
super(AppSettings, self).__setattr__('_prefix', prefix)
|
||||
for name, value in filter(self.issetting, getmembers(self.__class__)):
|
||||
prefixed_name = "%s_%s" % (prefix.upper(), name.upper())
|
||||
value = getattr(settings, prefixed_name, value)
|
||||
callback = getattr(self, "configure_%s" % name.lower(), None)
|
||||
if callable(callback):
|
||||
value = callback(value)
|
||||
delattr(self.__class__, name)
|
||||
setattr(self, prefixed_name, value)
|
||||
|
||||
def issetting(self, (name, value)):
|
||||
return name == name.upper()
|
||||
def issetting(self, (name, value)):
|
||||
return name == name.upper()
|
||||
|
||||
|
||||
class cached_property(object):
|
||||
"""Property descriptor that caches the return value
|
||||
of the get function.
|
||||
"""Property descriptor that caches the return value
|
||||
of the get function.
|
||||
|
||||
*Examples*
|
||||
*Examples*
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: python
|
||||
|
||||
@cached_property
|
||||
def connection(self):
|
||||
return Connection()
|
||||
@cached_property
|
||||
def connection(self):
|
||||
return Connection()
|
||||
|
||||
@connection.setter # Prepares stored value
|
||||
def connection(self, value):
|
||||
if value is None:
|
||||
raise TypeError("Connection must be a connection")
|
||||
return value
|
||||
@connection.setter # Prepares stored value
|
||||
def connection(self, value):
|
||||
if value is None:
|
||||
raise TypeError("Connection must be a connection")
|
||||
return value
|
||||
|
||||
@connection.deleter
|
||||
def connection(self, value):
|
||||
# Additional action to do at del(self.attr)
|
||||
if value is not None:
|
||||
print("Connection %r deleted" % (value, ))
|
||||
"""
|
||||
@connection.deleter
|
||||
def connection(self, value):
|
||||
# Additional action to do at del(self.attr)
|
||||
if value is not None:
|
||||
print("Connection %r deleted" % (value, ))
|
||||
"""
|
||||
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
|
||||
self.__get = fget
|
||||
self.__set = fset
|
||||
self.__del = fdel
|
||||
self.__doc__ = doc or fget.__doc__
|
||||
self.__name__ = fget.__name__
|
||||
self.__module__ = fget.__module__
|
||||
|
||||
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
|
||||
self.__get = fget
|
||||
self.__set = fset
|
||||
self.__del = fdel
|
||||
self.__doc__ = doc or fget.__doc__
|
||||
self.__name__ = fget.__name__
|
||||
self.__module__ = fget.__module__
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
try:
|
||||
return obj.__dict__[self.__name__]
|
||||
except KeyError:
|
||||
value = obj.__dict__[self.__name__] = self.__get(obj)
|
||||
return value
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
try:
|
||||
return obj.__dict__[self.__name__]
|
||||
except KeyError:
|
||||
value = obj.__dict__[self.__name__] = self.__get(obj)
|
||||
return value
|
||||
def __set__(self, obj, value):
|
||||
if obj is None:
|
||||
return self
|
||||
if self.__set is not None:
|
||||
value = self.__set(obj, value)
|
||||
obj.__dict__[self.__name__] = value
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if obj is None:
|
||||
return self
|
||||
if self.__set is not None:
|
||||
value = self.__set(obj, value)
|
||||
obj.__dict__[self.__name__] = value
|
||||
def __delete__(self, obj):
|
||||
if obj is None:
|
||||
return self
|
||||
try:
|
||||
value = obj.__dict__.pop(self.__name__)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if self.__del is not None:
|
||||
self.__del(obj, value)
|
||||
|
||||
def __delete__(self, obj):
|
||||
if obj is None:
|
||||
return self
|
||||
try:
|
||||
value = obj.__dict__.pop(self.__name__)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if self.__del is not None:
|
||||
self.__del(obj, value)
|
||||
def setter(self, fset):
|
||||
return self.__class__(self.__get, fset, self.__del)
|
||||
|
||||
def setter(self, fset):
|
||||
return self.__class__(self.__get, fset, self.__del)
|
||||
|
||||
def deleter(self, fdel):
|
||||
return self.__class__(self.__get, self.__set, fdel)
|
||||
def deleter(self, fdel):
|
||||
return self.__class__(self.__get, self.__set, fdel)
|
||||
|
Reference in New Issue
Block a user