diff --git a/compressor/management/commands/compress.py b/compressor/management/commands/compress.py index 9a46048..19e41cf 100644 --- a/compressor/management/commands/compress.py +++ b/compressor/management/commands/compress.py @@ -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)) diff --git a/compressor/tests/__init__.py b/compressor/tests/__init__.py index 5c68a71..69f3303 100644 --- a/compressor/tests/__init__.py +++ b/compressor/tests/__init__.py @@ -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 diff --git a/compressor/tests/offline.py b/compressor/tests/offline.py index 9f0fabc..c59de2d 100644 --- a/compressor/tests/offline.py +++ b/compressor/tests/offline.py @@ -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'', + u'' + ], 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" diff --git a/compressor/tests/test_templates/test_block_super_extra/base.html b/compressor/tests/test_templates/test_block_super_extra/base.html new file mode 100644 index 0000000..e9ca3ad --- /dev/null +++ b/compressor/tests/test_templates/test_block_super_extra/base.html @@ -0,0 +1,15 @@ +{% spaceless %} +{% block js %} + +{% endblock %} + +{% block css %} + +{% endblock %} +{% endspaceless %} diff --git a/compressor/tests/test_templates/test_block_super_extra/test_compressor_offline.html b/compressor/tests/test_templates/test_block_super_extra/test_compressor_offline.html new file mode 100644 index 0000000..2293065 --- /dev/null +++ b/compressor/tests/test_templates/test_block_super_extra/test_compressor_offline.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% load compress %} + +{% block js %}{% spaceless %} + {% compress js %} + + {% endcompress %} + + {% compress js %} + {{ block.super }} + + {% endcompress %} +{% endspaceless %}{% endblock %} + +{% block css %}{% endblock %} diff --git a/compressor/tests/test_templates/test_block_super_multiple/base.html b/compressor/tests/test_templates/test_block_super_multiple/base.html new file mode 100644 index 0000000..c9ee6cc --- /dev/null +++ b/compressor/tests/test_templates/test_block_super_multiple/base.html @@ -0,0 +1,15 @@ +{% spaceless %} +{% block js %} + +{% endblock %} + +{% block css %} + +{% endblock %} +{% endspaceless %} diff --git a/compressor/tests/test_templates/test_block_super_multiple/base2.html b/compressor/tests/test_templates/test_block_super_multiple/base2.html new file mode 100644 index 0000000..b0b2fef --- /dev/null +++ b/compressor/tests/test_templates/test_block_super_multiple/base2.html @@ -0,0 +1,3 @@ +{% extends "base.html" %} + +{% block css %}{% endblock %} diff --git a/compressor/tests/test_templates/test_block_super_multiple/test_compressor_offline.html b/compressor/tests/test_templates/test_block_super_multiple/test_compressor_offline.html new file mode 100644 index 0000000..a05a7b7 --- /dev/null +++ b/compressor/tests/test_templates/test_block_super_multiple/test_compressor_offline.html @@ -0,0 +1,11 @@ +{% extends "base2.html" %} +{% load compress %} + +{% block js %}{% spaceless %} + {% compress js %} + {{ block.super }} + + {% endcompress %} +{% endspaceless %}{% endblock %}