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.

context.py 8.8KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. from contextlib import contextmanager
  2. from copy import copy
  3. # Hard-coded processor for easier use of CSRF protection.
  4. _builtin_context_processors = ("django.template.context_processors.csrf",)
  5. class ContextPopException(Exception):
  6. "pop() has been called more times than push()"
  7. pass
  8. class ContextDict(dict):
  9. def __init__(self, context, *args, **kwargs):
  10. super().__init__(*args, **kwargs)
  11. context.dicts.append(self)
  12. self.context = context
  13. def __enter__(self):
  14. return self
  15. def __exit__(self, *args, **kwargs):
  16. self.context.pop()
  17. class BaseContext:
  18. def __init__(self, dict_=None):
  19. self._reset_dicts(dict_)
  20. def _reset_dicts(self, value=None):
  21. builtins = {"True": True, "False": False, "None": None}
  22. self.dicts = [builtins]
  23. if value is not None:
  24. self.dicts.append(value)
  25. def __copy__(self):
  26. duplicate = copy(super())
  27. duplicate.dicts = self.dicts[:]
  28. return duplicate
  29. def __repr__(self):
  30. return repr(self.dicts)
  31. def __iter__(self):
  32. return reversed(self.dicts)
  33. def push(self, *args, **kwargs):
  34. dicts = []
  35. for d in args:
  36. if isinstance(d, BaseContext):
  37. dicts += d.dicts[1:]
  38. else:
  39. dicts.append(d)
  40. return ContextDict(self, *dicts, **kwargs)
  41. def pop(self):
  42. if len(self.dicts) == 1:
  43. raise ContextPopException
  44. return self.dicts.pop()
  45. def __setitem__(self, key, value):
  46. "Set a variable in the current context"
  47. self.dicts[-1][key] = value
  48. def set_upward(self, key, value):
  49. """
  50. Set a variable in one of the higher contexts if it exists there,
  51. otherwise in the current context.
  52. """
  53. context = self.dicts[-1]
  54. for d in reversed(self.dicts):
  55. if key in d:
  56. context = d
  57. break
  58. context[key] = value
  59. def __getitem__(self, key):
  60. "Get a variable's value, starting at the current context and going upward"
  61. for d in reversed(self.dicts):
  62. if key in d:
  63. return d[key]
  64. raise KeyError(key)
  65. def __delitem__(self, key):
  66. "Delete a variable from the current context"
  67. del self.dicts[-1][key]
  68. def __contains__(self, key):
  69. return any(key in d for d in self.dicts)
  70. def get(self, key, otherwise=None):
  71. for d in reversed(self.dicts):
  72. if key in d:
  73. return d[key]
  74. return otherwise
  75. def setdefault(self, key, default=None):
  76. try:
  77. return self[key]
  78. except KeyError:
  79. self[key] = default
  80. return default
  81. def new(self, values=None):
  82. """
  83. Return a new context with the same properties, but with only the
  84. values given in 'values' stored.
  85. """
  86. new_context = copy(self)
  87. new_context._reset_dicts(values)
  88. return new_context
  89. def flatten(self):
  90. """
  91. Return self.dicts as one dictionary.
  92. """
  93. flat = {}
  94. for d in self.dicts:
  95. flat.update(d)
  96. return flat
  97. def __eq__(self, other):
  98. """
  99. Compare two contexts by comparing theirs 'dicts' attributes.
  100. """
  101. if not isinstance(other, BaseContext):
  102. return NotImplemented
  103. # flatten dictionaries because they can be put in a different order.
  104. return self.flatten() == other.flatten()
  105. class Context(BaseContext):
  106. "A stack container for variable context"
  107. def __init__(self, dict_=None, autoescape=True, use_l10n=None, use_tz=None):
  108. self.autoescape = autoescape
  109. self.use_l10n = use_l10n
  110. self.use_tz = use_tz
  111. self.template_name = "unknown"
  112. self.render_context = RenderContext()
  113. # Set to the original template -- as opposed to extended or included
  114. # templates -- during rendering, see bind_template.
  115. self.template = None
  116. super().__init__(dict_)
  117. @contextmanager
  118. def bind_template(self, template):
  119. if self.template is not None:
  120. raise RuntimeError("Context is already bound to a template")
  121. self.template = template
  122. try:
  123. yield
  124. finally:
  125. self.template = None
  126. def __copy__(self):
  127. duplicate = super().__copy__()
  128. duplicate.render_context = copy(self.render_context)
  129. return duplicate
  130. def update(self, other_dict):
  131. "Push other_dict to the stack of dictionaries in the Context"
  132. if not hasattr(other_dict, "__getitem__"):
  133. raise TypeError("other_dict must be a mapping (dictionary-like) object.")
  134. if isinstance(other_dict, BaseContext):
  135. other_dict = other_dict.dicts[1:].pop()
  136. return ContextDict(self, other_dict)
  137. class RenderContext(BaseContext):
  138. """
  139. A stack container for storing Template state.
  140. RenderContext simplifies the implementation of template Nodes by providing a
  141. safe place to store state between invocations of a node's `render` method.
  142. The RenderContext also provides scoping rules that are more sensible for
  143. 'template local' variables. The render context stack is pushed before each
  144. template is rendered, creating a fresh scope with nothing in it. Name
  145. resolution fails if a variable is not found at the top of the RequestContext
  146. stack. Thus, variables are local to a specific template and don't affect the
  147. rendering of other templates as they would if they were stored in the normal
  148. template context.
  149. """
  150. template = None
  151. def __iter__(self):
  152. yield from self.dicts[-1]
  153. def __contains__(self, key):
  154. return key in self.dicts[-1]
  155. def get(self, key, otherwise=None):
  156. return self.dicts[-1].get(key, otherwise)
  157. def __getitem__(self, key):
  158. return self.dicts[-1][key]
  159. @contextmanager
  160. def push_state(self, template, isolated_context=True):
  161. initial = self.template
  162. self.template = template
  163. if isolated_context:
  164. self.push()
  165. try:
  166. yield
  167. finally:
  168. self.template = initial
  169. if isolated_context:
  170. self.pop()
  171. class RequestContext(Context):
  172. """
  173. This subclass of template.Context automatically populates itself using
  174. the processors defined in the engine's configuration.
  175. Additional processors can be specified as a list of callables
  176. using the "processors" keyword argument.
  177. """
  178. def __init__(
  179. self,
  180. request,
  181. dict_=None,
  182. processors=None,
  183. use_l10n=None,
  184. use_tz=None,
  185. autoescape=True,
  186. ):
  187. super().__init__(dict_, use_l10n=use_l10n, use_tz=use_tz, autoescape=autoescape)
  188. self.request = request
  189. self._processors = () if processors is None else tuple(processors)
  190. self._processors_index = len(self.dicts)
  191. # placeholder for context processors output
  192. self.update({})
  193. # empty dict for any new modifications
  194. # (so that context processors don't overwrite them)
  195. self.update({})
  196. @contextmanager
  197. def bind_template(self, template):
  198. if self.template is not None:
  199. raise RuntimeError("Context is already bound to a template")
  200. self.template = template
  201. # Set context processors according to the template engine's settings.
  202. processors = template.engine.template_context_processors + self._processors
  203. updates = {}
  204. for processor in processors:
  205. updates.update(processor(self.request))
  206. self.dicts[self._processors_index] = updates
  207. try:
  208. yield
  209. finally:
  210. self.template = None
  211. # Unset context processors.
  212. self.dicts[self._processors_index] = {}
  213. def new(self, values=None):
  214. new_context = super().new(values)
  215. # This is for backwards-compatibility: RequestContexts created via
  216. # Context.new don't include values from context processors.
  217. if hasattr(new_context, "_processors_index"):
  218. del new_context._processors_index
  219. return new_context
  220. def make_context(context, request=None, **kwargs):
  221. """
  222. Create a suitable Context from a plain dict and optionally an HttpRequest.
  223. """
  224. if context is not None and not isinstance(context, dict):
  225. raise TypeError(
  226. "context must be a dict rather than %s." % context.__class__.__name__
  227. )
  228. if request is None:
  229. context = Context(context, **kwargs)
  230. else:
  231. # The following pattern is required to ensure values from
  232. # context override those from template context processors.
  233. original_context = context
  234. context = RequestContext(request, **kwargs)
  235. if original_context:
  236. context.push(original_context)
  237. return context