123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- from contextlib import contextmanager
- from copy import copy
-
- # Hard-coded processor for easier use of CSRF protection.
- _builtin_context_processors = ("django.template.context_processors.csrf",)
-
-
- class ContextPopException(Exception):
- "pop() has been called more times than push()"
- pass
-
-
- class ContextDict(dict):
- def __init__(self, context, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- context.dicts.append(self)
- self.context = context
-
- def __enter__(self):
- return self
-
- def __exit__(self, *args, **kwargs):
- self.context.pop()
-
-
- class BaseContext:
- def __init__(self, dict_=None):
- self._reset_dicts(dict_)
-
- def _reset_dicts(self, value=None):
- builtins = {"True": True, "False": False, "None": None}
- self.dicts = [builtins]
- if value is not None:
- self.dicts.append(value)
-
- def __copy__(self):
- duplicate = copy(super())
- duplicate.dicts = self.dicts[:]
- return duplicate
-
- def __repr__(self):
- return repr(self.dicts)
-
- def __iter__(self):
- return reversed(self.dicts)
-
- def push(self, *args, **kwargs):
- dicts = []
- for d in args:
- if isinstance(d, BaseContext):
- dicts += d.dicts[1:]
- else:
- dicts.append(d)
- return ContextDict(self, *dicts, **kwargs)
-
- def pop(self):
- if len(self.dicts) == 1:
- raise ContextPopException
- return self.dicts.pop()
-
- def __setitem__(self, key, value):
- "Set a variable in the current context"
- self.dicts[-1][key] = value
-
- def set_upward(self, key, value):
- """
- Set a variable in one of the higher contexts if it exists there,
- otherwise in the current context.
- """
- context = self.dicts[-1]
- for d in reversed(self.dicts):
- if key in d:
- context = d
- break
- context[key] = value
-
- def __getitem__(self, key):
- "Get a variable's value, starting at the current context and going upward"
- for d in reversed(self.dicts):
- if key in d:
- return d[key]
- raise KeyError(key)
-
- def __delitem__(self, key):
- "Delete a variable from the current context"
- del self.dicts[-1][key]
-
- def __contains__(self, key):
- return any(key in d for d in self.dicts)
-
- def get(self, key, otherwise=None):
- for d in reversed(self.dicts):
- if key in d:
- return d[key]
- return otherwise
-
- def setdefault(self, key, default=None):
- try:
- return self[key]
- except KeyError:
- self[key] = default
- return default
-
- def new(self, values=None):
- """
- Return a new context with the same properties, but with only the
- values given in 'values' stored.
- """
- new_context = copy(self)
- new_context._reset_dicts(values)
- return new_context
-
- def flatten(self):
- """
- Return self.dicts as one dictionary.
- """
- flat = {}
- for d in self.dicts:
- flat.update(d)
- return flat
-
- def __eq__(self, other):
- """
- Compare two contexts by comparing theirs 'dicts' attributes.
- """
- if not isinstance(other, BaseContext):
- return NotImplemented
- # flatten dictionaries because they can be put in a different order.
- return self.flatten() == other.flatten()
-
-
- class Context(BaseContext):
- "A stack container for variable context"
-
- def __init__(self, dict_=None, autoescape=True, use_l10n=None, use_tz=None):
- self.autoescape = autoescape
- self.use_l10n = use_l10n
- self.use_tz = use_tz
- self.template_name = "unknown"
- self.render_context = RenderContext()
- # Set to the original template -- as opposed to extended or included
- # templates -- during rendering, see bind_template.
- self.template = None
- super().__init__(dict_)
-
- @contextmanager
- def bind_template(self, template):
- if self.template is not None:
- raise RuntimeError("Context is already bound to a template")
- self.template = template
- try:
- yield
- finally:
- self.template = None
-
- def __copy__(self):
- duplicate = super().__copy__()
- duplicate.render_context = copy(self.render_context)
- return duplicate
-
- def update(self, other_dict):
- "Push other_dict to the stack of dictionaries in the Context"
- if not hasattr(other_dict, "__getitem__"):
- raise TypeError("other_dict must be a mapping (dictionary-like) object.")
- if isinstance(other_dict, BaseContext):
- other_dict = other_dict.dicts[1:].pop()
- return ContextDict(self, other_dict)
-
-
- class RenderContext(BaseContext):
- """
- A stack container for storing Template state.
-
- RenderContext simplifies the implementation of template Nodes by providing a
- safe place to store state between invocations of a node's `render` method.
-
- The RenderContext also provides scoping rules that are more sensible for
- 'template local' variables. The render context stack is pushed before each
- template is rendered, creating a fresh scope with nothing in it. Name
- resolution fails if a variable is not found at the top of the RequestContext
- stack. Thus, variables are local to a specific template and don't affect the
- rendering of other templates as they would if they were stored in the normal
- template context.
- """
-
- template = None
-
- def __iter__(self):
- yield from self.dicts[-1]
-
- def __contains__(self, key):
- return key in self.dicts[-1]
-
- def get(self, key, otherwise=None):
- return self.dicts[-1].get(key, otherwise)
-
- def __getitem__(self, key):
- return self.dicts[-1][key]
-
- @contextmanager
- def push_state(self, template, isolated_context=True):
- initial = self.template
- self.template = template
- if isolated_context:
- self.push()
- try:
- yield
- finally:
- self.template = initial
- if isolated_context:
- self.pop()
-
-
- class RequestContext(Context):
- """
- This subclass of template.Context automatically populates itself using
- the processors defined in the engine's configuration.
- Additional processors can be specified as a list of callables
- using the "processors" keyword argument.
- """
-
- def __init__(
- self,
- request,
- dict_=None,
- processors=None,
- use_l10n=None,
- use_tz=None,
- autoescape=True,
- ):
- super().__init__(dict_, use_l10n=use_l10n, use_tz=use_tz, autoescape=autoescape)
- self.request = request
- self._processors = () if processors is None else tuple(processors)
- self._processors_index = len(self.dicts)
-
- # placeholder for context processors output
- self.update({})
-
- # empty dict for any new modifications
- # (so that context processors don't overwrite them)
- self.update({})
-
- @contextmanager
- def bind_template(self, template):
- if self.template is not None:
- raise RuntimeError("Context is already bound to a template")
-
- self.template = template
- # Set context processors according to the template engine's settings.
- processors = template.engine.template_context_processors + self._processors
- updates = {}
- for processor in processors:
- updates.update(processor(self.request))
- self.dicts[self._processors_index] = updates
-
- try:
- yield
- finally:
- self.template = None
- # Unset context processors.
- self.dicts[self._processors_index] = {}
-
- def new(self, values=None):
- new_context = super().new(values)
- # This is for backwards-compatibility: RequestContexts created via
- # Context.new don't include values from context processors.
- if hasattr(new_context, "_processors_index"):
- del new_context._processors_index
- return new_context
-
-
- def make_context(context, request=None, **kwargs):
- """
- Create a suitable Context from a plain dict and optionally an HttpRequest.
- """
- if context is not None and not isinstance(context, dict):
- raise TypeError(
- "context must be a dict rather than %s." % context.__class__.__name__
- )
- if request is None:
- context = Context(context, **kwargs)
- else:
- # The following pattern is required to ensure values from
- # context override those from template context processors.
- original_context = context
- context = RequestContext(request, **kwargs)
- if original_context:
- context.push(original_context)
- return context
|