Merge from upstream.

This commit is contained in:
Jonathan Lukens
2011-08-26 20:51:31 -04:00
28 changed files with 172 additions and 222 deletions

View File

@@ -1,16 +1,2 @@
VERSION = (0, 9, 2, "f", 0) # following PEP 386 # following PEP 386, versiontools will pick it up
DEV_N = None __version__ = (1, 0, 0, "alpha", 1)
def get_version():
version = "%s.%s" % (VERSION[0], VERSION[1])
if VERSION[2]:
version = "%s.%s" % (version, VERSION[2])
if VERSION[3] != "f":
version = "%s%s%s" % (version, VERSION[3], VERSION[4])
if DEV_N:
version = "%s.dev%s" % (version, DEV_N)
return version
__version__ = get_version()

View File

@@ -2,12 +2,14 @@ from __future__ import with_statement
import os import os
import codecs import codecs
from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.template import Context
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from compressor.cache import get_hexdigest, get_mtime from compressor.cache import get_hexdigest, get_mtime
from compressor.conf import settings
from compressor.exceptions import CompressorError, UncompressableFileError from compressor.exceptions import CompressorError, UncompressableFileError
from compressor.filters import CompilerFilter from compressor.filters import CompilerFilter
from compressor.storage import default_storage from compressor.storage import default_storage
@@ -27,13 +29,14 @@ class Compressor(object):
""" """
type = None type = None
def __init__(self, content=None, output_prefix="compressed", block_name=None): def __init__(self, content=None, output_prefix=None, context=None, *args, **kwargs):
self.content = content or "" self.content = content or ""
self.output_prefix = output_prefix self.output_prefix = output_prefix or "compressed"
self.output_dir = settings.COMPRESS_OUTPUT_DIR.strip('/') self.output_dir = settings.COMPRESS_OUTPUT_DIR.strip('/')
self.charset = settings.DEFAULT_CHARSET self.charset = settings.DEFAULT_CHARSET
self.storage = default_storage self.storage = default_storage
self.split_content = [] self.split_content = []
self.context = context or {}
self.extra_context = {} self.extra_context = {}
self.all_mimetypes = dict(settings.COMPRESS_PRECOMPILERS) self.all_mimetypes = dict(settings.COMPRESS_PRECOMPILERS)
self.finders = staticfiles.finders self.finders = staticfiles.finders
@@ -86,6 +89,11 @@ class Compressor(object):
except IOError, e: except IOError, e:
raise UncompressableFileError("IOError while processing " raise UncompressableFileError("IOError while processing "
"'%s': %s" % (filename, e)) "'%s': %s" % (filename, e))
except UnicodeDecodeError, e:
raise UncompressableFileError("UnicodeDecodeError while "
"processing '%s' with "
"charset %s: %s" %
(filename, charset, e))
@cached_property @cached_property
def parser(self): def parser(self):
@@ -250,7 +258,10 @@ class Compressor(object):
""" """
if context is None: if context is None:
context = {} context = {}
context.update(self.extra_context) final_context = Context()
post_compress.send(sender='django-compressor', name=self.block_name, type=self.type, mode=mode, context=context) final_context.update(context)
return render_to_string( final_context.update(self.context)
"compressor/%s_%s.html" % (self.type, mode), context) final_context.update(self.extra_context)
post_compress.send(sender='django-compressor', name=self.block_name, type=self.type, mode=mode, context=final_context)
return render_to_string("compressor/%s_%s.html" %
(self.type, mode), final_context)

View File

