Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

library.py 13KB

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