Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/template/library.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

216 statements  

1from collections.abc import Iterable 

2from functools import wraps 

3from importlib import import_module 

4from inspect import getfullargspec, unwrap 

5 

6from django.utils.html import conditional_escape 

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: 

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: 

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[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: 

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 ) 

98 

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

100 return self.filter(func.__name__, func, **flags) 

101 

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

103 """ 

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

105 

106 @register.simple_tag 

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

108 return 'world' 

109 """ 

110 

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__ 

122 

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) 

143 

144 self.tag(function_name, compile_func) 

145 return func 

146 

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

155 

156 def simple_block_tag(self, func=None, takes_context=None, name=None, end_name=None): 

157 """ 

158 Register a callable as a compiled block template tag. Example: 

159 

160 @register.simple_block_tag 

161 def hello(content): 

162 return 'world' 

163 """ 

164 

165 def dec(func): 

166 nonlocal end_name 

167 

168 ( 

169 params, 

170 varargs, 

171 varkw, 

172 defaults, 

173 kwonly, 

174 kwonly_defaults, 

175 _, 

176 ) = getfullargspec(unwrap(func)) 

177 function_name = name or func.__name__ 

178 

179 if end_name is None: 

180 end_name = f"end{function_name}" 

181 

182 @wraps(func) 

183 def compile_func(parser, token): 

184 tag_params = params.copy() 

185 

186 if takes_context: 

187 if len(tag_params) >= 2 and tag_params[1] == "content": 

188 del tag_params[1] 

189 else: 

190 raise TemplateSyntaxError( 

191 f"{function_name!r} is decorated with takes_context=True so" 

192 " it must have a first argument of 'context' and a second " 

193 "argument of 'content'" 

194 ) 

195 elif tag_params and tag_params[0] == "content": 

196 del tag_params[0] 

197 else: 

198 raise TemplateSyntaxError( 

199 f"'{function_name}' must have a first argument of 'content'" 

200 ) 

201 

202 bits = token.split_contents()[1:] 

203 target_var = None 

204 if len(bits) >= 2 and bits[-2] == "as": 

205 target_var = bits[-1] 

206 bits = bits[:-2] 

207 

208 nodelist = parser.parse((end_name,)) 

209 parser.delete_first_token() 

210 

211 args, kwargs = parse_bits( 

212 parser, 

213 bits, 

214 tag_params, 

215 varargs, 

216 varkw, 

217 defaults, 

218 kwonly, 

219 kwonly_defaults, 

220 takes_context, 

221 function_name, 

222 ) 

223 

224 return SimpleBlockNode( 

225 nodelist, func, takes_context, args, kwargs, target_var 

226 ) 

227 

228 self.tag(function_name, compile_func) 

229 return func 

230 

231 if func is None: 

232 # @register.simple_block_tag(...) 

233 return dec 

234 elif callable(func): 

235 # @register.simple_block_tag 

236 return dec(func) 

237 else: 

238 raise ValueError("Invalid arguments provided to simple_block_tag") 

239 

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

241 """ 

242 Register a callable as an inclusion tag: 

243 

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

245 def show_results(poll): 

246 choices = poll.choice_set.all() 

247 return {'choices': choices} 

248 """ 

249 

250 def dec(func): 

251 ( 

252 params, 

253 varargs, 

254 varkw, 

255 defaults, 

256 kwonly, 

257 kwonly_defaults, 

258 _, 

259 ) = getfullargspec(unwrap(func)) 

260 function_name = name or func.__name__ 

261 

262 @wraps(func) 

263 def compile_func(parser, token): 

264 bits = token.split_contents()[1:] 

265 args, kwargs = parse_bits( 

266 parser, 

267 bits, 

268 params, 

269 varargs, 

270 varkw, 

271 defaults, 

272 kwonly, 

273 kwonly_defaults, 

274 takes_context, 

275 function_name, 

276 ) 

277 return InclusionNode( 

278 func, 

279 takes_context, 

280 args, 

281 kwargs, 

282 filename, 

283 ) 

284 

285 self.tag(function_name, compile_func) 

286 return func 

287 

288 return dec 

289 

290 

291class TagHelperNode(Node): 

292 """ 

293 Base class for tag helper nodes such as SimpleNode and InclusionNode. 

294 Manages the positional and keyword arguments to be passed to the decorated 

295 function. 

296 """ 

297 

298 def __init__(self, func, takes_context, args, kwargs): 

299 self.func = func 

300 self.takes_context = takes_context 

301 self.args = args 

302 self.kwargs = kwargs 

303 

304 def get_resolved_arguments(self, context): 

305 resolved_args = [var.resolve(context) for var in self.args] 

306 if self.takes_context: 

307 resolved_args = [context] + resolved_args 

308 resolved_kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()} 

309 return resolved_args, resolved_kwargs 

310 

311 

312class SimpleNode(TagHelperNode): 

313 child_nodelists = () 

314 

315 def __init__(self, func, takes_context, args, kwargs, target_var): 

316 super().__init__(func, takes_context, args, kwargs) 

317 self.target_var = target_var 

318 

319 def render(self, context): 

320 resolved_args, resolved_kwargs = self.get_resolved_arguments(context) 

321 output = self.func(*resolved_args, **resolved_kwargs) 

322 if self.target_var is not None: 

323 context[self.target_var] = output 

324 return "" 

325 if context.autoescape: 

326 output = conditional_escape(output) 

327 return output 

328 

329 

330class SimpleBlockNode(SimpleNode): 

331 def __init__(self, nodelist, *args, **kwargs): 

332 super().__init__(*args, **kwargs) 

