Make {{ block.super }} in compress management command a little more robust

- Support multiple {% compress %} calls, with one using {{ block.super }}, inside the same {% block %}
- Support more complex inheritance chain
This commit is contained in:
Mathieu Pillard
2012-02-01 13:06:09 +01:00
parent 40a9fc34c2
commit 480e7d0562
8 changed files with 153 additions and 29 deletions

View File

@@ -28,12 +28,62 @@ from compressor.exceptions import OfflineGenerationError
from compressor.templatetags.compress import CompressorNode
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):
# Patch template returned by get_parent to make sure their _render method is
# just returning the context instead of actually rendering stuff.
# Patch template returned by extendsnode's get_parent to make sure their
# _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._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
@@ -189,32 +239,19 @@ class Command(NoArgsCommand):
offline_manifest = {}
for template, nodes in compressor_nodes.iteritems():
context = Context(settings.COMPRESS_OFFLINE_CONTEXT)
extra_context = {}
firstnode = template.nodelist[0]
if isinstance(firstnode, ExtendsNode):
# If this template has a ExtendsNode, we apply our patch to
# generate the necessary context, and then use it for all the
# nodes in it, just in case (we don't know which nodes were
# in a block)
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
template._log = log
template._log_verbosity = verbosity
template._render_firstnode = MethodType(patched__render_firstnode, template)
extra_context = template._render_firstnode(context)
if extra_context is None:
# Something is wrong - ignore this template
continue
for node in nodes:
context.push()
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']:
context['block'].context = context
key = get_offline_hexdigest(node.nodelist.render(context))

View File

@@ -5,9 +5,14 @@ from compressor.tests.filters import (CssTidyTestCase, PrecompilerTestCase,
CssDataUriTestCase, TemplateTestCase)
from compressor.tests.jinja2ext import TestJinja2CompressorExtension
from compressor.tests.offline import (
OfflineGenerationBlockSuperTestCase, OfflineGenerationConditionTestCase,
OfflineGenerationTemplateTagTestCase, OfflineGenerationTestCaseWithContext,
OfflineGenerationTestCaseErrors, OfflineGenerationTestCase)
OfflineGenerationBlockSuperTestCase,
OfflineGenerationBlockSuperTestCaseWithExtraContent,
OfflineGenerationBlockSuperMultipleTestCase,
OfflineGenerationConditionTestCase,
OfflineGenerationTemplateTagTestCase,
OfflineGenerationTestCaseWithContext,
OfflineGenerationTestCaseErrors,
OfflineGenerationTestCase)
from compressor.tests.parsers import (LxmlParserTests, Html5LibParserTests,
BeautifulSoupParserTests, HtmlParserTests)
from compressor.tests.signals import PostCompressSignalTestCase

View File

@@ -67,6 +67,25 @@ class OfflineGenerationBlockSuperTestCase(OfflineTestCaseMixin, TestCase):
templates_dir = "test_block_super"
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):
templates_dir = "test_condition"

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -0,0 +1,3 @@
{% extends "base.html" %}
{% block css %}{% endblock %}

View File

@@ -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 %}