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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. return (
  102. isinstance(other, BaseContext) and
  103. # because dictionaries can be put in different order
  104. # we have to flatten them like in templates
  105. self.flatten() == other.flatten()
  106. )
  107. class Context(BaseContext):
  108. "A stack container for variable context"
  109. def __init__(self, dict_=None, autoescape=True, use_l10n=None, use_tz=None):
  110. self.autoescape = autoescape
  111. self.use_l10n = use_l10n
  112. self.use_tz = use_tz
  113. self.template_name = "unknown"
  114. self.render_context = RenderContext()
  115. # Set to the original template -- as opposed to extended or included
  116. # templates -- during rendering, see bind_template.
  117. self.template = None
  118. super().__init__(dict_)
  119. @contextmanager
  120. def bind_template(self, template):
  121. if self.template is not None:
  122. raise RuntimeError("Context is already bound to a template")
  123. self.template = template
  124. try:
  125. yield
  126. finally:
  127. self.template = None
  128. def __copy__(self):
  129. duplicate = super().__copy__()
  130. duplicate.render_context = copy(self.render_context)
  131. return duplicate
  132. def update(self, other_dict):
  133. "Push other_dict to the stack of dictionaries in the Context"
  134. if not hasattr(other_dict, '__getitem__'):
  135. raise TypeError('other_dict must be a mapping (dictionary-like) object.')
  136. if isinstance(other_dict, BaseContext):
  137. other_dict = other_dict.dicts[1:].pop()
  138. return ContextDict(self, other_dict)
  139. class RenderContext(BaseContext):
  140. """
  141. A stack container for storing Template state.
  142. RenderContext simplifies the implementation of template Nodes by providing a
  143. safe place to store state between invocations of a node's `render` method.
  144. The RenderContext also provides scoping rules that are more sensible for
  145. 'template local' variables. The render context stack is pushed before each
  146. template is rendered, creating a fresh scope with nothing in it. Name
  147. resolution fails if a variable is not found at the top of the RequestContext
  148. stack. Thus, variables are local to a specific template and don't affect the
  149. rendering of other templates as they would if they were stored in the normal
  150. template context.
  151. """
  152. template = None
  153. def __iter__(self):
  154. yield from self.dicts[-1]
  155. def __contains__(self, key):
  156. return key in self.dicts[-1]
  157. def get(self, key, otherwise=None):
  158. return self.dicts[-1].get(key, otherwise)
  159. def __getitem__(self, key):
  160. return self.dicts[-1][key]
  161. @contextmanager
  162. def push_state(self, template, isolated_context=True):
  163. initial = self.template
  164. self.template = template
  165. if isolated_context:
  166. self.push()
  167. try:
  168. yield
  169. finally:
  170. self.template = initial
  171. if isolated_context:
  172. self.pop()
  173. class RequestContext(Context):
  174. """
  175. This subclass of template.Context automatically populates itself using
  176. the processors defined in the engine's configuration.
  177. Additional processors can be specified as a list of callables
  178. using the "processors" keyword argument.
  179. """
  180. def __init__(self, request, dict_=None, processors=None, use_l10n=None, use_tz=None, autoescape=True):
  181. super().__init__(dict_, use_l10n=use_l10n, use_tz=use_tz, autoescape=autoescape)
  182. self.request = request
  183. self._processors = () if processors is None else tuple(processors)
  184. self._processors_index = len(self.dicts)
  185. # placeholder for context processors output
  186. self.update({})
  187. # empty dict for any new modifications
  188. # (so that context processors don't overwrite them)
  189. self.update({})
  190. @contextmanager
  191. def bind_template(self, template):
  192. if self.template is not None:
  193. raise RuntimeError("Context is already bound to a template")
  194. self.template = template
  195. # Set context processors according to the template engine's settings.
  196. processors = (template.engine.template_context_processors +
  197. self._processors)
  198. updates = {}
  199. for processor in processors:
  200. updates.update(processor(self.request))
  201. self.dicts[self._processors_index] = updates
  202. try:
  203. yield
  204. finally:
  205. self.template = None
  206. # Unset context processors.
  207. self.dicts[self._processors_index] = {}
  208. def new(self, values=None):
  209. new_context = super().new(values)
  210. # This is for backwards-compatibility: RequestContexts created via
  211. # Context.new don't include values from context processors.
  212. if hasattr(new_context, '_processors_index'):
  213. del new_context._processors_index
  214. return new_context
  215. def make_context(context, request=None, **kwargs):
  216. """
  217. Create a suitable Context from a plain dict and optionally an HttpRequest.
  218. """
  219. if context is not None and not isinstance(context, dict):
  220. raise TypeError('context must be a dict rather than %s.' % context.__class__.__name__)
  221. if request is None:
  222. context = Context(context, **kwargs)
  223. else:
  224. # The following pattern is required to ensure values from
  225. # context override those from template context processors.
  226. original_context = context
  227. context = RequestContext(request, **kwargs)
  228. if original_context:
  229. context.push(original_context)
  230. return context