Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into develop

  • Loading branch information...
commit 8821d14a85d22498995b86f7095e74b7a70f8a83 2 parents f42b683 + 1a82db7
@jonasvp authored
View
6 AUTHORS
@@ -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,6 +22,7 @@ David Medina
David Ziegler
Eugene Mirotin
Fenn Bailey
+Francisco Souza
Gert Van Gool
Harro van der Klauw
Jaap Roes
@@ -27,9 +30,11 @@ 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
2  compressor/__init__.py
@@ -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
36 compressor/base.py
@@ -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,7 +156,6 @@ 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
@@ -159,15 +163,14 @@ def filtered_output(self, content):
"""
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
32 compressor/cache.py
@@ -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
2  compressor/conf.py
@@ -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
2  compressor/css.py
@@ -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)
View
11 compressor/filters/css_default.py
@@ -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)
View
2  compressor/filters/yui.py
@@ -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)
View
12 compressor/management/commands/compress.py
@@ -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
6 compressor/storage.py
@@ -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
View
35 compressor/templatetags/compress.py
@@ -1,9 +1,10 @@
from django import template
from django.core.exceptions import ImproperlyConfigured
-from compressor.cache import (cache, cache_get, cache_set,
- get_offline_cachekey, get_templatetag_cachekey)
+from compressor.cache import (cache_get, cache_set, get_offline_hexdigest,
+ get_offline_manifest, get_templatetag_cachekey)
from compressor.conf import settings
+from compressor.exceptions import OfflineGenerationError
from compressor.utils import get_class
register = template.Library()
@@ -12,6 +13,7 @@
OUTPUT_INLINE = 'inline'
OUTPUT_MODES = (OUTPUT_FILE, OUTPUT_INLINE)
+
class CompressorNode(template.Node):
def __init__(self, nodelist, kind=None, mode=OUTPUT_FILE, name=None):
@@ -21,7 +23,7 @@ def __init__(self, nodelist, kind=None, mode=OUTPUT_FILE, name=None):
self.name = name
def compressor_cls(self, *args, **kwargs):
- compressors = {
+ compressors = {
"css": settings.COMPRESS_CSS_COMPRESSOR,
"js": settings.COMPRESS_JS_COMPRESSOR,
}
@@ -39,14 +41,19 @@ def debug_mode(self, context):
if request is not None:
return settings.COMPRESS_DEBUG_TOGGLE in request.GET
- def render_offline(self, forced):
+ def render_offline(self, compressor, forced):
"""
If enabled and in offline mode, and not forced or in debug mode
check the offline cache and return the result if given
"""
if (settings.COMPRESS_ENABLED and
settings.COMPRESS_OFFLINE) and not forced:
- return cache.get(get_offline_cachekey(self.nodelist))
+ key = get_offline_hexdigest(self.nodelist)
+ offline_manifest = get_offline_manifest()
+ if key in offline_manifest:
+ return offline_manifest[key]
+ else:
+ raise OfflineGenerationError('You have offline compression enabled but key "%s" is missing from offline manifest. You may need to run "python manage.py compress".' % key)
def render_cached(self, compressor, forced):
"""
@@ -61,24 +68,26 @@ def render_cached(self, compressor, forced):
return None, None
def render(self, context, forced=False):
- # 1. Check if in debug mode
+ # Check if in debug mode
if self.debug_mode(context):
return self.nodelist.render(context)
- # 2. Try offline cache.
- cached_offline = self.render_offline(forced)
+ # Prepare the compressor
+ context.update({'name': self.name})
+ compressor = self.compressor_cls(content=self.nodelist.render(context),
+ context=context)
+
+ # See if it has been rendered offline
+ cached_offline = self.render_offline(compressor, forced)
if cached_offline:
return cached_offline
- # 3. Prepare the actual compressor and check cache
- context.update({'name': self.name})
- compressor = self.compressor_cls(content=self.nodelist.render(context),
- context=context)
+ # Check cache
cache_key, cache_content = self.render_cached(compressor, forced)
if cache_content is not None:
return cache_content
- # 4. call compressor output method and handle exceptions
+ # call compressor output method and handle exceptions
rendered_output = compressor.output(self.mode, forced=forced)
if cache_key:
cache_set(cache_key, rendered_output)
View
28 compressor/utils/decorators.py
@@ -1,31 +1,3 @@
-import functools
-
-class memoize(object):
-
- def __init__ (self, func):
- self.func = func
-
- def __call__ (self, *args, **kwargs):
- if (args, str(kwargs)) in self.__dict__:
- value = self.__dict__[args, str(kwargs)]
- else:
- value = self.func(*args, **kwargs)
- self.__dict__[args, str(kwargs)] = value
- return value
-
- def __repr__(self):
- """
- Return the function's docstring.
- """
- return self.func.__doc__ or ''
-
- def __get__(self, obj, objtype):
- """
- Support instance methods.
- """
- return functools.partial(self.__call__, obj)
-
-
class cached_property(object):
"""Property descriptor that caches the return value
of the get function.
View
56 docs/changelog.txt
@@ -1,6 +1,62 @@
Changelog
=========
+v1.1.2
+------
+
+- Fixed an installation issue related to versiontools.
+
+v1.1.1
+------
+
+- Fixed a stupid ImportError bug introduced in 1.1.
+
+- Fixed Jinja2 docs of since ``JINJA2_EXTENSIONS`` expects
+ a class, not a module.
+
+- Fixed a Windows bug with regard to file resolving with
+ staticfiles finders.
+
+- Stopped a potential memory leak when memoizing the rendered
+ output.
+
+- Fixed the integration between staticfiles (e.g. in Django <= 1.3.1)
+ and compressor which prevents the collectstatic management command
+ to work.
+
+ .. warning::
+
+ Make sure to **remove** the ``path`` method of your custom
+ :ref:`remote storage <remote_storages>` class!
+
+v1.1
+----
+
+- Made offline compression completely independent from cache (by writing a
+ manifest.json file).
+
+ You can now easily run the :ref:`compress <pre-compression>` management
+ command locally and transfer the :ref:`COMPRESS_ROOT <compress_root>` dir
+ to your server.
+
+- Updated installation instructions to properly mention all dependencies,
+ even those internally used.
+
+- Fixed a bug introduced in 1.0 which would prevent the proper deactivation
+ of the compression in production.
+
+- Added a Jinja2_ :doc:`contrib extension </jinja2>`.
+
+- Made sure the rel attribute of link tags can be mixed case.
+
+- Avoid overwriting context variables needed for compressor to work.
+
+- Stopped the compress management command to require model validation.
+
+- Added missing imports and fixed a few :pep:`8` issues.
+
+.. _Jinja2: http://jinja.pocoo.org/2/
+
v1.0.1
------
View
6 docs/conf.py
@@ -41,16 +41,16 @@
# General information about the project.
project = u'Django Compressor'
-copyright = u'2011, Django Compressor authors'
+copyright = u'2012, Django Compressor authors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '1.0'
+version = '1.1'
# The full version, including alpha/beta/rc tags.
-release = '1.0.1'
+release = '1.1.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
View
2  docs/jinja2.txt
@@ -32,7 +32,7 @@ only requires to add extension to ``JINJA2_EXTENSIONS`` at main settings
module::
JINJA2_EXTENSIONS = [
- 'compressor.contrib.jinja2ext',
+ 'compressor.contrib.jinja2ext.CompressorExtension',
]
And that's it - our extension is loaded and ready to be used.
View
3  docs/remote-storages.txt
@@ -68,9 +68,6 @@ integrated.
self.local_storage._save(name, content)
return name
- def path(self, name):
- return self.local_storage.path(name)
-
#. Set your :ref:`COMPRESS_STORAGE <compress_storage>` and STATICFILES_STORAGE_
settings to the dotted path of your custom cached storage backend, e.g.
``'mysite.storage.CachedS3BotoStorage'``.
View
10 docs/settings.txt
@@ -385,3 +385,13 @@ the contents of ``{% compress %}`` template tags and saving the result in the
offline cache.
If available, the ``STATIC_URL`` setting is also added to the context.
+
+.. _compress_offline_manifest:
+
+COMPRESS_OFFLINE_MANIFEST
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:Default: ``manifest.json``
+
+The name of the file to be used for saving the names of the files compressed
+offline.
View
11 docs/usage.txt
@@ -101,7 +101,6 @@ access it in the `post_compress signal <signals>`.
.. _caching documentation: http://docs.djangoproject.com/en/1.2/topics/cache/#memcached
.. _pre-compression:
-.. _signals:
Pre-compression
---------------
@@ -146,13 +145,15 @@ If not specified, the ``COMPRESS_OFFLINE_CONTEXT`` will by default contain
the commonly used setting to refer to saved files ``MEDIA_URL`` and
``STATIC_URL`` (if specified in the settings).
-The result of running the ``compress`` management command will be saved
-in the cache defined in :ref:`COMPRESS_CACHE_BACKEND <compress_cache_backend>`
-for the number of seconds defined in the
-:ref:`COMPRESS_OFFLINE_TIMEOUT <compress_offline_timeout>` setting.
+The result of running the ``compress`` management command will be cached
+in a file called ``manifest.json`` using the :ref:`configured storage
+<compress_storage>` to be able to be transfered from your developement
+computer to the server easily.
.. _TEMPLATE_LOADERS: http://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
+.. _signals:
+
Signals
-------
View
2  setup.py
@@ -126,6 +126,6 @@ def find_package_data(
'django-appconf >= 0.4',
],
setup_requires = [
- 'versiontools >= 1.6',
+ 'versiontools >= 1.8.2',
],
)
View
53 tests/runtests.py
@@ -1,53 +0,0 @@
-#!/usr/bin/env python
-import os
-import sys
-import coverage
-from os.path import join
-
-from django.conf import settings
-
-TEST_DIR = os.path.dirname(os.path.abspath(__file__))
-
-if not settings.configured:
- settings.configure(
- COMPRESS_CACHE_BACKEND = 'locmem://',
- DATABASE_ENGINE='sqlite3',
- INSTALLED_APPS=[
- 'compressor',
- 'tests',
- ],
- MEDIA_URL = '/media/',
- MEDIA_ROOT = os.path.join(TEST_DIR, 'media'),
- TEMPLATE_DIRS = (
- os.path.join(TEST_DIR, 'templates'),
- ),
- TEST_DIR = TEST_DIR,
- )
-
-from django.test.simple import run_tests
-
-
-def runtests(*test_args):
- if not test_args:
- test_args = ['tests']
- parent_dir = os.path.join(TEST_DIR, "..")
- sys.path.insert(0, parent_dir)
- cov = coverage.coverage(branch=True,
- include=[
- os.path.join(parent_dir, 'compressor', '*.py')
- ],
- omit=[
- join(parent_dir, 'compressor', 'utils', 'stringformat.py'),
- join(parent_dir, 'compressor', 'filters', 'jsmin', 'rjsmin.py'),
- join(parent_dir, 'compressor', 'filters', 'cssmin', 'cssmin.py'),
- ])
- cov.load()
- cov.start()
- failures = run_tests(test_args, verbosity=1, interactive=True)
- cov.stop()
- cov.save()
- sys.exit(failures)
-
-
-if __name__ == '__main__':
- runtests(*sys.argv[1:])
View
10 tests/tests/base.py
@@ -96,6 +96,16 @@ def test_js_output(self):
out = u'<script type="text/javascript" src="/media/CACHE/js/066cd253eada.js"></script>'
self.assertEqual(out, self.js_node.output())
+ def test_js_override_url(self):
+ self.js_node.context.update({'url': u'This is not a url, just a text'})
+ out = u'<script type="text/javascript" src="/media/CACHE/js/066cd253eada.js"></script>'
+ self.assertEqual(out, self.js_node.output())
+
+ def test_css_override_url(self):
+ self.css_node.context.update({'url': u'This is not a url, just a text'})
+ output = css_tag('/media/CACHE/css/e41ba2cc6982.css')
+ self.assertEqual(output, self.css_node.output().strip())
+
def test_js_return_if_off(self):
try:
enabled = settings.COMPRESS_ENABLED
View
11 tests/tests/offline.py
@@ -5,7 +5,9 @@
from django.test import TestCase
from compressor.conf import settings
+from compressor.exceptions import OfflineGenerationError
from compressor.management.commands.compress import Command as CompressCommand
+from compressor.storage import default_storage
from .base import test_dir, css_tag
@@ -25,6 +27,15 @@ def tearDown(self):
settings.COMPRESS_ENABLED = self._old_compress
settings.COMPRESS_OFFLINE = self._old_compress_offline
self.template_file.close()
+ if default_storage.exists('CACHE/manifest.json'):
+ default_storage.delete('CACHE/manifest.json')
+
+ def test_rendering_without_compressing_raises_exception(self):
+ self.assertRaises(OfflineGenerationError,
+ self.template.render, Context({}))
+
+ def test_requires_model_validation(self):
+ self.assertFalse(CompressCommand.requires_model_validation)
def test_offline(self):
count, result = CompressCommand().compress()
View
9 tests/tests/templatetags.py
@@ -45,6 +45,15 @@ def test_css_tag(self):
out = css_tag("/media/CACHE/css/e41ba2cc6982.css")
self.assertEqual(out, render(template, self.context))
+ def test_uppercase_rel(self):
+ template = u"""{% load compress %}{% compress css %}
+ <link rel="StyleSheet" href="{{ MEDIA_URL }}css/one.css" type="text/css">
+ <style type="text/css">p { border:5px solid green;}</style>
+ <link rel="StyleSheet" href="{{ MEDIA_URL }}css/two.css" type="text/css">
+ {% endcompress %}"""
+ out = css_tag("/media/CACHE/css/e41ba2cc6982.css")
+ self.assertEqual(out, render(template, self.context))
+
def test_nonascii_css_tag(self):
template = u"""{% load compress %}{% compress css %}
<link rel="stylesheet" href="{{ MEDIA_URL }}css/nonasc.css" type="text/css">
Please sign in to comment.
Something went wrong with that request. Please try again.