Skip to content

Commit

Permalink
Merge pull request django-compressor#109 from threepress/develop
Browse files Browse the repository at this point in the history
Added `post_compress` signal.
  • Loading branch information
jezdez committed Sep 1, 2011
2 parents 37b5415 + 8afbd9d commit d48bc5f
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 6 deletions.
2 changes: 2 additions & 0 deletions compressor/base.py
Expand Up @@ -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

Expand Down Expand Up @@ -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)
4 changes: 4 additions & 0 deletions compressor/signals.py
@@ -0,0 +1,4 @@
import django.dispatch


post_compress = django.dispatch.Signal(providing_args=['type', 'mode', 'context'])
16 changes: 11 additions & 5 deletions compressor/templatetags/compress.py
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -128,18 +130,22 @@ 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(
"%r's second argument must be '%s' or '%s'." %
(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)
47 changes: 46 additions & 1 deletion docs/usage.txt
Expand Up @@ -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 %}

Expand Down Expand Up @@ -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
---------------
Expand Down Expand Up @@ -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
---------

Expand Down
1 change: 1 addition & 0 deletions tests/tests/__init__.py
Expand Up @@ -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
67 changes: 67 additions & 0 deletions 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)
17 changes: 17 additions & 0 deletions tests/tests/templatetags.py
Expand Up @@ -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


Expand Down Expand Up @@ -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'])
5 changes: 5 additions & 0 deletions tests/tox.ini
Expand Up @@ -38,6 +38,7 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
mock
django==1.2.5

[testenv:py27-1.2.X]
Expand All @@ -47,6 +48,7 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
mock
django==1.2.5


Expand All @@ -57,6 +59,7 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
mock
django==1.3

[testenv:py26-1.3.X]
Expand All @@ -66,6 +69,7 @@ deps =
BeautifulSoup==3.2.0
html5lib
coverage
mock
django==1.3

[testenv:py27-1.3.X]
Expand All @@ -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.