@@ -2,14 +2,17 @@ import os
import socket import socket
import time import time
from django.conf import settings
from django.core.cache import get_cache from django.core.cache import get_cache
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
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.utils import get_mod_func from compressor.utils import get_mod_func
_cachekey_func = None
def get_hexdigest(plaintext, length=None): def get_hexdigest(plaintext, length=None):
digest = md5_constructor(smart_str(plaintext)).hexdigest() digest = md5_constructor(smart_str(plaintext)).hexdigest()
@@ -17,18 +20,26 @@ def get_hexdigest(plaintext, length=None):
return digest[:length] return digest[:length]
return digest return digest
def simple_cachekey(key): def simple_cachekey(key):
return 'django_compressor.%s' % smart_str(key) return 'django_compressor.%s' % smart_str(key)
def socket_cachekey(key): def socket_cachekey(key):
return "django_compressor.%s.%s" % (socket.gethostname(), smart_str(key)) return "django_compressor.%s.%s" % (socket.gethostname(), smart_str(key))
try:
mod_name, func_name = get_mod_func(settings.COMPRESS_CACHE_KEY_FUNCTION) def get_cachekey(*args, **kwargs):
get_cachekey = getattr(import_module(mod_name), func_name) global _cachekey_func
except (AttributeError, ImportError), e: if _cachekey_func is None:
raise ImportError("Couldn't import cache key function %s: %s" % try:
(settings.COMPRESS_CACHE_KEY_FUNCTION, e)) 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" %
(settings.COMPRESS_CACHE_KEY_FUNCTION, e))
return _cachekey_func(*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))
@@ -78,12 +89,13 @@ def cache_get(key):
return val return val
def cache_set(key, val, refreshed=False, def cache_set(key, val, refreshed=False, timeout=None):
timeout=settings.COMPRESS_REBUILD_TIMEOUT): if timeout is None:
timeout = settings.COMPRESS_REBUILD_TIMEOUT
refresh_time = timeout + time.time() refresh_time = timeout + time.time()
real_timeout = timeout + settings.COMPRESS_MINT_DELAY real_timeout = timeout + settings.COMPRESS_MINT_DELAY
packed_val = (val, refresh_time, refreshed) packed_val = (val, refresh_time, refreshed)
return cache.set(key, packed_val, real_timeout) return cache.set(key, packed_val, real_timeout)
cache = get_cache(settings.COMPRESS_CACHE_BACKEND) cache = SimpleLazyObject(lambda: get_cache(settings.COMPRESS_CACHE_BACKEND))

View File

@@ -1,4 +1,5 @@
from compressor.conf import settings from django.conf import settings
from compressor.base import Compressor, SOURCE_HUNK, SOURCE_FILE from compressor.base import Compressor, SOURCE_HUNK, SOURCE_FILE
from compressor.exceptions import UncompressableFileError from compressor.exceptions import UncompressableFileError
@@ -7,8 +8,9 @@ class CssCompressor(Compressor):
template_name = "compressor/css.html" template_name = "compressor/css.html"
template_name_inline = "compressor/css_inline.html" template_name_inline = "compressor/css_inline.html"
def __init__(self, content=None, output_prefix="css", block_name=None): def __init__(self, content=None, output_prefix="css", context=None):
super(CssCompressor, self).__init__(content, output_prefix, block_name) super(CssCompressor, self).__init__(content=content,
output_prefix=output_prefix, context=context)
self.filters = list(settings.COMPRESS_CSS_FILTERS) self.filters = list(settings.COMPRESS_CSS_FILTERS)
self.type = output_prefix self.type = output_prefix
@@ -34,7 +36,8 @@ class CssCompressor(Compressor):
if self.media_nodes and self.media_nodes[-1][0] == media: if self.media_nodes and self.media_nodes[-1][0] == media:
self.media_nodes[-1][1].split_content.append(data) self.media_nodes[-1][1].split_content.append(data)
else: else:
node = CssCompressor(self.parser.elem_str(elem), block_name=self.block_name) node = CssCompressor(content=self.parser.elem_str(elem),
context=self.context)
node.split_content.append(data) node.split_content.append(data)
self.media_nodes.append((media, node)) self.media_nodes.append((media, node))
return self.split_content return self.split_content

View File

@@ -3,11 +3,11 @@ import os
import logging import logging
import subprocess import subprocess
import tempfile import tempfile
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.files.temp import NamedTemporaryFile from django.core.files.temp import NamedTemporaryFile
from django.utils.importlib import import_module from django.utils.importlib import import_module
from compressor.conf import settings
from compressor.exceptions import FilterError from compressor.exceptions import FilterError
from compressor.utils import get_mod_func from compressor.utils import get_mod_func
from compressor.utils.stringformat import FormattableString as fstr from compressor.utils.stringformat import FormattableString as fstr

