mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Improved {% include %} implementation
Merged BaseIncludeNode, ConstantIncludeNode and Include node. This avoids raising TemplateDoesNotExist at parsing time, allows recursion when passing a literal template name, and should make TEMPLATE_DEBUG behavior consistant. Thanks loic84 for help with the tests. Fixed #3544, fixed #12064, fixed #16147
This commit is contained in:
		
				
					committed by
					
						 Anssi Kääriäinen
						Anssi Kääriäinen
					
				
			
			
				
	
			
			
			
						parent
						
							e973ee6a98
						
					
				
				
					commit
					e2f06226ea
				
			| @@ -121,55 +121,34 @@ class ExtendsNode(Node): | |||||||
|         # the same. |         # the same. | ||||||
|         return compiled_parent._render(context) |         return compiled_parent._render(context) | ||||||
|  |  | ||||||
| class BaseIncludeNode(Node): | class IncludeNode(Node): | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, template, *args, **kwargs): | ||||||
|  |         self.template = template | ||||||
|         self.extra_context = kwargs.pop('extra_context', {}) |         self.extra_context = kwargs.pop('extra_context', {}) | ||||||
|         self.isolated_context = kwargs.pop('isolated_context', False) |         self.isolated_context = kwargs.pop('isolated_context', False) | ||||||
|         super(BaseIncludeNode, self).__init__(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     def render_template(self, template, context): |  | ||||||
|         values = dict([(name, var.resolve(context)) for name, var |  | ||||||
|                        in six.iteritems(self.extra_context)]) |  | ||||||
|         if self.isolated_context: |  | ||||||
|             return template.render(context.new(values)) |  | ||||||
|         with context.push(**values): |  | ||||||
|             return template.render(context) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ConstantIncludeNode(BaseIncludeNode): |  | ||||||
|     def __init__(self, template_path, *args, **kwargs): |  | ||||||
|         super(ConstantIncludeNode, self).__init__(*args, **kwargs) |  | ||||||
|         try: |  | ||||||
|             t = get_template(template_path) |  | ||||||
|             self.template = t |  | ||||||
|         except: |  | ||||||
|             if settings.TEMPLATE_DEBUG: |  | ||||||
|                 raise |  | ||||||
|             self.template = None |  | ||||||
|  |  | ||||||
|     def render(self, context): |  | ||||||
|         if not self.template: |  | ||||||
|             return '' |  | ||||||
|         return self.render_template(self.template, context) |  | ||||||
|  |  | ||||||
| class IncludeNode(BaseIncludeNode): |  | ||||||
|     def __init__(self, template_name, *args, **kwargs): |  | ||||||
|         super(IncludeNode, self).__init__(*args, **kwargs) |         super(IncludeNode, self).__init__(*args, **kwargs) | ||||||
|         self.template_name = template_name |  | ||||||
|  |  | ||||||
|     def render(self, context): |     def render(self, context): | ||||||
|         try: |         try: | ||||||
|             template = self.template_name.resolve(context) |             template = self.template.resolve(context) | ||||||
|             # Does this quack like a Template? |             # Does this quack like a Template? | ||||||
|             if not callable(getattr(template, 'render', None)): |             if not callable(getattr(template, 'render', None)): | ||||||
|                 # If not, we'll try get_template |                 # If not, we'll try get_template | ||||||
|                 template = get_template(template) |                 template = get_template(template) | ||||||
|             return self.render_template(template, context) |             values = { | ||||||
|  |                 name: var.resolve(context) | ||||||
|  |                 for name, var in six.iteritems(self.extra_context) | ||||||
|  |             } | ||||||
|  |             if self.isolated_context: | ||||||
|  |                 return template.render(context.new(values)) | ||||||
|  |             with context.push(**values): | ||||||
|  |                 return template.render(context) | ||||||
|         except: |         except: | ||||||
|             if settings.TEMPLATE_DEBUG: |             if settings.TEMPLATE_DEBUG: | ||||||
|                 raise |                 raise | ||||||
|             return '' |             return '' | ||||||
|  |  | ||||||
|  |  | ||||||
| @register.tag('block') | @register.tag('block') | ||||||
| def do_block(parser, token): | def do_block(parser, token): | ||||||
|     """ |     """ | ||||||
| @@ -258,9 +237,5 @@ def do_include(parser, token): | |||||||
|         options[option] = value |         options[option] = value | ||||||
|     isolated_context = options.get('only', False) |     isolated_context = options.get('only', False) | ||||||
|     namemap = options.get('with', {}) |     namemap = options.get('with', {}) | ||||||
|     path = bits[1] |  | ||||||
|     if path[0] in ('"', "'") and path[-1] == path[0]: |  | ||||||
|         return ConstantIncludeNode(path[1:-1], extra_context=namemap, |  | ||||||
|                                    isolated_context=isolated_context) |  | ||||||
|     return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap, |     return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap, | ||||||
|                        isolated_context=isolated_context) |                        isolated_context=isolated_context) | ||||||
|   | |||||||
| @@ -263,6 +263,8 @@ Templates | |||||||
|   arguments will be looked up using |   arguments will be looked up using | ||||||
|   :func:`~django.template.loader.get_template` as always. |   :func:`~django.template.loader.get_template` as always. | ||||||
|  |  | ||||||
|  | * It is now possible to :ttag:`include` templates recursively. | ||||||
|  |  | ||||||
| Backwards incompatible changes in 1.7 | Backwards incompatible changes in 1.7 | ||||||
| ===================================== | ===================================== | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								tests/template_tests/templates/recursive_include.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/template_tests/templates/recursive_include.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | Recursion! | ||||||
|  | {% for comment in comments %} | ||||||
|  |     {{ comment.comment }} | ||||||
|  |     {% if comment.children %} | ||||||
|  |         {% include "recursive_include.html" with comments=comment.children %} | ||||||
|  |     {% endif %} | ||||||
|  | {% endfor %} | ||||||
| @@ -349,6 +349,40 @@ class TemplateLoaderTests(TestCase): | |||||||
|         output = outer_tmpl.render(ctx) |         output = outer_tmpl.render(ctx) | ||||||
|         self.assertEqual(output, 'This worked!') |         self.assertEqual(output, 'This worked!') | ||||||
|  |  | ||||||
|  |     @override_settings(TEMPLATE_DEBUG=True) | ||||||
|  |     def test_include_immediate_missing(self): | ||||||
|  |         """ | ||||||
|  |         Regression test for #16417 -- {% include %} tag raises TemplateDoesNotExist at compile time if TEMPLATE_DEBUG is True | ||||||
|  |  | ||||||
|  |         Test that an {% include %} tag with a literal string referencing a | ||||||
|  |         template that does not exist does not raise an exception at parse | ||||||
|  |         time. | ||||||
|  |         """ | ||||||
|  |         ctx = Context() | ||||||
|  |         tmpl = Template('{% include "this_does_not_exist.html" %}') | ||||||
|  |         self.assertIsInstance(tmpl, Template) | ||||||
|  |  | ||||||
|  |     @override_settings(TEMPLATE_DEBUG=True) | ||||||
|  |     def test_include_recursive(self): | ||||||
|  |         comments = [ | ||||||
|  |             { | ||||||
|  |                 'comment': 'A1', | ||||||
|  |                 'children': [ | ||||||
|  |                     {'comment': 'B1', 'children': []}, | ||||||
|  |                     {'comment': 'B2', 'children': []}, | ||||||
|  |                     {'comment': 'B3', 'children': [ | ||||||
|  |                         {'comment': 'C1', 'children': []} | ||||||
|  |                     ]}, | ||||||
|  |                 ] | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |         t = loader.get_template('recursive_include.html') | ||||||
|  |         self.assertEqual( | ||||||
|  |             "Recursion!  A1  Recursion!  B1   B2   B3  Recursion!  C1", | ||||||
|  |             t.render(Context({'comments': comments})).replace(' ', '').replace('\n', ' ').strip(), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class TemplateRegressionTests(TestCase): | class TemplateRegressionTests(TestCase): | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user