Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request django-compressor#198 from diox/develop
Make {{ block.super }} in compress management command a little more robust
  • Loading branch information
Jannis Leidel committed Feb 1, 2012
2 parents 40a9fc3 + 480e7d0 commit d63cf0e
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 29 deletions.
89 changes: 63 additions & 26 deletions compressor/management/commands/compress.py
Expand Up @@ -28,12 +28,62 @@
from compressor.templatetags.compress import CompressorNode from compressor.templatetags.compress import CompressorNode
from compressor.utils import walk, any from compressor.utils import walk, any


def patched__render(self, context):
# 'Fake' _render method that just returns the context instead of rendering.
# It also checks whether the first node is an extend node or not, to be able
# to handle complex inheritance chain.
self._render_firstnode = MethodType(patched__render_firstnode, self)
self._render_firstnode(context)
return context

def patched__render_firstnode(self, context):
# If this template has a ExtendsNode, we want to find out what
# should be put in render_context to make the {% block ... %}
# tags work.
#
# We can't fully render the base template(s) (we don't have the
# full context vars - only what's necessary to render the compress
# nodes!), therefore we hack the ExtendsNode we found, patching
# its get_parent method so that rendering the ExtendsNode only
# gives us the blocks content without doing any actual rendering.
extra_context = {}
firstnode = self.nodelist[0]
if isinstance(firstnode, ExtendsNode):
firstnode._log = self._log
firstnode._log_verbosity = self._log_verbosity
firstnode._old_get_parent = firstnode.get_parent
firstnode.get_parent = MethodType(patched_get_parent, firstnode)
try:
extra_context = firstnode.render(context)
context.render_context = extra_context.render_context
# We aren't rendering {% block %} tags, but we want {{ block.super }}
# inside {% compress %} inside {% block %}s to work. Therefore, we
# need to pop() the last block context for each block name, to
# emulate what would have been done if the {% block %} had been fully
# rendered.
for blockname in firstnode.blocks.keys():
context.render_context[BLOCK_CONTEXT_KEY].pop(blockname)
except (IOError, TemplateSyntaxError, TemplateDoesNotExist):
# That first node we are trying to render might cause more errors
# that we didn't catch when simply creating a Template instance
# above, so we need to catch that (and ignore it, just like above)
# as well.
if self._log_verbosity > 0:
self._log.write("Caught error when rendering extend node from \
template %s\n" % template.template_name)
return None
return extra_context


def patched_get_parent(self, context): def patched_get_parent(self, context):
# Patch template returned by get_parent to make sure their _render method is # Patch template returned by extendsnode's get_parent to make sure their
# just returning the context instead of actually rendering stuff. # _render method is just returning the context instead of actually
# rendering stuff.
# In addition, this follows the inheritance chain by looking if the first
# node of the template is an extend node itself.
compiled_template = self._old_get_parent(context) compiled_template = self._old_get_parent(context)
compiled_template._render = MethodType(lambda self, c: c, compiled_template) compiled_template._log = self._log
compiled_template._log_verbosity = self._log_verbosity
compiled_template._render = MethodType(patched__render, compiled_template)
return compiled_template return compiled_template




