From e4e140c49b1bd3dc8ae11900a0089ec908483cf0 Mon Sep 17 00:00:00 2001
From: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Thu, 19 Feb 2015 23:20:27 +0100
Subject: [PATCH] [1.8.x] Removed a non-obvious side-effect of assigning
 Context.template.

Explicit is better than implicit.

Backport of 51b606f from master
---
 django/template/base.py    | 14 ++++------
 django/template/context.py | 55 ++++++++++++++++++++++----------------
 2 files changed, 37 insertions(+), 32 deletions(-)

diff --git a/django/template/base.py b/django/template/base.py
index 76f77fc793..0c57050313 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -202,19 +202,15 @@ class Template(object):
 
     def render(self, context):
         "Display stage -- can be called many times"
-        # Set context.template to the original template -- as opposed to
-        # extended or included templates -- during rendering. This may be
-        # used for accessing context.template.engine.
-        toplevel_render = context.template is None
-        if toplevel_render:
-            context.template = self
         context.render_context.push()
         try:
-            return self._render(context)
+            if context.template is None:
+                with context.bind_template(self):
+                    return self._render(context)
+            else:
+                return self._render(context)
         finally:
             context.render_context.pop()
-            if toplevel_render:
-                context.template = None
 
 
 class Token(object):
diff --git a/django/template/context.py b/django/template/context.py
index b4983d1909..af4fb8853b 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -1,4 +1,5 @@
 import warnings
+from contextlib import contextmanager
 from copy import copy
 
 from django.utils.deprecation import RemovedInDjango20Warning
@@ -134,8 +135,8 @@ class Context(BaseContext):
         self.use_l10n = use_l10n
         self.use_tz = use_tz
         self.render_context = RenderContext()
-        # Set to the original template during rendering -- as opposed to
-        # extended or included templates
+        # 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_)
 
@@ -143,6 +144,16 @@ class Context(BaseContext):
     def current_app(self):
         return None if self._current_app is _current_app_undefined else self._current_app
 
+    @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)
@@ -210,28 +221,26 @@ class RequestContext(Context):
         self._processors_index = len(self.dicts)
         self.update({})         # placeholder for context processors output
 
-    @property
-    def template(self):
-        return self._template
+    @contextmanager
+    def bind_template(self, template):
+        if self.template is not None:
+            raise RuntimeError("Context is already bound to a template")
 
-    @template.setter
-    def template(self, template):
-        # Execute context processors when Template.render(self, context) sets
-        # context.template = self. Until then, since the context isn't tied to
-        # an engine, it has no way to know which context processors to apply.
-        self._template = template
-        if hasattr(self, '_processors_index'):
-            if template is None:
-                # Unset context processors.
-                self.dicts[self._processors_index] = {}
-            else:
-                # Set context processors for this engine.
-                processors = (template.engine.template_context_processors +
-                              self._processors)
-                updates = {}
-                for processor in processors:
-                    updates.update(processor(self.request))
-                self.dicts[self._processors_index] = updates
+        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)