# Τεκμηρίωση του Django - 1.10.7

# Πηγαίος κώδικας για το django.template.context

    import warnings
    from contextlib import contextmanager
    from copy import copy

    from django.utils.deprecation import RemovedInDjango20Warning

    # 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(ContextDict, self).__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(object):
        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(BaseContext, self))
            duplicate.dicts = self.dicts[:]
            return duplicate

        def __repr__(self):
            return repr(self.dicts)

        def __iter__(self):
            for d in reversed(self.dicts):
                yield d

        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 __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 has_key(self, key):
            warnings.warn(
                "%s.has_key() is deprecated in favor of the 'in' operator." % self.__class__.__name__,
                RemovedInDjango20Warning
            )
            return key in self

        def __contains__(self, key):
            for d in self.dicts:
                if key in d:
                    return True
            return False

        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):
            """
            Returns 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):
            """
            Returns self.dicts as one dictionary
            """
            flat = {}
            for d in self.dicts:
                flat.update(d)
            return flat

        def __eq__(self, other):
            """
            Compares two contexts by comparing theirs 'dicts' attributes.
            """
            if isinstance(other, BaseContext):
                # because dictionaries can be put in different order
                # we have to flatten them like in templates
                return self.flatten() == other.flatten()

            # if it's not comparable return false
            return False

    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(Context, self).__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(Context, self).__copy__()
            duplicate.render_context = copy(self.render_context)
            return duplicate

        def update(self, other_dict):
            "Pushes 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.
        """
        def __iter__(self):
            for d in self.dicts[-1]:
                yield d

        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]

    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(RequestContext, self).__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(RequestContext, self).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 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