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

1from functools import wraps 

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: 

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 inclusion_tag(self, filename, func=None, takes_context=None, name=None): 

157 """ 

158 Register a callable as an inclusion tag: 

159 

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

161 def show_results(poll): 

162 choices = poll.choice_set.all() 

163 return {'choices': choices} 

164 """ 

165 

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__ 

177 

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 ) 

200 

201 self.tag(function_name, compile_func) 

202 return func 

203 

204 return dec 

205 

206 

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

213 

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 

219 

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 

226 

227 

228class SimpleNode(TagHelperNode): 

229 child_nodelists = () 

230 

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 

234 

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 

244 

245 

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 

250 

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) 

259 

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) 

279 

280 

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 

367 

368 

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 )