View File

@@ -1,4 +1,5 @@
from compressor.conf import settings from django.conf import settings
from compressor.filters import CompilerFilter from compressor.filters import CompilerFilter

View File

@@ -2,8 +2,9 @@ import os
import re import re
import posixpath import posixpath
from django.conf import settings
from compressor.cache import get_hexdigest, get_hashed_mtime from compressor.cache import get_hexdigest, get_hashed_mtime
from compressor.conf import settings
from compressor.filters import FilterBase from compressor.filters import FilterBase
from compressor.utils import staticfiles from compressor.utils import staticfiles

View File

@@ -1,4 +1,5 @@
from compressor.conf import settings from django.conf import settings
from compressor.filters import CompilerFilter from compressor.filters import CompilerFilter

View File

@@ -3,7 +3,8 @@ import re
import mimetypes import mimetypes
from base64 import b64encode from base64 import b64encode
from compressor.conf import settings from django.conf import settings
from compressor.filters import FilterBase from compressor.filters import FilterBase

View File

@@ -1,4 +1,5 @@
from compressor.conf import settings from django.conf import settings
from compressor.filters import CompilerFilter from compressor.filters import CompilerFilter

View File

@@ -1,4 +1,5 @@
from compressor.conf import settings from django.conf import settings
from compressor.base import Compressor, SOURCE_HUNK, SOURCE_FILE from compressor.base import Compressor, SOURCE_HUNK, SOURCE_FILE
from compressor.exceptions import UncompressableFileError from compressor.exceptions import UncompressableFileError
@@ -7,8 +8,8 @@ class JsCompressor(Compressor):
template_name = "compressor/js.html" template_name = "compressor/js.html"
template_name_inline = "compressor/js_inline.html" template_name_inline = "compressor/js_inline.html"
def __init__(self, content=None, output_prefix="js", block_name=None): def __init__(self, content=None, output_prefix="js", context=None):
super(JsCompressor, self).__init__(content, output_prefix, block_name) super(JsCompressor, self).__init__(content, output_prefix, context)
self.filters = list(settings.COMPRESS_JS_FILTERS) self.filters = list(settings.COMPRESS_JS_FILTERS)
self.type = output_prefix self.type = output_prefix

View File

@@ -9,6 +9,7 @@ try:
except ImportError: except ImportError:
from StringIO import StringIO from StringIO import StringIO
from django.conf import settings
from django.core.management.base import NoArgsCommand, CommandError from django.core.management.base import NoArgsCommand, CommandError
from django.template import Context, Template, TemplateDoesNotExist, TemplateSyntaxError from django.template import Context, Template, TemplateDoesNotExist, TemplateSyntaxError
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
@@ -22,7 +23,6 @@ except ImportError:
CachedLoader = None CachedLoader = None
from compressor.cache import cache, get_offline_cachekey from compressor.cache import cache, get_offline_cachekey
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
from compressor.utils import walk, any from compressor.utils import walk, any

View File

@@ -2,10 +2,10 @@ import fnmatch
import os import os
from optparse import make_option from optparse import make_option
from django.conf import settings
from django.core.management.base import NoArgsCommand, CommandError from django.core.management.base import NoArgsCommand, CommandError
from compressor.cache import cache, get_mtime, get_mtime_cachekey from compressor.cache import cache, get_mtime, get_mtime_cachekey
from compressor.conf import settings
from compressor.utils import walk from compressor.utils import walk

View File

