Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/template/loader_tags.py: 18%
187 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import posixpath
2from collections import defaultdict
4from django.utils.safestring import mark_safe
6from .base import Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs
7from .library import Library
9register = Library()
11BLOCK_CONTEXT_KEY = "block_context"
14class BlockContext:
15 def __init__(self):
16 # Dictionary of FIFO queues.
17 self.blocks = defaultdict(list)
19 def __repr__(self):
20 return f"<{self.__class__.__qualname__}: blocks={self.blocks!r}>"
22 def add_blocks(self, blocks):
23 for name, block in blocks.items():
24 self.blocks[name].insert(0, block)
26 def pop(self, name):
27 try:
28 return self.blocks[name].pop()
29 except IndexError:
30 return None
32 def push(self, name, block):
33 self.blocks[name].append(block)
35 def get_block(self, name):
36 try:
37 return self.blocks[name][-1]
38 except IndexError:
39 return None
42class BlockNode(Node):
43 def __init__(self, name, nodelist, parent=None):
44 self.name, self.nodelist, self.parent = name, nodelist, parent
46 def __repr__(self):
47 return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
49 def render(self, context):
50 block_context = context.render_context.get(BLOCK_CONTEXT_KEY)
51 with context.push():
52 if block_context is None:
53 context["block"] = self
54 result = self.nodelist.render(context)
55 else:
56 push = block = block_context.pop(self.name)
57 if block is None:
58 block = self
59 # Create new block so we can store context without thread-safety issues.
60 block = type(self)(block.name, block.nodelist)
61 block.context = context
62 context["block"] = block
63 result = block.nodelist.render(context)
64 if push is not None:
65 block_context.push(self.name, push)
66 return result
68 def super(self):
69 if not hasattr(self, "context"):
70 raise TemplateSyntaxError(
71 "'%s' object has no attribute 'context'. Did you use "
72 "{{ block.super }} in a base template?" % self.__class__.__name__
73 )
74 render_context = self.context.render_context
75 if (
76 BLOCK_CONTEXT_KEY in render_context
77 and render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None
78 ):
79 return mark_safe(self.render(self.context))
80 return ""
83class ExtendsNode(Node):
84 must_be_first = True
85 context_key = "extends_context"
87 def __init__(self, nodelist, parent_name, template_dirs=None):
88 self.nodelist = nodelist
89 self.parent_name = parent_name
90 self.template_dirs = template_dirs
91 self.blocks = {n.name: n for n in nodelist.get_nodes_by_type(BlockNode)}
93 def __repr__(self):
94 return "<%s: extends %s>" % (self.__class__.__name__, self.parent_name.token)
96 def find_template(self, template_name, context):
97 """
98 This is a wrapper around engine.find_template(). A history is kept in
99 the render_context attribute between successive extends calls and
100 passed as the skip argument. This enables extends to work recursively
101 without extending the same template twice.
102 """
103 history = context.render_context.setdefault(
104 self.context_key,
105 [self.origin],
106 )
107 template, origin = context.template.engine.find_template(
108 template_name,
109 skip=history,
110 )
111 history.append(origin)
112 return template
114 def get_parent(self, context):
115 parent = self.parent_name.resolve(context)
116 if not parent:
117 error_msg = "Invalid template name in 'extends' tag: %r." % parent
118 if self.parent_name.filters or isinstance(self.parent_name.var, Variable):
119 error_msg += (
120 " Got this from the '%s' variable." % self.parent_name.token
121 )
122 raise TemplateSyntaxError(error_msg)
123 if isinstance(parent, Template):
124 # parent is a django.template.Template
125 return parent
126 if isinstance(getattr(parent, "template", None), Template):
127 # parent is a django.template.backends.django.Template
128 return parent.template
129 return self.find_template(parent, context)
131 def render(self, context):
132 compiled_parent = self.get_parent(context)
134 if BLOCK_CONTEXT_KEY not in context.render_context:
135 context.render_context[BLOCK_CONTEXT_KEY] = BlockContext()
136 block_context = context.render_context[BLOCK_CONTEXT_KEY]
138 # Add the block nodes from this node to the block context
139 block_context.add_blocks(self.blocks)
141 # If this block's parent doesn't have an extends node it is the root,
142 # and its block nodes also need to be added to the block context.
143 for node in compiled_parent.nodelist:
144 # The ExtendsNode has to be the first non-text node.
145 if not isinstance(node, TextNode):
146 if not isinstance(node, ExtendsNode):
147 blocks = {
148 n.name: n
149 for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)
150 }
151 block_context.add_blocks(blocks)
152 break
154 # Call Template._render explicitly so the parser context stays
155 # the same.
156 with context.render_context.push_state(compiled_parent, isolated_context=False):
157 return compiled_parent._render(context)
160class IncludeNode(Node):
161 context_key = "__include_context"
163 def __init__(
164 self, template, *args, extra_context=None, isolated_context=False, **kwargs
165 ):
166 self.template = template
167 self.extra_context = extra_context or {}
168 self.isolated_context = isolated_context
169 super().__init__(*args, **kwargs)
171 def __repr__(self):
172 return f"<{self.__class__.__qualname__}: template={self.template!r}>"
174 def render(self, context):
175 """
176 Render the specified template and context. Cache the template object
177 in render_context to avoid reparsing and loading when used in a for
178 loop.
179 """
180 template = self.template.resolve(context)
181 # Does this quack like a Template?
182 if not callable(getattr(template, "render", None)):
183 # If not, try the cache and select_template().
184 template_name = template or ()
185 if isinstance(template_name, str):
186 template_name = (
187 construct_relative_path(
188 self.origin.template_name,
189 template_name,
190 ),
191 )
192 else:
193 template_name = tuple(template_name)
194 cache = context.render_context.dicts[0].setdefault(self, {})
195 template = cache.get(template_name)
196 if template is None:
197 template = context.template.engine.select_template(template_name)
198 cache[template_name] = template
199 # Use the base.Template of a backends.django.Template.
200 elif hasattr(template, "template"):
201 template = template.template
202 values = {
203 name: var.resolve(context) for name, var in self.extra_context.items()
204 }
205 if self.isolated_context:
206 return template.render(context.new(values))
207 with context.push(**values):
208 return template.render(context)
211@register.tag("block")
212def do_block(parser, token):
213 """
214 Define a block that can be overridden by child templates.
215 """
216 # token.split_contents() isn't useful here because this tag doesn't accept
217 # variable as arguments.
218 bits = token.contents.split()
219 if len(bits) != 2:
220 raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
221 block_name = bits[1]
222 # Keep track of the names of BlockNodes found in this template, so we can
223 # check for duplication.
224 try:
225 if block_name in parser.__loaded_blocks:
226 raise TemplateSyntaxError(
227 "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
228 )
229 parser.__loaded_blocks.append(block_name)
230 except AttributeError: # parser.__loaded_blocks isn't a list yet
231 parser.__loaded_blocks = [block_name]
232 nodelist = parser.parse(("endblock",))
234 # This check is kept for backwards-compatibility. See #3100.
235 endblock = parser.next_token()
236 acceptable_endblocks = ("endblock", "endblock %s" % block_name)
237 if endblock.contents not in acceptable_endblocks:
238 parser.invalid_block_tag(endblock, "endblock", acceptable_endblocks)
240 return BlockNode(block_name, nodelist)
243def construct_relative_path(current_template_name, relative_name):
244 """
245 Convert a relative path (starting with './' or '../') to the full template
246 name based on the current_template_name.
247 """
248 has_quotes = (relative_name.startswith('"') and relative_name.endswith('"')) or (
249 relative_name.startswith("'") and relative_name.endswith("'")
250 )
251 new_name = relative_name.strip("'\"")
252 if not new_name.startswith(("./", "../")):
253 # relative_name is a variable or a literal that doesn't contain a
254 # relative path.
255 return relative_name
257 new_name = posixpath.normpath(
258 posixpath.join(
259 posixpath.dirname(current_template_name.lstrip("/")),
260 new_name,
261 )
262 )
263 if new_name.startswith("../"):
264 raise TemplateSyntaxError(
265 "The relative path '%s' points outside the file hierarchy that "
266 "template '%s' is in." % (relative_name, current_template_name)
267 )
268 if current_template_name.lstrip("/") == new_name:
269 raise TemplateSyntaxError(
270 "The relative path '%s' was translated to template name '%s', the "
271 "same template in which the tag appears."
272 % (relative_name, current_template_name)
273 )
274 return f'"{new_name}"' if has_quotes else new_name
277@register.tag("extends")
278def do_extends(parser, token):
279 """
280 Signal that this template extends a parent template.
282 This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
283 uses the literal value "base" as the name of the parent template to extend,
284 or ``{% extends variable %}`` uses the value of ``variable`` as either the
285 name of the parent template to extend (if it evaluates to a string) or as
286 the parent template itself (if it evaluates to a Template object).
287 """
288 bits = token.split_contents()
289 if len(bits) != 2:
290 raise TemplateSyntaxError("'%s' takes one argument" % bits[0])
291 bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
292 parent_name = parser.compile_filter(bits[1])
293 nodelist = parser.parse()
294 if nodelist.get_nodes_by_type(ExtendsNode):
295 raise TemplateSyntaxError(
296 "'%s' cannot appear more than once in the same template" % bits[0]
297 )
298 return ExtendsNode(nodelist, parent_name)
301@register.tag("include")
302def do_include(parser, token):
303 """
304 Load a template and render it with the current context. You can pass
305 additional context using keyword arguments.
307 Example::
309 {% include "foo/some_include" %}
310 {% include "foo/some_include" with bar="BAZZ!" baz="BING!" %}
312 Use the ``only`` argument to exclude the current context when rendering
313 the included template::
315 {% include "foo/some_include" only %}
316 {% include "foo/some_include" with bar="1" only %}
317 """
318 bits = token.split_contents()
319 if len(bits) < 2:
320 raise TemplateSyntaxError(
321 "%r tag takes at least one argument: the name of the template to "
322 "be included." % bits[0]
323 )
324 options = {}
325 remaining_bits = bits[2:]
326 while remaining_bits:
327 option = remaining_bits.pop(0)
328 if option in options:
329 raise TemplateSyntaxError(
330 "The %r option was specified more than once." % option
331 )
332 if option == "with":
333 value = token_kwargs(remaining_bits, parser, support_legacy=False)
334 if not value:
335 raise TemplateSyntaxError(
336 '"with" in %r tag needs at least one keyword argument.' % bits[0]
337 )
338 elif option == "only":
339 value = True
340 else:
341 raise TemplateSyntaxError(
342 "Unknown argument for %r tag: %r." % (bits[0], option)
343 )
344 options[option] = value
345 isolated_context = options.get("only", False)
346 namemap = options.get("with", {})
347 bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
348 return IncludeNode(
349 parser.compile_filter(bits[1]),
350 extra_context=namemap,
351 isolated_context=isolated_context,
352 )