Expand Down Expand Up @@ -189,32 +239,19 @@ def compress(self, log=None, **options):
offline_manifest = {} offline_manifest = {}
for template, nodes in compressor_nodes.iteritems(): for template, nodes in compressor_nodes.iteritems():
context = Context(settings.COMPRESS_OFFLINE_CONTEXT) context = Context(settings.COMPRESS_OFFLINE_CONTEXT)
extra_context = {} template._log = log
firstnode = template.nodelist[0] template._log_verbosity = verbosity
if isinstance(firstnode, ExtendsNode): template._render_firstnode = MethodType(patched__render_firstnode, template)
# If this template has a ExtendsNode, we apply our patch to extra_context = template._render_firstnode(context)
# generate the necessary context, and then use it for all the if extra_context is None:
# nodes in it, just in case (we don't know which nodes were # Something is wrong - ignore this template
# in a block) continue
firstnode._old_get_parent = firstnode.get_parent
firstnode.get_parent = MethodType(patched_get_parent, firstnode)
try:
extra_context = firstnode.render(context)
context.render_context = extra_context.render_context
except (IOError, TemplateSyntaxError, TemplateDoesNotExist):
# That first node we are trying to render might cause more errors
# that we didn't catch when simply creating a Template instance
# above, so we need to catch that (and ignore it, just like above)
# as well.
if verbosity > 0:
log.write("Caught error when rendering extend node "
"from template %s\n" %
template.template_name)
continue
for node in nodes: for node in nodes:
context.push() context.push()
if extra_context and node._block_name: if extra_context and node._block_name:
context['block'] = context.render_context[BLOCK_CONTEXT_KEY].pop(node._block_name) # Give a block context to the node if it was found inside
# a {% block %}.
context['block'] = context.render_context[BLOCK_CONTEXT_KEY].get_block(node._block_name)
if context['block']: if context['block']:
context['block'].context = context context['block'].context = context
key = get_offline_hexdigest(node.nodelist.render(context)) key = get_offline_hexdigest(node.nodelist.render(context))
Expand Down
11 changes: 8 additions & 3 deletions compressor/tests/__init__.py
Expand Up @@ -5,9 +5,14 @@
CssDataUriTestCase, TemplateTestCase) CssDataUriTestCase, TemplateTestCase)
from compressor.tests.jinja2ext import TestJinja2CompressorExtension from compressor.tests.jinja2ext import TestJinja2CompressorExtension
from compressor.tests.offline import ( from compressor.tests.offline import (
OfflineGenerationBlockSuperTestCase, OfflineGenerationConditionTestCase, OfflineGenerationBlockSuperTestCase,
OfflineGenerationTemplateTagTestCase, OfflineGenerationTestCaseWithContext, OfflineGenerationBlockSuperTestCaseWithExtraContent,
OfflineGenerationTestCaseErrors, OfflineGenerationTestCase) OfflineGenerationBlockSuperMultipleTestCase,
OfflineGenerationConditionTestCase,
OfflineGenerationTemplateTagTestCase,
OfflineGenerationTestCaseWithContext,
OfflineGenerationTestCaseErrors,
OfflineGenerationTestCase)
from compressor.tests.parsers import (LxmlParserTests, Html5LibParserTests, from compressor.tests.parsers import (LxmlParserTests, Html5LibParserTests,
BeautifulSoupParserTests, HtmlParserTests) BeautifulSoupParserTests, HtmlParserTests)
from compressor.tests.signals import PostCompressSignalTestCase from compressor.tests.signals import PostCompressSignalTestCase
Expand Down
19 changes: 19 additions & 0 deletions compressor/tests/offline.py
Expand Up @@ -67,6 +67,25 @@ class OfflineGenerationBlockSuperTestCase(OfflineTestCaseMixin, TestCase):
templates_dir = "test_block_super" templates_dir = "test_block_super"
expected_hash = "7c02d201f69d" expected_hash = "7c02d201f69d"



class OfflineGenerationBlockSuperMultipleTestCase(OfflineTestCaseMixin, TestCase):
templates_dir = "test_block_super_multiple"
expected_hash = "2f6ef61c488e"


class OfflineGenerationBlockSuperTestCaseWithExtraContent(OfflineTestCaseMixin, TestCase):
templates_dir = "test_block_super_extra"

def test_offline(self):
count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity)
self.assertEqual(2, count)
self.assertEqual([
u'<script type="text/javascript" src="/media/CACHE/js/ced14aec5856.js"></script>',
u'<script type="text/javascript" src="/media/CACHE/js/7c02d201f69d.js"></script>'
], result)
rendered_template = self.template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT))
self.assertEqual(rendered_template, "".join(result) + "\n")



class OfflineGenerationConditionTestCase(OfflineTestCaseMixin, TestCase): class OfflineGenerationConditionTestCase(OfflineTestCaseMixin, TestCase):
templates_dir = "test_condition" templates_dir = "test_condition"
Expand Down
15 changes: 15 additions & 0 deletions compressor/tests/test_templates/test_block_super_extra/base.html
@@ -0,0 +1,15 @@
{% spaceless %}
{% block js %}
<script type="text/javascript">
alert("test using block.super");
</script>
{% endblock %}

{% block css %}
<style type="text/css">
body {
background: red;
}
</style>
{% endblock %}
{% endspaceless %}
@@ -0,0 +1,19 @@
{% extends "base.html" %}
{% load compress %}

{% block js %}{% spaceless %}
{% compress js %}
<script type="text/javascript">
alert("this alert should be alone.");
</script>
{% endcompress %}

{% compress js %}
{{ block.super }}
<script type="text/javascript">
alert("this alert shouldn't be alone!");
</script>
{% endcompress %}
{% endspaceless %}{% endblock %}

{% block css %}{% endblock %}
@@ -0,0 +1,15 @@
{% spaceless %}
{% block js %}
<script type="text/javascript">
alert("test using multiple inheritance and block.super");
</script>
{% endblock %}

{% block css %}
<style type="text/css">
body {
background: red;
}
</style>
{% endblock %}
{% endspaceless %}
@@ -0,0 +1,3 @@
{% extends "base.html" %}

{% block css %}{% endblock %}
@@ -0,0 +1,11 @@
{% extends "base2.html" %}
{% load compress %}

{% block js %}{% spaceless %}
{% compress js %}
{{ block.super }}
<script type="text/javascript">
alert("this alert shouldn't be alone!");
</script>
{% endcompress %}
{% endspaceless %}{% endblock %}

0 comments on commit d63cf0e

Please sign in to comment.