@@ -1,11 +1,12 @@
import os import os
from django import VERSION as DJANGO_VERSION from django import VERSION as DJANGO_VERSION
from django.conf import settings as global_settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from compressor.utils.settings import AppSettings from appconf import AppConf
class CompressorSettings(AppSettings):
class CompressorConf(AppConf):
# Main switch # Main switch
ENABLED = False ENABLED = False
# Allows changing verbosity from the settings. # Allows changing verbosity from the settings.
@@ -60,14 +61,17 @@ class CompressorSettings(AppSettings):
# The context to be used when compressing the files "offline" # The context to be used when compressing the files "offline"
OFFLINE_CONTEXT = {} OFFLINE_CONTEXT = {}
class Meta:
prefix = 'compress'
def configure_enabled(self, value): def configure_enabled(self, value):
return value or not global_settings.DEBUG return value or not settings.DEBUG
def configure_root(self, value): def configure_root(self, value):
if value is None: if value is None:
value = getattr(global_settings, 'STATIC_ROOT', None) value = getattr(settings, 'STATIC_ROOT', None)
if not value: if not value:
value = global_settings.MEDIA_ROOT value = settings.MEDIA_ROOT
if not value: if not value:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"The COMPRESS_ROOT setting must be set.") "The COMPRESS_ROOT setting must be set.")
@@ -76,9 +80,9 @@ class CompressorSettings(AppSettings):
def configure_url(self, value): def configure_url(self, value):
# Uses Django 1.3's STATIC_URL 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: if value is None:
value = getattr(global_settings, "STATIC_URL", None) value = getattr(settings, "STATIC_URL", None)
if not value: if not value:
value = global_settings.MEDIA_URL value = settings.MEDIA_URL
if not value.endswith("/"): if not value.endswith("/"):
raise ImproperlyConfigured("The URL settings (e.g. COMPRESS_URL) " raise ImproperlyConfigured("The URL settings (e.g. COMPRESS_URL) "
"must have a trailing slash.") "must have a trailing slash.")
@@ -87,11 +91,11 @@ class CompressorSettings(AppSettings):
def configure_cache_backend(self, value): def configure_cache_backend(self, value):
if value is None: if value is None:
# If we are on Django 1.3 AND using the new CACHES setting... # If we are on Django 1.3 AND using the new CACHES setting...
if DJANGO_VERSION[:2] >= (1, 3) and hasattr(global_settings, "CACHES"): if DJANGO_VERSION[:2] >= (1, 3) and hasattr(settings, "CACHES"):
value = "default" value = "default"
else: else:
# falling back to the old CACHE_BACKEND setting # falling back to the old CACHE_BACKEND setting
value = getattr(global_settings, "CACHE_BACKEND", None) value = getattr(settings, "CACHE_BACKEND", None)
if not value: if not value:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"Please specify a cache backend in your settings.") "Please specify a cache backend in your settings.")
@@ -100,11 +104,11 @@ class CompressorSettings(AppSettings):
def configure_offline_context(self, value): def configure_offline_context(self, value):
if not value: if not value:
value = { value = {
"MEDIA_URL": global_settings.MEDIA_URL, "MEDIA_URL": settings.MEDIA_URL,
} }
# Adds the 1.3 STATIC_URL setting to the context if available # Adds the 1.3 STATIC_URL setting to the context if available
if getattr(global_settings, "STATIC_URL", None): if getattr(settings, "STATIC_URL", None):
value["STATIC_URL"] = global_settings.STATIC_URL value["STATIC_URL"] = settings.STATIC_URL
return value return value
def configure_precompilers(self, value): def configure_precompilers(self, value):
@@ -112,5 +116,3 @@ class CompressorSettings(AppSettings):
raise ImproperlyConfigured("The COMPRESS_PRECOMPILERS setting " raise ImproperlyConfigured("The COMPRESS_PRECOMPILERS setting "
"must be a list or tuple. Check for missing commas.") "must be a list or tuple. Check for missing commas.")
return value return value
settings = CompressorSettings(prefix="COMPRESS")

View File

@@ -2,11 +2,10 @@ import gzip
from os import path from os import path
from datetime import datetime from datetime import datetime
from django.conf import settings
from django.core.files.storage import FileSystemStorage, get_storage_class from django.core.files.storage import FileSystemStorage, get_storage_class
from django.utils.functional import LazyObject from django.utils.functional import LazyObject
from compressor.conf import settings
class CompressorFileStorage(FileSystemStorage): class CompressorFileStorage(FileSystemStorage):
""" """

View File

@@ -1 +1 @@
<link rel="stylesheet" href="{{ url }}" type="text/css"{% if media %} media="{{ media }}"{% endif %} /> <link rel="stylesheet" href="{{ url }}" type="text/css"{% if media %} media="{{ media }}"{% endif %} />

