Skip to content
Browse files

Merge pull request #109 from threepress/develop

Added `post_compress` signal.
  • Loading branch information...
2 parents 37b5415 + 8afbd9d commit d48bc5f45d5a55b0f826eb605ccf09a6bf33fcb9 @jezdez jezdez committed
View
2 compressor/base.py
@@ -13,6 +13,7 @@
from compressor.exceptions import CompressorError, UncompressableFileError
from compressor.filters import CompilerFilter
from compressor.storage import default_storage
+from compressor.signals import post_compress
from compressor.utils import get_class, staticfiles
from compressor.utils.decorators import cached_property, memoize
@@ -260,5 +261,6 @@ def render_output(self, mode, context=None):
final_context.update(context)
final_context.update(self.context)
final_context.update(self.extra_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
4 compressor/signals.py
@@ -0,0 +1,4 @@
+import django.dispatch
+
+
+post_compress = django.dispatch.Signal(providing_args=['type', 'mode', 'context'])
View
16 compressor/templatetags/compress.py
@@ -15,10 +15,11 @@
class CompressorNode(template.Node):
- def __init__(self, nodelist, kind=None, mode=OUTPUT_FILE):
+ def __init__(self, nodelist, kind=None, mode=OUTPUT_FILE, name=None):
self.nodelist = nodelist
self.kind = kind
self.mode = mode
+ self.name = name
def compressor_cls(self, *args, **kwargs):
compressors = {
@@ -71,6 +72,7 @@ def render(self, context, forced=False):
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)
cache_key, cache_content = self.render_cached(compressor, forced)
@@ -128,13 +130,13 @@ def compress(parser, token):
args = token.split_contents()
- if not len(args) in (2, 3):
+ if not len(args) in (2, 3, 4):
raise template.TemplateSyntaxError(
- "%r tag requires either one or two arguments." % args[0])
+ "%r tag requires either one, two or three arguments." % args[0])
kind = args[1]
- if len(args) == 3:
+ if len(args) >= 3:
mode = args[2]
if not mode in OUTPUT_MODES:
raise template.TemplateSyntaxError(
@@ -142,4 +144,8 @@ def compress(parser, token):
(args[0], OUTPUT_FILE, OUTPUT_INLINE))
else:
mode = OUTPUT_FILE
- return CompressorNode(nodelist, kind, mode)
+ if len(args) == 4:
+ name = args[3]
+ else:
+ name = None
+ return CompressorNode(nodelist, kind, mode, name)
View
47 docs/usage.txt
@@ -6,7 +6,7 @@ Usage
.. code-block:: django
{% load compress %}
- {% compress <js/css> [<file/infile>] %}
+ {% compress <js/css> [<file/infile> [block_name]] %}
<html of inline or linked JS/CSS>
{% endcompress %}
@@ -85,10 +85,15 @@ would be rendered something like::
obj.value = "value";
</script>
+The compress template tag also supports a third argument for naming the output
+of that particular compress tag. This is then added to the context so you can
+access it in the `post_compress signal <signals>`.
+
.. _memcached: http://memcached.org/
.. _caching documentation: http://docs.djangoproject.com/en/1.2/topics/cache/#memcached
.. _pre-compression:
+.. _signals:
Pre-compression
---------------
@@ -140,6 +145,46 @@ for the number of seconds defined in the
.. _TEMPLATE_LOADERS: http://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
+Signals
+-------
+
+.. attribute:: compressor.signals.post_compress
+ :module:
+
+Django Compressor includes a ``post_compress`` signal that enables you to
+listen for changes to your compressed CSS/JS. This is useful, for example, if
+you need the exact filenames for use in an HTML5 manifest file. The signal
+sends the following arguments:
+
+``sender``
+ Always "django-compressor".
+
+``type``
+ Either "``js``" or "``css``".
+
+``mode``
+ Either "``file``" or "``inline``".
+
+``context``
+ The context dictionary used to render the output of the compress template
+ tag.
+
+ If ``mode`` is "``file``", this will contain a "``url``" key that maps to
+ the relative URL for the compressed asset.
+
+ If ``type`` is "``css``", the context will additionally contain a
+ "``media``" key with a value of ``None`` if no media attribute is specified on
+ the link/style tag and equal to that attribute if one is specified.
+
+ Additionally, ``context['name']`` will be the third positional argument to
+ the template tag, if provided.
+
+.. note::
+
+ When compressing CSS, the ``post_compress`` signal will be called once for
+ every different media attribute on the tags within the ``{% compress %}`` tag
+ in question.
+
CSS Notes
---------
View
1 tests/tests/__init__.py
@@ -2,5 +2,6 @@
from .filters import CssTidyTestCase, PrecompilerTestCase, CssMinTestCase, CssAbsolutizingTestCase, CssDataUriTestCase
from .offline import OfflineGenerationTestCase
from .parsers import LxmlParserTests, Html5LibParserTests, BeautifulSoupParserTests, HtmlParserTests
+from .signals import PostCompressSignalTestCase
from .storages import StorageTestCase
from .templatetags import TemplatetagTestCase
View
67 tests/tests/signals.py
@@ -0,0 +1,67 @@
+from django.test import TestCase
+
+from mock import Mock
+
+from compressor.conf import settings
+from compressor.css import CssCompressor
+from compressor.js import JsCompressor
+from compressor.signals import post_compress
+
+
+class PostCompressSignalTestCase(TestCase):
+ def setUp(self):
+ settings.COMPRESS_ENABLED = True
+ settings.COMPRESS_PRECOMPILERS = {}
+ settings.COMPRESS_DEBUG_TOGGLE = 'nocompress'
+ self.css = """\
+<link rel="stylesheet" href="/media/css/one.css" type="text/css" />
+<style type="text/css">p { border:5px solid green;}</style>
+<link rel="stylesheet" href="/media/css/two.css" type="text/css" />"""
+ self.css_node = CssCompressor(self.css)
+
+ self.js = """\
+<script src="/media/js/one.js" type="text/javascript"></script>
+<script type="text/javascript">obj.value = "value";</script>"""
+ self.js_node = JsCompressor(self.js)
+
+ def tearDown(self):
+ post_compress.disconnect()
+
+ def test_js_signal_sent(self):
+ def listener(sender, **kwargs):
+ pass
+ callback = Mock(wraps=listener)
+ post_compress.connect(callback)
+ self.js_node.output()
+ args, kwargs = callback.call_args
+ self.assertEquals('django-compressor', kwargs['sender'])
+ self.assertEquals('js', kwargs['type'])
+ self.assertEquals('file', kwargs['mode'])
+ context = kwargs['context']
+ assert 'url' in context
+
+ def test_css_signal_sent(self):
+ def listener(sender, **kwargs):
+ pass
+ callback = Mock(wraps=listener)
+ post_compress.connect(callback)
+ self.css_node.output()
+ args, kwargs = callback.call_args
+ self.assertEquals('django-compressor', kwargs['sender'])
+ self.assertEquals('css', kwargs['type'])
+ self.assertEquals('file', kwargs['mode'])
+ context = kwargs['context']
+ assert 'url' in context
+
+ def test_css_signal_multiple_media_attributes(self):
+ css = """\
+<link rel="stylesheet" href="/media/css/one.css" media="handheld" type="text/css" />
+<style type="text/css" media="print">p { border:5px solid green;}</style>
+<link rel="stylesheet" href="/media/css/two.css" type="text/css" />"""
+ css_node = CssCompressor(css)
+ def listener(sender, **kwargs):
+ pass
+ callback = Mock(wraps=listener)
+ post_compress.connect(callback)
+ css_node.output()
+ self.assertEquals(3, callback.call_count)
View
17 tests/tests/templatetags.py
@@ -4,6 +4,10 @@
from django.template import Template, Context, TemplateSyntaxError
from django.test import TestCase
+from mock import Mock
+
+from compressor.signals import post_compress
+
from .base import css_tag
@@ -96,3 +100,16 @@ class MockDebugRequest(object):
<script type="text/javascript">obj.value = "value";</script>"""
self.assertEqual(out, render(template, context))
+ def test_named_compress_tag(self):
+ template = u"""{% load compress %}{% compress js inline foo %}
+ <script type="text/javascript">obj.value = "value";</script>
+ {% endcompress %}
+ """
+ def listener(sender, **kwargs):
+ pass
+ callback = Mock(wraps=listener)
+ post_compress.connect(callback)
+ render(template)
+ args, kwargs = callback.call_args
+ context = kwargs['context']
+ self.assertEqual('foo', context['name'])
View
5 tests/tox.ini
@@ -38,6 +38,7 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
+ mock
django==1.2.5
[testenv:py27-1.2.X]
@@ -47,6 +48,7 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
+ mock
django==1.2.5
@@ -57,6 +59,7 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
+ mock
django==1.3
[testenv:py26-1.3.X]
@@ -66,6 +69,7 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
+ mock
django==1.3
[testenv:py27-1.3.X]
@@ -75,4 +79,5 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
+ mock
django==1.3

0 comments on commit d48bc5f

Please sign in to comment.
Something went wrong with that request. Please try again.