mirror of
				https://github.com/django/django.git
				synced 2025-10-26 07:06:08 +00:00 
			
		
		
		
	This change:
* Makes the InclusionNode cache-safe by removing render-time side effects
  to its nodelist.
* Ensures the render_context stack is properly scoped and reset by updating
  the render call to use Template.render rather than Nodelist.render.
Backport of 0808ccce38 from master
			
			
This commit is contained in:
		| @@ -1262,10 +1262,16 @@ class Library(object): | ||||
|             class InclusionNode(TagHelperNode): | ||||
|  | ||||
|                 def render(self, context): | ||||
|                     """ | ||||
|                     Renders the specified template and context. Caches the | ||||
|                     template object in render_context to avoid reparsing and | ||||
|                     loading when used in a for loop. | ||||
|                     """ | ||||
|                     resolved_args, resolved_kwargs = self.get_resolved_arguments(context) | ||||
|                     _dict = func(*resolved_args, **resolved_kwargs) | ||||
|  | ||||
|                     if not getattr(self, 'nodelist', False): | ||||
|                     t = context.render_context.get(self) | ||||
|                     if t is None: | ||||
|                         if isinstance(file_name, Template): | ||||
|                             t = file_name | ||||
|                         elif isinstance(getattr(file_name, 'template', None), Template): | ||||
| @@ -1274,7 +1280,7 @@ class Library(object): | ||||
|                             t = context.template.engine.select_template(file_name) | ||||
|                         else: | ||||
|                             t = context.template.engine.get_template(file_name) | ||||
|                         self.nodelist = t.nodelist | ||||
|                         context.render_context[self] = t | ||||
|                     new_context = context.new(_dict) | ||||
|                     # Copy across the CSRF token, if present, because | ||||
|                     # inclusion tags are often used for forms, and we need | ||||
| @@ -1283,7 +1289,7 @@ class Library(object): | ||||
|                     csrf_token = context.get('csrf_token', None) | ||||
|                     if csrf_token is not None: | ||||
|                         new_context['csrf_token'] = csrf_token | ||||
|                     return self.nodelist.render(new_context) | ||||
|                     return t.render(new_context) | ||||
|  | ||||
|             function_name = (name or | ||||
|                 getattr(func, '_decorated_function', func).__name__) | ||||
|   | ||||
							
								
								
									
										1
									
								
								tests/template_tests/templates/inclusion_base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/template_tests/templates/inclusion_base.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {% block content %}base{% endblock %} | ||||
							
								
								
									
										2
									
								
								tests/template_tests/templates/inclusion_extends1.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/template_tests/templates/inclusion_extends1.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| {% extends 'inclusion_base.html' %} | ||||
| {% block content %}one{% endblock %} | ||||
							
								
								
									
										2
									
								
								tests/template_tests/templates/inclusion_extends2.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/template_tests/templates/inclusion_extends2.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| {% extends 'inclusion_base.html' %} | ||||
| {% block content %}two{% endblock %} | ||||
| @@ -164,3 +164,13 @@ def inclusion_tag_without_context_parameter(arg): | ||||
|     """Expected inclusion_tag_without_context_parameter __doc__""" | ||||
|     return {} | ||||
| inclusion_tag_without_context_parameter.anything = "Expected inclusion_tag_without_context_parameter __dict__" | ||||
|  | ||||
|  | ||||
| @register.inclusion_tag('inclusion_extends1.html') | ||||
| def inclusion_extends1(): | ||||
|     return {} | ||||
|  | ||||
|  | ||||
| @register.inclusion_tag('inclusion_extends2.html') | ||||
| def inclusion_extends2(): | ||||
|     return {} | ||||
|   | ||||
| @@ -2,7 +2,8 @@ from __future__ import unicode_literals | ||||
|  | ||||
| import os | ||||
|  | ||||
| from django.template import Context, Template, TemplateSyntaxError | ||||
| from django.template import Context, Engine, Template, TemplateSyntaxError | ||||
| from django.template.base import Node | ||||
| from django.test import SimpleTestCase, ignore_warnings | ||||
| from django.test.utils import extend_sys_path | ||||
| from django.utils import six | ||||
| @@ -274,6 +275,26 @@ class CustomTagTests(SimpleTestCase): | ||||
|         c.use_l10n = True | ||||
|         self.assertEqual(t.render(c).strip(), 'True') | ||||
|  | ||||
|     def test_no_render_side_effect(self): | ||||
|         """ | ||||
|         #23441 -- InclusionNode shouldn't modify its nodelist at render time. | ||||
|         """ | ||||
|         engine = Engine(app_dirs=True) | ||||
|         template = engine.from_string('{% load inclusion %}{% inclusion_no_params %}') | ||||
|         count = template.nodelist.get_nodes_by_type(Node) | ||||
|         template.render(Context({})) | ||||
|         self.assertEqual(template.nodelist.get_nodes_by_type(Node), count) | ||||
|  | ||||
|     def test_render_context_is_cleared(self): | ||||
|         """ | ||||
|         #24555 -- InclusionNode should push and pop the render_context stack | ||||
|         when rendering. Otherwise, leftover values such as blocks from | ||||
|         extending can interfere with subsequent rendering. | ||||
|         """ | ||||
|         engine = Engine(app_dirs=True) | ||||
|         template = engine.from_string('{% load inclusion %}{% inclusion_extends1 %}{% inclusion_extends2 %}') | ||||
|         self.assertEqual(template.render(Context({})).strip(), 'one\ntwo') | ||||
|  | ||||
|     def test_assignment_tags(self): | ||||
|         c = Context({'value': 42}) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user