Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit e2f06226ea4a38377cdb69f2f5768e4e00c2d88e 1 parent e973ee6
Curtis Maloney authored August 29, 2013 akaariai committed August 30, 2013
51  django/template/loader_tags.py
@@ -121,55 +121,34 @@ def render(self, context):
121 121
         # the same.
122 122
         return compiled_parent._render(context)
123 123
 
124  
-class BaseIncludeNode(Node):
125  
-    def __init__(self, *args, **kwargs):
  124
+class IncludeNode(Node):
  125
+    def __init__(self, template, *args, **kwargs):
  126
+        self.template = template
126 127
         self.extra_context = kwargs.pop('extra_context', {})
127 128
         self.isolated_context = kwargs.pop('isolated_context', False)
128  
-        super(BaseIncludeNode, self).__init__(*args, **kwargs)
129  
-
130  
-    def render_template(self, template, context):
131  
-        values = dict([(name, var.resolve(context)) for name, var
132  
-                       in six.iteritems(self.extra_context)])
133  
-        if self.isolated_context:
134  
-            return template.render(context.new(values))
135  
-        with context.push(**values):
136  
-            return template.render(context)
137  
-
138  
-
139  
-class ConstantIncludeNode(BaseIncludeNode):
140  
-    def __init__(self, template_path, *args, **kwargs):
141  
-        super(ConstantIncludeNode, self).__init__(*args, **kwargs)
142  
-        try:
143  
-            t = get_template(template_path)
144  
-            self.template = t
145  
-        except:
146  
-            if settings.TEMPLATE_DEBUG:
147  
-                raise
148  
-            self.template = None
149  
-
150  
-    def render(self, context):
151  
-        if not self.template:
152  
-            return ''
153  
-        return self.render_template(self.template, context)
154  
-
155  
-class IncludeNode(BaseIncludeNode):
156  
-    def __init__(self, template_name, *args, **kwargs):
157 129
         super(IncludeNode, self).__init__(*args, **kwargs)
158  
-        self.template_name = template_name
159 130
 
160 131
     def render(self, context):
161 132
         try:
162  
-            template = self.template_name.resolve(context)
  133
+            template = self.template.resolve(context)
163 134
             # Does this quack like a Template?
164 135
             if not callable(getattr(template, 'render', None)):
165 136
                 # If not, we'll try get_template
166 137
                 template = get_template(template)
167  
-            return self.render_template(template, context)
  138
+            values = {
  139
+                name: var.resolve(context)
  140
+                for name, var in six.iteritems(self.extra_context)
  141
+            }
  142
+            if self.isolated_context:
  143
+                return template.render(context.new(values))
  144
+            with context.push(**values):
  145
+                return template.render(context)
168 146
         except:
169 147
             if settings.TEMPLATE_DEBUG:
170 148
                 raise
171 149
             return ''
172 150
 
  151
+
173 152
 @register.tag('block')
174 153
 def do_block(parser, token):
175 154
     """
@@ -258,9 +237,5 @@ def do_include(parser, token):
258 237
         options[option] = value
259 238
     isolated_context = options.get('only', False)
260 239
     namemap = options.get('with', {})
261  
-    path = bits[1]
262  
-    if path[0] in ('"', "'") and path[-1] == path[0]:
263  
-        return ConstantIncludeNode(path[1:-1], extra_context=namemap,
264  
-                                   isolated_context=isolated_context)
265 240
     return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap,
266 241
                        isolated_context=isolated_context)
2  docs/releases/1.7.txt
@@ -263,6 +263,8 @@ Templates
263 263
   arguments will be looked up using
264 264
   :func:`~django.template.loader.get_template` as always.
265 265
 
  266
+* It is now possible to :ttag:`include` templates recursively.
  267
+
266 268
 Backwards incompatible changes in 1.7
267 269
 =====================================
268 270
 
7  tests/template_tests/templates/recursive_include.html
... ...
@@ -0,0 +1,7 @@
  1
+Recursion!
  2
+{% for comment in comments %}
  3
+    {{ comment.comment }}
  4
+    {% if comment.children %}
  5
+        {% include "recursive_include.html" with comments=comment.children %}
  6
+    {% endif %}
  7
+{% endfor %}
34  tests/template_tests/tests.py
@@ -349,6 +349,40 @@ def test_include_template_argument(self):
349 349
         output = outer_tmpl.render(ctx)
350 350
         self.assertEqual(output, 'This worked!')
351 351
 
  352
+    @override_settings(TEMPLATE_DEBUG=True)
  353
+    def test_include_immediate_missing(self):
  354
+        """
  355
+        Regression test for #16417 -- {% include %} tag raises TemplateDoesNotExist at compile time if TEMPLATE_DEBUG is True
  356
+
  357
+        Test that an {% include %} tag with a literal string referencing a
  358
+        template that does not exist does not raise an exception at parse
  359
+        time.
  360
+        """
  361
+        ctx = Context()
  362
+        tmpl = Template('{% include "this_does_not_exist.html" %}')
  363
+        self.assertIsInstance(tmpl, Template)
  364
+
  365
+    @override_settings(TEMPLATE_DEBUG=True)
  366
+    def test_include_recursive(self):
  367
+        comments = [
  368
+            {
  369
+                'comment': 'A1',
  370
+                'children': [
  371
+                    {'comment': 'B1', 'children': []},
  372
+                    {'comment': 'B2', 'children': []},
  373
+                    {'comment': 'B3', 'children': [
  374
+                        {'comment': 'C1', 'children': []}
  375
+                    ]},
  376
+                ]
  377
+            }
  378
+        ]
  379
+
  380
+        t = loader.get_template('recursive_include.html')
  381
+        self.assertEqual(
  382
+            "Recursion!  A1  Recursion!  B1   B2   B3  Recursion!  C1",
  383
+            t.render(Context({'comments': comments})).replace(' ', '').replace('\n', ' ').strip(),
  384
+        )
  385
+
352 386
 
353 387
 class TemplateRegressionTests(TestCase):
354 388
 

0 notes on commit e2f0622

Please sign in to comment.
Something went wrong with that request. Please try again.