333 self.nodelist = nodelist 

334 

335 def get_resolved_arguments(self, context): 

336 resolved_args, resolved_kwargs = super().get_resolved_arguments(context) 

337 

338 # Restore the "content" argument. 

339 # It will move depending on whether takes_context was passed. 

340 resolved_args.insert( 

341 1 if self.takes_context else 0, self.nodelist.render(context) 

342 ) 

343 

344 return resolved_args, resolved_kwargs 

345 

346 

347class InclusionNode(TagHelperNode): 

348 def __init__(self, func, takes_context, args, kwargs, filename): 

349 super().__init__(func, takes_context, args, kwargs) 

350 self.filename = filename 

351 

352 def render(self, context): 

353 """ 

354 Render the specified template and context. Cache the template object 

355 in render_context to avoid reparsing and loading when used in a for 

356 loop. 

357 """ 

358 resolved_args, resolved_kwargs = self.get_resolved_arguments(context) 

359 _dict = self.func(*resolved_args, **resolved_kwargs) 

360 

361 t = context.render_context.get(self) 

362 if t is None: 

363 if isinstance(self.filename, Template): 

364 t = self.filename 

365 elif isinstance(getattr(self.filename, "template", None), Template): 

366 t = self.filename.template 

367 elif not isinstance(self.filename, str) and isinstance( 

368 self.filename, Iterable 

369 ): 

370 t = context.template.engine.select_template(self.filename) 

371 else: 

372 t = context.template.engine.get_template(self.filename) 

373 context.render_context[self] = t 

374 new_context = context.new(_dict) 

375 # Copy across the CSRF token, if present, because inclusion tags are 

376 # often used for forms, and we need instructions for using CSRF 

377 # protection to be as simple as possible. 

378 csrf_token = context.get("csrf_token") 

379 if csrf_token is not None: 

380 new_context["csrf_token"] = csrf_token 

381 return t.render(new_context) 

382 

383 

384def parse_bits( 

385 parser, 

386 bits, 

387 params, 

388 varargs, 

389 varkw, 

390 defaults, 

391 kwonly, 

392 kwonly_defaults, 

393 takes_context, 

394 name, 

395): 

396 """ 

397 Parse bits for template tag helpers simple_tag and inclusion_tag, in 

398 particular by detecting syntax errors and by extracting positional and 

399 keyword arguments. 

400 """ 

401 if takes_context: 

402 if params and params[0] == "context": 

403 params = params[1:] 

404 else: 

405 raise TemplateSyntaxError( 

406 "'%s' is decorated with takes_context=True so it must " 

407 "have a first argument of 'context'" % name 

408 ) 

409 args = [] 

410 kwargs = {} 

411 unhandled_params = list(params) 

412 unhandled_kwargs = [ 

413 kwarg for kwarg in kwonly if not kwonly_defaults or kwarg not in kwonly_defaults 

414 ] 

415 for bit in bits: 

416 # First we try to extract a potential kwarg from the bit 

417 kwarg = token_kwargs([bit], parser) 

418 if kwarg: 

419 # The kwarg was successfully extracted 

420 param, value = kwarg.popitem() 

421 if param not in params and param not in kwonly and varkw is None: 

422 # An unexpected keyword argument was supplied 

423 raise TemplateSyntaxError( 

424 "'%s' received unexpected keyword argument '%s'" % (name, param) 

425 ) 

426 elif param in kwargs: 

427 # The keyword argument has already been supplied once 

428 raise TemplateSyntaxError( 

429 "'%s' received multiple values for keyword argument '%s'" 

430 % (name, param) 

431 ) 

432 else: 

433 # All good, record the keyword argument 

434 kwargs[str(param)] = value 

435 if param in unhandled_params: 

436 # If using the keyword syntax for a positional arg, then 

437 # consume it. 

438 unhandled_params.remove(param) 

439 elif param in unhandled_kwargs: 

440 # Same for keyword-only arguments 

441 unhandled_kwargs.remove(param) 

442 else: 

443 if kwargs: 

444 raise TemplateSyntaxError( 

445 "'%s' received some positional argument(s) after some " 

446 "keyword argument(s)" % name 

447 ) 

448 else: 

449 # Record the positional argument 

450 args.append(parser.compile_filter(bit)) 

451 try: 

452 # Consume from the list of expected positional arguments 

453 unhandled_params.pop(0) 

454 except IndexError: 

455 if varargs is None: 

456 raise TemplateSyntaxError( 

457 "'%s' received too many positional arguments" % name 

458 ) 

459 if defaults is not None: 

460 # Consider the last n params handled, where n is the 

461 # number of defaults. 

462 unhandled_params = unhandled_params[: -len(defaults)] 

463 if unhandled_params or unhandled_kwargs: 

464 # Some positional arguments were not supplied 

465 raise TemplateSyntaxError( 

466 "'%s' did not receive value(s) for the argument(s): %s" 

467 % (name, ", ".join("'%s'" % p for p in unhandled_params + unhandled_kwargs)) 

468 ) 

469 return args, kwargs 

470 

471 

472def import_library(name): 

473 """ 

474 Load a Library object from a template tag module. 

475 """ 

476 try: 

477 module = import_module(name) 

478 except ImportError as e: 

479 raise InvalidTemplateLibrary( 

480 "Invalid template library specified. ImportError raised when " 

481 "trying to load '%s': %s" % (name, e) 

482 ) 

483 try: 

484 return module.register 

485 except AttributeError: 

486 raise InvalidTemplateLibrary( 

487 "Module %s does not have a variable named 'register'" % name, 

488 )