View File

@@ -1,9 +1,9 @@
from django import template from django import template
from django.conf import settings
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_cachekey, get_templatetag_cachekey)
from compressor.conf import settings
from compressor.utils import get_class from compressor.utils import get_class
register = template.Library() register = template.Library()
@@ -11,10 +11,6 @@ register = template.Library()
OUTPUT_FILE = 'file' OUTPUT_FILE = 'file'
OUTPUT_INLINE = 'inline' OUTPUT_INLINE = 'inline'
OUTPUT_MODES = (OUTPUT_FILE, OUTPUT_INLINE) OUTPUT_MODES = (OUTPUT_FILE, OUTPUT_INLINE)
COMPRESSORS = {
"css": settings.COMPRESS_CSS_COMPRESSOR,
"js": settings.COMPRESS_JS_COMPRESSOR,
}
class CompressorNode(template.Node): class CompressorNode(template.Node):
@@ -22,9 +18,17 @@ class CompressorNode(template.Node):
self.nodelist = nodelist self.nodelist = nodelist
self.kind = kind self.kind = kind
self.mode = mode self.mode = mode
self.name = name
self.compressor_cls = get_class( def compressor_cls(self, *args, **kwargs):
COMPRESSORS.get(self.kind), exception=ImproperlyConfigured) 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 debug_mode(self, context): def debug_mode(self, context):
if settings.COMPRESS_DEBUG_TOGGLE: if settings.COMPRESS_DEBUG_TOGGLE:
@@ -66,7 +70,8 @@ class CompressorNode(template.Node):
return cached_offline return cached_offline
# 3. Prepare the actual compressor and check cache # 3. Prepare the actual compressor and check cache
compressor = self.compressor_cls(self.nodelist.render(context), block_name=self.name) compressor = self.compressor_cls(content=self.nodelist.render(context),
context=context)
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
@@ -127,9 +132,6 @@ def compress(parser, token):
"%r tag requires either one or two arguments." % args[0]) "%r tag requires either one or two arguments." % args[0])
kind = args[1] kind = args[1]
if not kind in COMPRESSORS.keys():
raise template.TemplateSyntaxError(
"%r's argument must be 'js' or 'css'." % args[0])
if len(args) == 3: if len(args) == 3:
mode = args[2] mode = args[2]

View File

@@ -1,107 +0,0 @@
from inspect import getmembers
from django.conf import settings
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::
from path.to.utils import AppSettings
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::
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::
MYAPP_SETTING_1 = "uno"
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::
from myapp.conf import settings
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::
from myapp.conf import settings
if "myapp" in settings.INSTALLED_APPS:
print "yay, myapp is installed!"
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::
from django.conf import settings
class MyCustomAppSettings(AppSettings):
ENABLED = True
def configure_enabled(self, value):
return value and not self.DEBUG
custom_settings = MyCustomAppSettings("MYAPP")
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))))
__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 __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 issetting(self, (name, value)):
return name == name.upper()

View File

@@ -40,8 +40,14 @@ HEAD
- Added new CallbackOutputFilter to ease the implementation of Python-based - Added new CallbackOutputFilter to ease the implementation of Python-based
callback filters that only need to pass the content to a callable. callback filters that only need to pass the content to a callable.
- Make use of `django-appconf`_ for settings handling and `versiontools`_
for versions.
- Uses the current context when rendering the render templates.
.. _`Slim It`: http://slimit.org/ .. _`Slim It`: http://slimit.org/
.. _`django-appconf`: http://django-appconf.rtfd.org/
.. _`versiontools`: http://pypi.python.org/pypi/versiontools
0.9.2 0.9.2
----- -----

View File

