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:
		| @@ -28,12 +28,62 @@ from compressor.exceptions import OfflineGenerationError | |||||||
| 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 | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -189,32 +239,19 @@ class Command(NoArgsCommand): | |||||||
|         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)) | ||||||
|   | |||||||
| @@ -5,9 +5,14 @@ from compressor.tests.filters import (CssTidyTestCase, PrecompilerTestCase, | |||||||
|     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 | ||||||
|   | |||||||
| @@ -68,6 +68,25 @@ class OfflineGenerationBlockSuperTestCase(OfflineTestCaseMixin, TestCase): | |||||||
|     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" | ||||||
|     expected_hash = "4e3758d50224" |     expected_hash = "4e3758d50224" | ||||||
|   | |||||||
| @@ -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 %} | ||||||
		Reference in New Issue
	
	Block a user
	 Mathieu Pillard
					Mathieu Pillard