Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/template/library.py: 45%
178 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 functools
2from importlib import import_module
3from inspect import getfullargspec, unwrap
5from django.utils.html import conditional_escape
6from django.utils.itercompat import is_iterable
8from .base import Node, Template, token_kwargs
9from .exceptions import TemplateSyntaxError
12class InvalidTemplateLibrary(Exception):
13 pass
16class Library:
17 """
18 A class for registering template tags and filters. Compiled filter and
19 template tag functions are stored in the filters and tags attributes.
20 The filter, simple_tag, and inclusion_tag methods provide a convenient
21 way to register callables as tags.
22 """
24 def __init__(self):
25 self.filters = {}
26 self.tags = {}
28 def tag(self, name=None, compile_function=None):
29 if name is None and compile_function is None: 29 ↛ 31line 29 didn't jump to line 31, because the condition on line 29 was never true
30 # @register.tag()
31 return self.tag_function
32 elif name is not None and compile_function is None:
33 if callable(name):
34 # @register.tag
35 return self.tag_function(name)
36 else:
37 # @register.tag('somename') or @register.tag(name='somename')
38 def dec(func):
39 return self.tag(name, func)
41 return dec
42 elif name is not None and compile_function is not None: 42 ↛ 47line 42 didn't jump to line 47, because the condition on line 42 was never false
43 # register.tag('somename', somefunc)
44 self.tags[name] = compile_function
45 return compile_function
46 else:
47 raise ValueError(
48 "Unsupported arguments to Library.tag: (%r, %r)"
49 % (name, compile_function),
50 )
52 def tag_function(self, func):
53 self.tags[getattr(func, "_decorated_function", func).__name__] = func
54 return func
56 def filter(self, name=None, filter_func=None, **flags):
57 """
58 Register a callable as a template filter. Example:
60 @register.filter
61 def lower(value):
62 return value.lower()
63 """
64 if name is None and filter_func is None:
65 # @register.filter()
66 def dec(func):
67 return self.filter_function(func, **flags)
69 return dec
70 elif name is not None and filter_func is None:
71 if callable(name):
72 # @register.filter
73 return self.filter_function(name, **flags)
74 else:
75 # @register.filter('somename') or @register.filter(name='somename')
76 def dec(func):
77 return self.filter(name, func, **flags)
79 return dec
80 elif name is not None and filter_func is not None: 80 ↛ 95line 80 didn't jump to line 95, because the condition on line 80 was never false
81 # register.filter('somename', somefunc)
82 self.filters[name] = filter_func
83 for attr in ("expects_localtime", "is_safe", "needs_autoescape"):
84 if attr in flags:
85 value = flags[attr]
86 # set the flag on the filter for FilterExpression.resolve
87 setattr(filter_func, attr, value)
88 # set the flag on the innermost decorated function
89 # for decorators that need it, e.g. stringfilter
90 if hasattr(filter_func, "_decorated_function"):
91 setattr(filter_func._decorated_function, attr, value)
92 filter_func._filter_name = name
93 return filter_func
94 else:
95 raise ValueError(
96 "Unsupported arguments to Library.filter: (%r, %r)"
97 % (name, filter_func),
98 )
100 def filter_function(self, func, **flags):
101 name = getattr(func, "_decorated_function", func).__name__
102 return self.filter(name, func, **flags)
104 def simple_tag(self, func=None, takes_context=None, name=None):
105 """
106 Register a callable as a compiled template tag. Example:
108 @register.simple_tag
109 def hello(*args, **kwargs):
110 return 'world'
111 """
113 def dec(func):
114 (
115 params,
116 varargs,
117 varkw,
118 defaults,
119 kwonly,
120 kwonly_defaults,
121 _,
122 ) = getfullargspec(unwrap(func))
123 function_name = name or getattr(func, "_decorated_function", func).__name__
125 @functools.wraps(func)
126 def compile_func(parser, token):
127 bits = token.split_contents()[1:]
128 target_var = None
129 if len(bits) >= 2 and bits[-2] == "as":
130 target_var = bits[-1]
131 bits = bits[:-2]
132 args, kwargs = parse_bits(
133 parser,
134 bits,
135 params,
136 varargs,
137 varkw,
138 defaults,
139 kwonly,
140 kwonly_defaults,
141 takes_context,
142 function_name,
143 )
144 return SimpleNode(func, takes_context, args, kwargs, target_var)
146 self.tag(function_name, compile_func)
147 return func
149 if func is None:
150 # @register.simple_tag(...)
151 return dec
152 elif callable(func): 152 ↛ 156line 152 didn't jump to line 156, because the condition on line 152 was never false
153 # @register.simple_tag
154 return dec(func)
155 else:
156 raise ValueError("Invalid arguments provided to simple_tag")
158 def inclusion_tag(self, filename, func=None, takes_context=None, name=None):
159 """
160 Register a callable as an inclusion tag:
162 @register.inclusion_tag('results.html')
163 def show_results(poll):
164 choices = poll.choice_set.all()
165 return {'choices': choices}
166 """
168 def dec(func):
169 (
170 params,
171 varargs,
172 varkw,
173 defaults,
174 kwonly,
175 kwonly_defaults,
176 _,
177 ) = getfullargspec(unwrap(func))
178 function_name = name or getattr(func, "_decorated_function", func).__name__
180 @functools.wraps(func)
181 def compile_func(parser, token):
182 bits = token.split_contents()[1:]
183 args, kwargs = parse_bits(
184 parser,
185 bits,
186 params,
187 varargs,
188 varkw,
189 defaults,
190 kwonly,
191 kwonly_defaults,
192 takes_context,
193 function_name,
194 )
195 return InclusionNode(
196 func,
197 takes_context,
198 args,
199 kwargs,
200 filename,
201 )
203 self.tag(function_name, compile_func)
204 return func
206 return dec
209class TagHelperNode(Node):
210 """
211 Base class for tag helper nodes such as SimpleNode and InclusionNode.
212 Manages the positional and keyword arguments to be passed to the decorated
213 function.
214 """
216 def __init__(self, func, takes_context, args, kwargs):
217 self.func = func
218 self.takes_context = takes_context
219 self.args = args
220 self.kwargs = kwargs
222 def get_resolved_arguments(self, context):
223 resolved_args = [var.resolve(context) for var in self.args]
224 if self.takes_context:
225 resolved_args = [context] + resolved_args
226 resolved_kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()}
227 return resolved_args, resolved_kwargs
230class SimpleNode(TagHelperNode):
231 child_nodelists = ()
233 def __init__(self, func, takes_context, args, kwargs, target_var):
234 super().__init__(func, takes_context, args, kwargs)
235 self.target_var = target_var
237 def render(self, context):
238 resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
239 output = self.func(*resolved_args, **resolved_kwargs)
240 if self.target_var is not None:
241 context[self.target_var] = output
242 return ""
243 if context.autoescape:
244 output = conditional_escape(output)
245 return output
248class InclusionNode(TagHelperNode):
249 def __init__(self, func, takes_context, args, kwargs, filename):
250 super().__init__(func, takes_context, args, kwargs)
251 self.filename = filename
253 def render(self, context):
254 """
255 Render the specified template and context. Cache the template object
256 in render_context to avoid reparsing and loading when used in a for
257 loop.
258 """
259 resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
260 _dict = self.func(*resolved_args, **resolved_kwargs)
262 t = context.render_context.get(self)
263 if t is None:
264 if isinstance(self.filename, Template):
265 t = self.filename
266 elif isinstance(getattr(self.filename, "template", None), Template):
267 t = self.filename.template
268 elif not isinstance(self.filename, str) and is_iterable(self.filename):
269 t = context.template.engine.select_template(self.filename)
270 else:
271 t = context.template.engine.get_template(self.filename)
272 context.render_context[self] = t
273 new_context = context.new(_dict)
274 # Copy across the CSRF token, if present, because inclusion tags are
275 # often used for forms, and we need instructions for using CSRF
276 # protection to be as simple as possible.
277 csrf_token = context.get("csrf_token")
278 if csrf_token is not None:
279 new_context["csrf_token"] = csrf_token
280 return t.render(new_context)
283def parse_bits(
284 parser,
285 bits,
286 params,
287 varargs,
288 varkw,
289 defaults,
290 kwonly,
291 kwonly_defaults,
292 takes_context,
293 name,
294):
295 """
296 Parse bits for template tag helpers simple_tag and inclusion_tag, in
297 particular by detecting syntax errors and by extracting positional and
298 keyword arguments.
299 """
300 if takes_context:
301 if params and params[0] == "context":
302 params = params[1:]
303 else:
304 raise TemplateSyntaxError(
305 "'%s' is decorated with takes_context=True so it must "
306 "have a first argument of 'context'" % name
307 )
308 args = []
309 kwargs = {}
310 unhandled_params = list(params)
311 unhandled_kwargs = [
312 kwarg for kwarg in kwonly if not kwonly_defaults or kwarg not in kwonly_defaults
313 ]
314 for bit in bits:
315 # First we try to extract a potential kwarg from the bit
316 kwarg = token_kwargs([bit], parser)
317 if kwarg:
318 # The kwarg was successfully extracted
319 param, value = kwarg.popitem()
320 if param not in params and param not in kwonly and varkw is None:
321 # An unexpected keyword argument was supplied
322 raise TemplateSyntaxError(
323 "'%s' received unexpected keyword argument '%s'" % (name, param)
324 )
325 elif param in kwargs:
326 # The keyword argument has already been supplied once
327 raise TemplateSyntaxError(
328 "'%s' received multiple values for keyword argument '%s'"
329 % (name, param)
330 )
331 else:
332 # All good, record the keyword argument
333 kwargs[str(param)] = value
334 if param in unhandled_params:
335 # If using the keyword syntax for a positional arg, then
336 # consume it.
337 unhandled_params.remove(param)
338 elif param in unhandled_kwargs:
339 # Same for keyword-only arguments
340 unhandled_kwargs.remove(param)
341 else:
342 if kwargs:
343 raise TemplateSyntaxError(
344 "'%s' received some positional argument(s) after some "
345 "keyword argument(s)" % name
346 )
347 else:
348 # Record the positional argument
349 args.append(parser.compile_filter(bit))
350 try:
351 # Consume from the list of expected positional arguments
352 unhandled_params.pop(0)
353 except IndexError:
354 if varargs is None:
355 raise TemplateSyntaxError(
356 "'%s' received too many positional arguments" % name
357 )
358 if defaults is not None:
359 # Consider the last n params handled, where n is the
360 # number of defaults.
361 unhandled_params = unhandled_params[: -len(defaults)]
362 if unhandled_params or unhandled_kwargs:
363 # Some positional arguments were not supplied
364 raise TemplateSyntaxError(
365 "'%s' did not receive value(s) for the argument(s): %s"
366 % (name, ", ".join("'%s'" % p for p in unhandled_params + unhandled_kwargs))
367 )
368 return args, kwargs
371def import_library(name):
372 """
373 Load a Library object from a template tag module.
374 """
375 try:
376 module = import_module(name)
377 except ImportError as e:
378 raise InvalidTemplateLibrary(
379 "Invalid template library specified. ImportError raised when "
380 "trying to load '%s': %s" % (name, e)
381 )
382 try:
383 return module.register
384 except AttributeError:
385 raise InvalidTemplateLibrary(
386 "Module %s does not have a variable named 'register'" % name,
387 )