@@ -1,11 +1,12 @@
import os import os
import sys import sys
import codecs
from fnmatch import fnmatchcase from fnmatch import fnmatchcase
from distutils.util import convert_path from distutils.util import convert_path
from setuptools import setup, find_packages from setuptools import setup, find_packages
def read(fname): def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read() return codecs.open(os.path.join(os.path.dirname(__file__), fname)).read()
# Provided as an attribute, so you can append to these instead # Provided as an attribute, so you can append to these instead
# of replicating them: # of replicating them:
@@ -97,16 +98,13 @@ def find_package_data(
return out return out
README = read('README.rst')
VERSION = __import__("compressor").__version__
setup( setup(
name = "django_compressor", name = "django_compressor",
version = VERSION, version = ":versiontools:compressor:",
url = 'http://django_compressor.readthedocs.org/', url = 'http://django_compressor.readthedocs.org/',
license = 'BSD', license = 'MIT',
description = "Compresses linked and inline JavaScript or CSS into single cached files.", description = "Compresses linked and inline JavaScript or CSS into single cached files.",
long_description = README, long_description = read('README.rst'),
author = 'Jannis Leidel', author = 'Jannis Leidel',
author_email = 'jannis@leidel.info', author_email = 'jannis@leidel.info',
packages = find_packages(exclude=['tests', 'tests.*']), packages = find_packages(exclude=['tests', 'tests.*']),
@@ -115,10 +113,19 @@ setup(
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
'Framework :: Django', 'Framework :: Django',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License', 'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP',
], ],
zip_safe = False, zip_safe = False,
install_requires=[
'django-appconf >= 0.4',
],
setup_requires=[
'versiontools >= 1.6',
],
) )

24
tests/settings.py Normal file
View File

@@ -0,0 +1,24 @@
import os
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
COMPRESS_CACHE_BACKEND = 'locmem://'
DATABASE_ENGINE = 'sqlite3'
INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.sites',
'django.contrib.auth',
'django.contrib.admin',
'compressor',
'tests',
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(TEST_DIR, 'media')
TEMPLATE_DIRS = (
os.path.join(TEST_DIR, 'templates'),
)

View File

@@ -4,12 +4,11 @@ import re
from BeautifulSoup import BeautifulSoup from BeautifulSoup import BeautifulSoup
from django.conf import settings
from django.core.cache.backends import locmem from django.core.cache.backends import locmem
from django.test import TestCase from django.test import TestCase
from compressor.base import SOURCE_HUNK, SOURCE_FILE from compressor.base import SOURCE_HUNK, SOURCE_FILE
from compressor.cache import get_hexdigest
from compressor.conf import settings
from compressor.css import CssCompressor from compressor.css import CssCompressor
from compressor.js import JsCompressor from compressor.js import JsCompressor
@@ -22,6 +21,7 @@ def css_tag(href, **kwargs):
test_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) test_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
class CompressorTestCase(TestCase): class CompressorTestCase(TestCase):
def setUp(self): def setUp(self):
@@ -81,7 +81,7 @@ class CompressorTestCase(TestCase):
def test_js_split(self): def test_js_split(self):
out = [ out = [
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'js/one.js'), u'js/one.js', '<script src="/media/js/one.js" type="text/javascript"></script>'), (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'js/one.js'), u'js/one.js', '<script src="/media/js/one.js" type="text/javascript"></script>'),
(SOURCE_HUNK, u'obj.value = "value";', None, '<script type="text/javascript">obj.value = "value";</script>') (SOURCE_HUNK, u'obj.value = "value";', None, '<script type="text/javascript">obj.value = "value";</script>'),
] ]
split = self.js_node.split_contents() split = self.js_node.split_contents()
split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split] split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split]
@@ -127,7 +127,6 @@ class CompressorTestCase(TestCase):
settings.COMPRESS_OUTPUT_DIR = old_output_dir settings.COMPRESS_OUTPUT_DIR = old_output_dir
class CssMediaTestCase(TestCase): class CssMediaTestCase(TestCase):
def setUp(self): def setUp(self):
self.css = """\ self.css = """\
@@ -151,8 +150,6 @@ class CssMediaTestCase(TestCase):
self.assertEqual(media, [l.get('media', None) for l in links]) self.assertEqual(media, [l.get('media', None) for l in links])
class VerboseTestCase(CompressorTestCase): class VerboseTestCase(CompressorTestCase):
def setUp(self): def setUp(self):
@@ -165,5 +162,3 @@ class CacheBackendTestCase(CompressorTestCase):
def test_correct_backend(self): def test_correct_backend(self):
from compressor.cache import cache from compressor.cache import cache
self.assertEqual(cache.__class__, locmem.CacheClass) self.assertEqual(cache.__class__, locmem.CacheClass)

