Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/template/library.py: 22%
176 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1from functools import wraps
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:
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:
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[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:
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 setattr(unwrap(filter_func), attr, value)
91 filter_func._filter_name = name
92 return filter_func
93 else:
94 raise ValueError(
95 "Unsupported arguments to Library.filter: (%r, %r)"
96 % (name, filter_func),
97 )
99 def filter_function(self, func, **flags):
100 return self.filter(func.__name__, func, **flags)
102 def simple_tag(self, func=None, takes_context=None, name=None):
103 """
104 Register a callable as a compiled template tag. Example:
106 @register.simple_tag
107 def hello(*args, **kwargs):
108 return 'world'
109 """
111 def dec(func):
112 (
113 params,
114 varargs,
115 varkw,
116 defaults,
117 kwonly,
118 kwonly_defaults,
119 _,
120 ) = getfullargspec(unwrap(func))
121 function_name = name or func.__name__
123 @wraps(func)
124 def compile_func(parser, token):
125 bits = token.split_contents()[1:]
126 target_var = None
127 if len(bits) >= 2 and bits[-2] == "as":
128 target_var = bits[-1]
129 bits = bits[:-2]
130 args, kwargs = parse_bits(
131 parser,
132 bits,
133 params,
134 varargs,
135 varkw,
136 defaults,
137 kwonly,
138 kwonly_defaults,
139 takes_context,
140 function_name,
141 )
142 return SimpleNode(func, takes_context, args, kwargs, target_var)
144 self.tag(function_name, compile_func)
145 return func
147 if func is None:
148 # @register.simple_tag(...)
149 return dec
150 elif callable(func):
151 # @register.simple_tag
152 return dec(func)
153 else:
154 raise ValueError("Invalid arguments provided to simple_tag")
156 def inclusion_tag(self, filename, func=None, takes_context=None, name=None):
157 """
158 Register a callable as an inclusion tag:
160 @register.inclusion_tag('results.html')
161 def show_results(poll):
162 choices = poll.choice_set.all()
163 return {'choices': choices}
164 """
166 def dec(func):
167 (
168 params,
169 varargs,
170 varkw,
171 defaults,
172 kwonly,
173 kwonly_defaults,
174 _,
175 ) = getfullargspec(unwrap(func))
176 function_name = name or func.__name__
178 @wraps(func)
179 def compile_func(parser, token):
180 bits = token.split_contents()[1:]
181 args, kwargs = parse_bits(
182 parser,
183 bits,
184 params,
185 varargs,
186 varkw,
187 defaults,
188 kwonly,
189 kwonly_defaults,
190 takes_context,
191 function_name,
192 )
193 return InclusionNode(
194 func,
195 takes_context,
196 args,
197 kwargs,
198 filename,
199 )
201 self.tag(function_name, compile_func)
202 return func
204 return dec
207class TagHelperNode(Node):
208 """
209 Base class for tag helper nodes such as SimpleNode and InclusionNode.
210 Manages the positional and keyword arguments to be passed to the decorated
211 function.
212 """
214 def __init__(self, func, takes_context, args, kwargs):
215 self.func = func
216 self.takes_context = takes_context
217 self.args = args
218 self.kwargs = kwargs
220 def get_resolved_arguments(self, context):
221 resolved_args = [var.resolve(context) for var in self.args]
222 if self.takes_context:
223 resolved_args = [context] + resolved_args
224 resolved_kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()}
225 return resolved_args, resolved_kwargs
228class SimpleNode(TagHelperNode):
229 child_nodelists = ()
231 def __init__(self, func, takes_context, args, kwargs, target_var):
232 super().__init__(func, takes_context, args, kwargs)
233 self.target_var = target_var
235 def render(self, context):
236 resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
237 output = self.func(*resolved_args, **resolved_kwargs)
238 if self.target_var is not None:
239 context[self.target_var] = output
240 return ""
241 if context.autoescape:
242 output = conditional_escape(output)
243 return output
246class InclusionNode(TagHelperNode):
247 def __init__(self, func, takes_context, args, kwargs, filename):
248 super().__init__(func, takes_context, args, kwargs)
249 self.filename = filename
251 def render(self, context):
252 """
253 Render the specified template and context. Cache the template object
254 in render_context to avoid reparsing and loading when used in a for
255 loop.
256 """
257 resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
258 _dict = self.func(*resolved_args, **resolved_kwargs)
260 t = context.render_context.get(self)
261 if t is None:
262 if isinstance(self.filename, Template):
263 t = self.filename
264 elif isinstance(getattr(self.filename, "template", None), Template):
265 t = self.filename.template
266 elif not isinstance(self.filename, str) and is_iterable(self.filename):
267 t = context.template.engine.select_template(self.filename)
268 else:
269 t = context.template.engine.get_template(self.filename)
270 context.render_context[self] = t
271 new_context = context.new(_dict)
272 # Copy across the CSRF token, if present, because inclusion tags are
273 # often used for forms, and we need instructions for using CSRF
274 # protection to be as simple as possible.
275 csrf_token = context.get("csrf_token")
276 if csrf_token is not None:
277 new_context["csrf_token"] = csrf_token
278 return t.render(new_context)
281def parse_bits(
282 parser,
283 bits,
284 params,
285 varargs,
286 varkw,
287 defaults,
288 kwonly,
289 kwonly_defaults,
290 takes_context,
291 name,
292):
293 """
294 Parse bits for template tag helpers simple_tag and inclusion_tag, in
295 particular by detecting syntax errors and by extracting positional and
296 keyword arguments.
297 """
298 if takes_context:
299 if params and params[0] == "context":
300 params = params[1:]
301 else:
302 raise TemplateSyntaxError(
303 "'%s' is decorated with takes_context=True so it must "
304 "have a first argument of 'context'" % name
305 )
306 args = []
307 kwargs = {}
308 unhandled_params = list(params)
309 unhandled_kwargs = [
310 kwarg for kwarg in kwonly if not kwonly_defaults or kwarg not in kwonly_defaults
311 ]
312 for bit in bits:
313 # First we try to extract a potential kwarg from the bit
314 kwarg = token_kwargs([bit], parser)
315 if kwarg:
316 # The kwarg was successfully extracted
317 param, value = kwarg.popitem()
318 if param not in params and param not in kwonly and varkw is None:
319 # An unexpected keyword argument was supplied
320 raise TemplateSyntaxError(
321 "'%s' received unexpected keyword argument '%s'" % (name, param)
322 )
323 elif param in kwargs:
324 # The keyword argument has already been supplied once
325 raise TemplateSyntaxError(
326 "'%s' received multiple values for keyword argument '%s'"
327 % (name, param)
328 )
329 else:
330 # All good, record the keyword argument
331 kwargs[str(param)] = value
332 if param in unhandled_params:
333 # If using the keyword syntax for a positional arg, then
334 # consume it.
335 unhandled_params.remove(param)
336 elif param in unhandled_kwargs:
337 # Same for keyword-only arguments
338 unhandled_kwargs.remove(param)
339 else:
340 if kwargs:
341 raise TemplateSyntaxError(
342 "'%s' received some positional argument(s) after some "
343 "keyword argument(s)" % name
344 )
345 else:
346 # Record the positional argument
347 args.append(parser.compile_filter(bit))
348 try:
349 # Consume from the list of expected positional arguments
350 unhandled_params.pop(0)
351 except IndexError:
352 if varargs is None:
353 raise TemplateSyntaxError(
354 "'%s' received too many positional arguments" % name
355 )
356 if defaults is not None:
357 # Consider the last n params handled, where n is the
358 # number of defaults.
359 unhandled_params = unhandled_params[: -len(defaults)]
360 if unhandled_params or unhandled_kwargs:
361 # Some positional arguments were not supplied
362 raise TemplateSyntaxError(
363 "'%s' did not receive value(s) for the argument(s): %s"
364 % (name, ", ".join("'%s'" % p for p in unhandled_params + unhandled_kwargs))
365 )
366 return args, kwargs
369def import_library(name):
370 """
371 Load a Library object from a template tag module.
372 """
373 try:
374 module = import_module(name)
375 except ImportError as e:
376 raise InvalidTemplateLibrary(
377 "Invalid template library specified. ImportError raised when "
378 "trying to load '%s': %s" % (name, e)
379 )
380 try:
381 return module.register
382 except AttributeError:
383 raise InvalidTemplateLibrary(
384 "Module %s does not have a variable named 'register'" % name,
385 )