This repository has been archived by the owner on Oct 21, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
include_experiments.py
708 lines (625 loc) · 30 KB
/
include_experiments.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2017 Brandon Ray Beck <brandonrbeck101@gmail.com>
#
# Distributed under terms of the MIT license.
"""
NOTE: This is an experimental tag library written to test the usefullness and limitations of its methods.
Only the bare-basics have been tested.
It may be possible to create uncaught dependency loops.
We have yet to instpect how State/context is working.
This currently only has the bare-minimum needed changes to generate expected results in the simplist use cases,
or rather, just 3 simple tests. Written in 1-2hrs without prior knowledge of the code.
DO NOT USE this in a production enviorment!
If deemed significantly easier than existing methods, then thorough tests will be written,
bugs and limitations will be fixed, and a patch will be sent upstream.
Description:
extends_included BLOCK -
includes a template in-place, with block_oveload contents replacing block contents.
NOTE This creates its own block-context between the begin/end tag.
block_overload -
replaces contents of blocks in a extend_include.
NOTE: block-overload is a block-local tag. block, however, is a template-global tag.
block tags INSIDE the extends_included will also overload the included blocks.
HOWEVER, since block tags are template-local, not block-local, it is not recommended.
block_overload allows you to overload the same block with diffrent content for each extends_included.
block only allows you to overload once.
NOTE: blocks inside extend_included will be overloaded by any templates calling us with extends as well.
block_overload, however, will not be overloaded (unless they are inside a block tag)
TODO:
include template_name
with_blocks include_block_name=our_block_name # overload only the blocks specified
other_include_block_name=our_block_overload_name
shared_block_name
extended # overload all shared_block_names
only_blocks included_block_names # only include contents of included_block_names, and overloaded_block_names
# (overloaded_block_names as populated by extended, or with_blocks)
posible new with_blocks to overload include blocks with blocks defined within our template
^^ Similar options needed for extends_included ^^
extend_us template_name:
like extends, but reversed.
similar to putting 'extends this_template' at the top of 'template_name', and a block.super at the top of block.
Use Cases:
Including A generic template multiple times, but with slightly diffrent innermost contents.
Eg.
Current Limitations and complications:
The included template cannot alter the calling template (as is with extends and include).
This means there is no easy way for the included template to appened data above it's included spot,
specificly, a included template in the body cannot alter the head.
If you want that, then extends will have to cut it for you for now.
Example Usage:
generic template base_ui/carousel.htm:
{% with carousel_id|or_gen_uuid:'carousel_' as carousel_id %}
{% block carousel-header %}
{% endblock carousel-header %}
<div id="{{carousel_id}}" class="carousel slide" data-ride="carousel" >
<div class="carousel-inner">
{% block carousel-indicators %}
<ol class="carousel-indicators">
{% for item in item-list %}
<li data-target="#{{carousel_id}}" data-slide-to="{{forloop.counter0}}" class="{% if forloop.first %} active {% endif %}"></li>
{% endfor item in item-list %}
</ol>
{% endblock carousel-indicators %}
{% for item in item-list %}
{% block carousel-item %}
<div class="carousel-item {% if forloop.first %} active {% endif %}">
<!-- should content not overlap the controls? div id="{{item.htmlid}}" class="container"-->
<img src="{{item.image_url}}">
<div class="carousel-caption d-none d-md-block">
<h1>{{item.title}}</h1>
{{item.short_desc}}
<br>
{% if item.permalink %}
<a href="{{item.permalink}}" class="btn btn-lg btn-primary" role="button">Learn More</a>
{% endif %}
</div>
<!--/div-->
</div>
{% endblock carousel-item %}
{% endfor %}
{% block carousel-controls %}
<a class="carousel-control-prev" href="#{{carousel_id}}" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#{{carousel_id}}" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
{% endblock carousel-controls %}
</div>
</div>
{% endwith carousel_id|or_gen_uuid:'carousel_' as carousel_id %}
calling template extensive_use.html:
{% extend_included base_ui/carousel.htm with item-list=blog_posts %}
{% block_overload carousel-item %}
<h1>{{item.title}}</h1>
<p>{{item.description}}</p>
{% endblock_overload carousel-item %}
{% endextend_included base_ui/carousel.htm %}
{% extend_included base_ui/carousel.htm with item-list=img_posts %}
{% block_overload carousel-item %}
<img src="{{item.url}}" title="{{item.title}}"/>
{% endblock_overload carousel-item %}
{% endextend_included base_ui/carousel.htm %}
{% extend_included base_ui/carousel.htm with item-list=people_list %}
{% block_overload carousel-item %}
<img src="{{item.url}}" title="{{item.title}}"/>
{% endblock_overload carousel-item %}
{% endextend_included base_ui/carousel.htm %}
"""
import logging
import posixpath
import warnings
from collections import defaultdict
from django.utils.deprecation import RemovedInDjango21Warning
from django.utils.safestring import mark_safe
from django.template.base import (
Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs,
)
from django.template.library import Library
register = Library()
BLOCK_CONTEXT_KEY = 'block_context'
#OVERLOAD_BLOCK_CONTEXT_KEY = 'overload_block_context'
OVERLOAD_BLOCK_CONTEXT_KEY = 'block_context'
logger = logging.getLogger('django.template')
class BlockContext:
def __init__(self):
# Dictionary of FIFO queues.
self.blocks = defaultdict(list)
def add_blocks(self, blocks):
for name, block in blocks.items():
self.blocks[name].insert(0, block)
def pop(self, name):
try:
return self.blocks[name].pop()
except IndexError:
return None
def push(self, name, block):
self.blocks[name].append(block)
def get_block(self, name):
try:
return self.blocks[name][-1]
except IndexError:
return None
class BlockNode(Node):
def __init__(self, name, nodelist, parent=None):
self.name, self.nodelist, self.parent = name, nodelist, parent
def __repr__(self):
return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
def render(self, context):
block_context = context.render_context.get(BLOCK_CONTEXT_KEY)
with context.push():
if block_context is None:
context['block'] = self
result = self.nodelist.render(context)
else:
push = block = block_context.pop(self.name)
if block is None:
block = self
# Create new block so we can store context without thread-safety issues.
block = type(self)(block.name, block.nodelist)
block.context = context
context['block'] = block
result = block.nodelist.render(context)
if push is not None:
block_context.push(self.name, push)
return result
def super(self):
if not hasattr(self, 'context'):
raise TemplateSyntaxError(
"'%s' object has no attribute 'context'. Did you use "
"{{ block.super }} in a base template?" % self.__class__.__name__
)
render_context = self.context.render_context
if (BLOCK_CONTEXT_KEY in render_context and
render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None):
return mark_safe(self.render(self.context))
return ''
class OverloadBlockNode(Node): # Lives in the context of the include_extended block we are in.
def __init__(self, name, nodelist, parent=None):
self.name, self.nodelist, self.parent = name, nodelist, parent
def __repr__(self):
return "<Overload Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
def render(self, context):
block_context = context.render_context.get(OVERLOAD_BLOCK_CONTEXT_KEY)
with context.push():
if block_context is None:
context['overloadblock'] = self
result = self.nodelist.render(context)
else:
push = block = block_context.pop(self.name)
if block is None:
block = self
# Create new block so we can store context without thread-safety issues.
block = type(self)(block.name, block.nodelist)
block.context = context
context['overloadblock'] = block
result = block.nodelist.render(context)
if push is not None:
block_context.push(self.name, push)
return result
class ExtendsNode(Node):
must_be_first = True
context_key = 'extends_context'
def __init__(self, nodelist, parent_name, template_dirs=None):
self.nodelist = nodelist
self.parent_name = parent_name
self.template_dirs = template_dirs
self.blocks = {n.name: n for n in nodelist.get_nodes_by_type(BlockNode)}
def __repr__(self):
return '<%s: extends %s>' % (self.__class__.__name__, self.parent_name.token)
def find_template(self, template_name, context):
"""
This is a wrapper around engine.find_template(). A history is kept in
the render_context attribute between successive extends calls and
passed as the skip argument. This enables extends to work recursively
without extending the same template twice.
"""
history = context.render_context.setdefault(
self.context_key, [self.origin],
)
template, origin = context.template.engine.find_template(
template_name, skip=history,
)
history.append(origin)
return template
def get_parent(self, context):
parent = self.parent_name.resolve(context)
if not parent:
error_msg = "Invalid template name in 'extends' tag: %r." % parent
if self.parent_name.filters or\
isinstance(self.parent_name.var, Variable):
error_msg += " Got this from the '%s' variable." %\
self.parent_name.token
raise TemplateSyntaxError(error_msg)
if isinstance(parent, Template):
# parent is a django.template.Template
return parent
if isinstance(getattr(parent, 'template', None), Template):
# parent is a django.template.backends.django.Template
return parent.template
return self.find_template(parent, context)
def render(self, context):
compiled_parent = self.get_parent(context)
if BLOCK_CONTEXT_KEY not in context.render_context:
context.render_context[BLOCK_CONTEXT_KEY] = BlockContext()
block_context = context.render_context[BLOCK_CONTEXT_KEY]
# Add the block nodes from this node to the block context
block_context.add_blocks(self.blocks)
# If this block's parent doesn't have an extends node it is the root,
# and its block nodes also need to be added to the block context.
for node in compiled_parent.nodelist:
# The ExtendsNode has to be the first non-text node.
if not isinstance(node, TextNode):
if not isinstance(node, ExtendsNode):
blocks = {n.name: n for n in
compiled_parent.nodelist.get_nodes_by_type(BlockNode)}
block_context.add_blocks(blocks)
break
# Call Template._render explicitly so the parser context stays
# the same.
with context.render_context.push_state(compiled_parent, isolated_context=False):
return compiled_parent._render(context)
# We are the parent, extending the included child.
""" Example usage:
{% include_extended base_ui/carosel.htm %}
{% block carosel-item %}
<h1>Extended includes makes working with multiple templates more natural </h1>
<h2>You can override blocks in multiple templates, or the same one</h2>
<h3>The included template can be overridden </h3>
<h1>{{item.title}}</h1>
{% endblock carosel-item %}
{% endinclude_extended base_ui/carosel.htm %}
{% include_extended base_ui/checklist.htm %}
{% block item %}
<h3>Working with a generic interface templete, and don't want to create 20 diffrent templates to replace that one block that differs?</h3>
{% block item %}
{% include_extended base_ui/carosel.htm %}
{% include_extended base_ui/checklist.htm %}
{% block item %}
<h3>Fear not! Just like includes, include_extended allows multiple invocations to the same template!</h3>
{% block item %}
{% include_extended base_ui/carosel.htm %}
Possible Complications:
Blocks need to be local inside the tag? else we need a replace_block/endreplace_block or override_block
"""
class IncludeExtendedNode(Node):
must_be_first = False
context_key = '__include_context'
def __init__(self, nodelist, parent_name, *args, template_dirs=None,extra_context=None, isolated_context=False, **kwargs):
self.nodelist = nodelist
self.parent_name = parent_name
self.template_dirs = template_dirs
self.blocks = {n.name: n for n in nodelist.get_nodes_by_type(BlockNode)}
self.overload_blocks = {n.name: n for n in nodelist.get_nodes_by_type(OverloadBlockNode)}
self.extra_context = extra_context or {}
self.isolated_context = isolated_context
super().__init__(*args, **kwargs)
def __repr__(self):
return '<%s: include_extends %s>' % (self.__class__.__name__, self.parent_name.token)
def find_template(self, template_name, context):
"""
This is a wrapper around engine.find_template(). A history is kept in
the render_context attribute between successive extends calls and
passed as the skip argument. This enables extends to work recursively
without extending the same template twice.
"""
history = context.render_context.setdefault(
self.context_key, [self.origin],
)
for h in history:
print(h)
template, origin = context.template.engine.find_template(
template_name, skip=history,
)
#history.append(origin)
return template
def get_parent(self, context):
parent = self.parent_name.resolve(context)
if not parent:
error_msg = "Invalid template name in 'extends' tag: %r." % parent
if self.parent_name.filters or\
isinstance(self.parent_name.var, Variable):
error_msg += " Got this from the '%s' variable." %\
self.parent_name.token
raise TemplateSyntaxError(error_msg)
if isinstance(parent, Template):
# parent is a django.template.Template
return parent
if isinstance(getattr(parent, 'template', None), Template):
# parent is a django.template.backends.django.Template
return parent.template
return self.find_template(parent, context)
def render(self, context):
# FIXME This is where the magic happens
compiled_parent = self.get_parent(context)
if BLOCK_CONTEXT_KEY not in context.render_context:
context.render_context[BLOCK_CONTEXT_KEY] = BlockContext()
block_context = context.render_context[BLOCK_CONTEXT_KEY]
# Add the block nodes from this node to the block context
block_context.add_blocks(self.blocks)
# Why is this needed for overload_blocks but not blocks?
for name, block in self.overload_blocks.items():
block_context.blocks[name].append(block)
print("OVERLOADING IT\n" * 3)
for n in self.nodelist.get_nodes_by_type(OverloadBlockNode):
print(n.name)
print(n)
# If this block's parent doesn't have an extends node it is the root,
# and its block nodes also need to be added to the block context.
for node in compiled_parent.nodelist:
# The ExtendsNode has to be the first non-text node.
if not isinstance(node, TextNode):
if not isinstance(node, ExtendsNode):
blocks = {n.name: n for n in
compiled_parent.nodelist.get_nodes_by_type(BlockNode)}
overloaded_blocks = {n.name: n for n in
compiled_parent.nodelist.get_nodes_by_type(OverloadBlockNode)}
block_context.add_blocks(blocks)
block_context.add_blocks(overloaded_blocks)
break
# Call Template._render explicitly so the parser context stays
# the same.
with context.render_context.push_state(compiled_parent, isolated_context=False):
values = {
name: var.resolve(context)
for name, var in self.extra_context.items()
}
res=None
if self.isolated_context:
ncontext=context.new(values)
res=compiled_parent._render(ncontext)
else:
with context.push(**values):
res=compiled_parent._render(context)
for name, block in self.overload_blocks.items():
block_context.blocks[name].pop()
return res
class IncludeNode(Node):
context_key = '__include_context'
def __init__(self, template, *args, extra_context=None, isolated_context=False, **kwargs):
self.template = template
self.extra_context = extra_context or {}
self.isolated_context = isolated_context
super().__init__(*args, **kwargs)
def render(self, context):
"""
Render the specified template and context. Cache the template object
in render_context to avoid reparsing and loading when used in a for
loop.
"""
try:
template = self.template.resolve(context)
# Does this quack like a Template?
if not callable(getattr(template, 'render', None)):
# If not, we'll try our cache, and get_template()
template_name = template
cache = context.render_context.dicts[0].setdefault(self, {})
template = cache.get(template_name)
if template is None:
template = context.template.engine.get_template(template_name)
cache[template_name] = template
# Use the base.Template of a backends.django.Template.
elif hasattr(template, 'template'):
template = template.template
values = {
name: var.resolve(context)
for name, var in self.extra_context.items()
}
if self.isolated_context:
return template.render(context.new(values))
with context.push(**values):
return template.render(context)
except Exception as e:
if context.template.engine.debug:
raise
template_name = getattr(context, 'template_name', None) or 'unknown'
warnings.warn(
"Rendering {%% include '%s' %%} raised %s. In Django 2.1, "
"this exception will be raised rather than silenced and "
"rendered as an empty string." %
(template_name, e.__class__.__name__),
RemovedInDjango21Warning,
)
logger.warning(
"Exception raised while rendering {%% include %%} for "
"template '%s'. Empty string rendered instead.",
template_name,
exc_info=True,
)
return ''
@register.tag('block')
def do_block(parser, token):
"""
Define a block that can be overridden by child templates.
"""
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
bits = token.contents.split()
if len(bits) != 2:
raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
block_name = bits[1]
# Keep track of the names of BlockNodes found in this template, so we can
# check for duplication.
try:
if block_name in parser.__loaded_blocks:
raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % (bits[0], block_name))
parser.__loaded_blocks.append(block_name)
except AttributeError: # parser.__loaded_blocks isn't a list yet
parser.__loaded_blocks = [block_name]
nodelist = parser.parse(('endblock',))
# This check is kept for backwards-compatibility. See #3100.
endblock = parser.next_token()
acceptable_endblocks = ('endblock', 'endblock %s' % block_name)
if endblock.contents not in acceptable_endblocks:
parser.invalid_block_tag(endblock, 'endblock', acceptable_endblocks)
return BlockNode(block_name, nodelist)
@register.tag('block_overload')
def do_overload_block(parser, token):
"""
Define a overload_block that can be overrides a include_extended template.
"""
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
bits = token.contents.split()
if len(bits) != 2:
raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
block_name = bits[1]
# FIXME Keep track of the names of BlockNodes found in this include_extended, so we can
# check for duplication.
"""
try:
if block_name in parser.__loaded_blocks:
raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % (bits[0], block_name))
parser.__loaded_blocks.append(block_name)
except AttributeError: # parser.__loaded_blocks isn't a list yet
parser.__loaded_blocks = [block_name]
"""
nodelist = parser.parse(('endblock_overload',))
# This check is kept for backwards-compatibility. See #3100.
endblock = parser.next_token()
acceptable_endblocks = ('endblock_overload', 'endblock_overload %s' % block_name)
if endblock.contents not in acceptable_endblocks:
parser.invalid_block_tag(endblock, 'endblock_overload', acceptable_endblocks)
return OverloadBlockNode(block_name, nodelist)
def construct_relative_path(current_template_name, relative_name):
"""
Convert a relative path (starting with './' or '../') to the full template
name based on the current_template_name.
"""
if not any(relative_name.startswith(x) for x in ["'./", "'../", '"./', '"../']):
# relative_name is a variable or a literal that doesn't contain a
# relative path.
return relative_name
new_name = posixpath.normpath(
posixpath.join(
posixpath.dirname(current_template_name.lstrip('/')),
relative_name.strip('\'"')
)
)
if new_name.startswith('../'):
raise TemplateSyntaxError(
"The relative path '%s' points outside the file hierarchy that "
"template '%s' is in." % (relative_name, current_template_name)
)
if current_template_name.lstrip('/') == new_name:
raise TemplateSyntaxError(
"The relative path '%s' was translated to template name '%s', the "
"same template in which the tag appears."
% (relative_name, current_template_name)
)
return '"%s"' % new_name
@register.tag('extends')
def do_extends(parser, token):
"""
Signal that this template extends a parent template.
This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
uses the literal value "base" as the name of the parent template to extend,
or ``{% extends variable %}`` uses the value of ``variable`` as either the
name of the parent template to extend (if it evaluates to a string) or as
the parent template itself (if it evaluates to a Template object).
"""
bits = token.split_contents()
if len(bits) != 2:
raise TemplateSyntaxError("'%s' takes one argument" % bits[0])
bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
parent_name = parser.compile_filter(bits[1])
nodelist = parser.parse()
if nodelist.get_nodes_by_type(ExtendsNode):
raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % bits[0])
return ExtendsNode(nodelist, parent_name)
@register.tag('extends_included')
def do_include_extended(parser, token):
"""
Signal that this template extends a parent template.
This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
uses the literal value "base" as the name of the parent template to extend,
or ``{% extends variable %}`` uses the value of ``variable`` as either the
name of the parent template to extend (if it evaluates to a string) or as
the parent template itself (if it evaluates to a Template object).
"""
bits = token.split_contents()
if len(bits) < 2:
raise TemplateSyntaxError(
"%r tag takes at least one argument: the name of the template to "
"be included." % bits[0]
)
block_name = bits[1]
#if nodelist.get_nodes_by_type(ExtendsNode):
# raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % bits[0])
#return ExtendsNode(nodelist, parent_name)
# This check is kept for backwards-compatibility. See #3100.
#return BlockNode(block_name, nodelist)
options = {}
remaining_bits = bits[2:]
while remaining_bits:
option = remaining_bits.pop(0)
if option in options:
raise TemplateSyntaxError('The %r option was specified more '
'than once.' % option)
if option == 'with':
value = token_kwargs(remaining_bits, parser, support_legacy=False)
if not value:
raise TemplateSyntaxError('"with" in %r tag needs at least '
'one keyword argument.' % bits[0])
elif option == 'only':
value = True
else:
raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
(bits[0], option))
options[option] = value
isolated_context = options.get('only', False)
namemap = options.get('with', {})
bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
parent_name = parser.compile_filter(bits[1])
nodelist = parser.parse(('endextends_included',))
endblock = parser.next_token()
acceptable_endblocks = ('endextends_included', 'endextends_included %s' % block_name)
if endblock.contents not in acceptable_endblocks and not endblock.contents.startswith('end%s' % bits[0]):
parser.invalid_block_tag(endblock, 'endextends_included', acceptable_endblocks)
return IncludeExtendedNode(nodelist, parent_name, extra_context=namemap,
isolated_context=isolated_context)
@register.tag('include')
def do_include(parser, token):
"""
Load a template and render it with the current context. You can pass
additional context using keyword arguments.
Example::
{% include "foo/some_include" %}
{% include "foo/some_include" with bar="BAZZ!" baz="BING!" %}
Use the ``only`` argument to exclude the current context when rendering
the included template::
{% include "foo/some_include" only %}
{% include "foo/some_include" with bar="1" only %}
"""
bits = token.split_contents()
if len(bits) < 2:
raise TemplateSyntaxError(
"%r tag takes at least one argument: the name of the template to "
"be included." % bits[0]
)
options = {}
remaining_bits = bits[2:]
while remaining_bits:
option = remaining_bits.pop(0)
if option in options:
raise TemplateSyntaxError('The %r option was specified more '
'than once.' % option)
if option == 'with':
value = token_kwargs(remaining_bits, parser, support_legacy=False)
if not value:
raise TemplateSyntaxError('"with" in %r tag needs at least '
'one keyword argument.' % bits[0])
elif option == 'only':
value = True
else:
raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
(bits[0], option))
options[option] = value
isolated_context = options.get('only', False)
namemap = options.get('with', {})
bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap,
isolated_context=isolated_context)