View File

@@ -3,10 +3,10 @@ import os
import sys import sys
from unittest2 import skipIf from unittest2 import skipIf
from django.conf import settings
from django.test import TestCase from django.test import TestCase
from compressor.cache import get_hashed_mtime from compressor.cache import get_hashed_mtime
from compressor.conf import settings
from compressor.css import CssCompressor from compressor.css import CssCompressor
from compressor.utils import find_command from compressor.utils import find_command
from compressor.filters.base import CompilerFilter from compressor.filters.base import CompilerFilter

View File

@@ -1,10 +1,10 @@
from __future__ import with_statement from __future__ import with_statement
import os import os
from django.conf import settings
from django.template import Template, Context from django.template import Template, Context
from django.test import TestCase from django.test import TestCase
from compressor.conf import settings
from compressor.management.commands.compress import Command as CompressCommand from compressor.management.commands.compress import Command as CompressCommand
from .base import test_dir, css_tag from .base import test_dir, css_tag
@@ -30,11 +30,11 @@ class OfflineGenerationTestCase(TestCase):
count, result = CompressCommand().compress() count, result = CompressCommand().compress()
self.assertEqual(5, count) self.assertEqual(5, count)
self.assertEqual([ self.assertEqual([
css_tag('/media/CACHE/css/cd579b7deb7d.css')+'\n', css_tag('/media/CACHE/css/cd579b7deb7d.css'),
u'<script type="text/javascript" src="/media/CACHE/js/0a2bb9a287c0.js"></script>', u'<script type="text/javascript" src="/media/CACHE/js/0a2bb9a287c0.js"></script>',
u'<script type="text/javascript" src="/media/CACHE/js/fb1736ad48b7.js"></script>', u'<script type="text/javascript" src="/media/CACHE/js/fb1736ad48b7.js"></script>',
u'<script type="text/javascript" src="/media/CACHE/js/770a7311729e.js"></script>', u'<script type="text/javascript" src="/media/CACHE/js/770a7311729e.js"></script>',
u'<link rel="stylesheet" href="/media/CACHE/css/67ed6aff7f7b.css" type="text/css" />\n', u'<link rel="stylesheet" href="/media/CACHE/css/67ed6aff7f7b.css" type="text/css" />',
], result) ], result)
# Template rendering should use the cache. FIXME: how to make sure of it ? Should we test the cache # Template rendering should use the cache. FIXME: how to make sure of it ? Should we test the cache
# key<->values ourselves? # key<->values ourselves?
@@ -49,11 +49,11 @@ class OfflineGenerationTestCase(TestCase):
count, result = CompressCommand().compress() count, result = CompressCommand().compress()
self.assertEqual(5, count) self.assertEqual(5, count)
self.assertEqual([ self.assertEqual([
css_tag('/media/CACHE/css/ee62fbfd116a.css')+'\n', css_tag('/media/CACHE/css/ee62fbfd116a.css'),
u'<script type="text/javascript" src="/media/CACHE/js/0a2bb9a287c0.js"></script>', u'<script type="text/javascript" src="/media/CACHE/js/0a2bb9a287c0.js"></script>',
u'<script type="text/javascript" src="/media/CACHE/js/fb1736ad48b7.js"></script>', u'<script type="text/javascript" src="/media/CACHE/js/fb1736ad48b7.js"></script>',
u'<script type="text/javascript" src="/media/CACHE/js/770a7311729e.js"></script>', u'<script type="text/javascript" src="/media/CACHE/js/770a7311729e.js"></script>',
u'<link rel="stylesheet" href="/media/CACHE/css/73e015f740c6.css" type="text/css" />\n', u'<link rel="stylesheet" href="/media/CACHE/css/73e015f740c6.css" type="text/css" />',
], result) ], result)
# Template rendering should use the cache. FIXME: how to make sure of it ? Should we test the cache # Template rendering should use the cache. FIXME: how to make sure of it ? Should we test the cache
# key<->values ourselves? # key<->values ourselves?

