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

1import functools 

2from importlib import import_module 

3from inspect import getfullargspec, unwrap 

4 

5from django.utils.html import conditional_escape 

6from django.utils.itercompat import is_iterable 

7 

8from .base import Node, Template, token_kwargs 

9from .exceptions import TemplateSyntaxError 

10 

11 

12class InvalidTemplateLibrary(Exception): 

13 pass 

14 

15 

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 """ 

23 

24 def __init__(self): 

25 self.filters = {} 

26 self.tags = {} 

27 

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) 

40 

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 ) 

51 

52 def tag_function(self, func): 

53 self.tags[getattr(func, "_decorated_function", func).__name__] = func 

54 return func 

55 

56 def filter(self, name=None, filter_func=None, **flags): 

57 """ 

58 Register a callable as a template filter. Example: 

59 

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) 

68 

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) 

78 

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 ) 

99 

100 def filter_function(self, func, **flags): 

101 name = getattr(func, "_decorated_function", func).__name__ 

102 return self.filter(name, func, **flags) 

103 

104 def simple_tag(self, func=None, takes_context=None, name=None): 

105 """ 

106 Register a callable as a compiled template tag. Example: 

107 

108 @register.simple_tag 

109 def hello(*args, **kwargs): 

110 return 'world' 

111 """ 

112 

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__ 

124 

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) 

145 

146 self.tag(function_name, compile_func) 

147 return func 

148 

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") 

157 

158 def inclusion_tag(self, filename, func=None, takes_context=None, name=None): 

159 """ 

160 Register a callable as an inclusion tag: 

161 

162 @register.inclusion_tag('results.html') 

163 def show_results(poll): 

164 choices = poll.choice_set.all() 

165 return {'choices': choices} 

166 """ 

167 

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__ 

179 

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 ) 

202 

203 self.tag(function_name, compile_func) 

204 return func 

205 

206 return dec 

207 

208 

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 """ 

215 

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 

221 

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 

228 

229 

230class SimpleNode(TagHelperNode): 

231 child_nodelists = () 

232 

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 

236 

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 

246 

247 

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 

252 

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) 

261 

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) 

281 

282 

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 

369 

370 

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 )