View File

@@ -2,8 +2,6 @@ from __future__ import with_statement
import os import os
from unittest2 import skipIf from unittest2 import skipIf
from BeautifulSoup import BeautifulSoup
try: try:
import lxml import lxml
except ImportError: except ImportError:
@@ -20,7 +18,8 @@ except ImportError:
BeautifulSoup = None BeautifulSoup = None
from compressor.conf import settings from django.conf import settings
from compressor.base import SOURCE_HUNK, SOURCE_FILE from compressor.base import SOURCE_HUNK, SOURCE_FILE
from .base import CompressorTestCase from .base import CompressorTestCase

View File

@@ -1,10 +1,10 @@
from __future__ import with_statement from __future__ import with_statement
from django.conf import settings
from django.core.files.storage import get_storage_class from django.core.files.storage import get_storage_class
from django.test import TestCase from django.test import TestCase
from compressor import base from compressor import base
from compressor.conf import settings
from .base import css_tag from .base import css_tag
from .templatetags import render from .templatetags import render

View File

@@ -1,10 +1,9 @@
from __future__ import with_statement from __future__ import with_statement
from django.conf import settings
from django.template import Template, Context, TemplateSyntaxError from django.template import Template, Context, TemplateSyntaxError
from django.test import TestCase from django.test import TestCase
from compressor.conf import settings
from .base import css_tag from .base import css_tag

View File

@@ -1,12 +1,18 @@
[tox] [tox]
setupdir = .. setupdir = ..
distribute = false distribute = false
downloadcache = {toxinidir}/_download/
[testenv] [testenv]
commands = commands =
{envpython} {toxinidir}/runtests.py [] {envbindir}/coverage erase
coverage html -d {envtmpdir}/coverage {envbindir}/coverage run --branch --include=*compressor* --omit=*test*,*rjsmin*,*cssmin*,*stringformat*,*models* {envbindir}/django-admin.py test {posargs:tests} --settings=tests.settings
{envbindir}/coverage report
{envbindir}/coverage html -d {envtmpdir}
echo "Type the following to open the coverage report: python -m webbrowser -t file://{envtmpdir}/index.html"
setenv =
PYTHONPATH = {toxinidir}/..
downloadcache = {toxworkdir}/_download/
[testenv:docs] [testenv:docs]
changedir = ../docs changedir = ../docs
@@ -20,7 +26,7 @@ commands =
basepython = python2.5 basepython = python2.5
deps = deps =
unittest2 unittest2
BeautifulSoup BeautifulSoup==3.2.0
html5lib html5lib
coverage coverage
django==1.2.5 django==1.2.5
@@ -29,7 +35,7 @@ deps =
basepython = python2.6 basepython = python2.6
deps = deps =
unittest2 unittest2
BeautifulSoup==3.2 BeautifulSoup==3.2.0
html5lib html5lib
coverage coverage
mock mock
@@ -39,7 +45,7 @@ deps =
basepython = python2.7 basepython = python2.7
deps = deps =
unittest2 unittest2
BeautifulSoup==3.2 BeautifulSoup==3.2.0
html5lib html5lib
coverage coverage
mock mock
@@ -50,7 +56,7 @@ deps =
basepython = python2.5 basepython = python2.5
deps = deps =
unittest2 unittest2
BeautifulSoup==3.2 BeautifulSoup==3.2.0
html5lib html5lib
coverage coverage
mock mock
@@ -60,7 +66,7 @@ deps =
basepython = python2.6 basepython = python2.6
deps = deps =
unittest2 unittest2
BeautifulSoup==3.2 BeautifulSoup==3.2.0
html5lib html5lib
coverage coverage
mock mock
@@ -70,7 +76,7 @@ deps =
basepython = python2.7 basepython = python2.7
deps = deps =
unittest2 unittest2
BeautifulSoup==3.2 BeautifulSoup==3.2.0
html5lib html5lib
coverage coverage
mock mock