From 1bfcc950ab6e5264bbaf2eba0ce3d3e23d671404 Mon Sep 17 00:00:00 2001
From: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Tue, 17 Feb 2015 22:49:59 +0100
Subject: [PATCH] Set context.template instead of context.engine while
 rendering.

This opens more possibilities, like accessing context.template.origin.

It also follows the chain of objects instead of following a shortcut.
---
 django/template/base.py             | 24 +++++++++++------------
 django/template/context.py          | 30 +++++++++++++++++------------
 django/template/defaulttags.py      |  6 +++---
 django/template/loader_tags.py      |  6 +++---
 django/templatetags/i18n.py         |  2 +-
 docs/howto/custom-template-tags.txt | 22 +++++++++++----------
 docs/ref/templates/upgrading.txt    |  4 ++--
 tests/template_tests/tests.py       |  7 ++-----
 8 files changed, 53 insertions(+), 48 deletions(-)

diff --git a/django/template/base.py b/django/template/base.py
index 16b76f51fd..325cd7e560 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -204,19 +204,19 @@ class Template(object):
 
     def render(self, context):
         "Display stage -- can be called many times"
-        # Set engine attribute here to avoid changing the signature of either
-        # Context.__init__ or Node.render. The engine is set only on the first
-        # call to render. Further calls e.g. for includes don't override it.
-        toplevel_render = context.engine is None
+        # 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.engine = self.engine
+            context.template = self
         context.render_context.push()
         try:
             return self._render(context)
         finally:
             context.render_context.pop()
             if toplevel_render:
-                context.engine = None
+                context.template = None
 
 
 class Token(object):
@@ -655,7 +655,7 @@ class FilterExpression(object):
                 if ignore_failures:
                     obj = None
                 else:
-                    string_if_invalid = context.engine.string_if_invalid
+                    string_if_invalid = context.template.engine.string_if_invalid
                     if string_if_invalid:
                         if '%s' in string_if_invalid:
                             return string_if_invalid % self.var
@@ -847,7 +847,7 @@ class Variable(object):
                     if getattr(current, 'do_not_call_in_templates', False):
                         pass
                     elif getattr(current, 'alters_data', False):
-                        current = context.engine.string_if_invalid
+                        current = context.template.engine.string_if_invalid
                     else:
                         try:  # method call (assuming no args required)
                             current = current()
@@ -855,12 +855,12 @@ class Variable(object):
                             try:
                                 getcallargs(current)
                             except TypeError:  # arguments *were* required
-                                current = context.engine.string_if_invalid  # invalid method call
+                                current = context.template.engine.string_if_invalid  # invalid method call
                             else:
                                 raise
         except Exception as e:
             if getattr(e, 'silent_variable_failure', False):
-                current = context.engine.string_if_invalid
+                current = context.template.engine.string_if_invalid
             else:
                 raise
 
@@ -1257,9 +1257,9 @@ class Library(object):
                         elif isinstance(getattr(file_name, 'template', None), Template):
                             t = file_name.template
                         elif not isinstance(file_name, six.string_types) and is_iterable(file_name):
-                            t = context.engine.select_template(file_name)
+                            t = context.template.engine.select_template(file_name)
                         else:
-                            t = context.engine.get_template(file_name)
+                            t = context.template.engine.get_template(file_name)
                         self.nodelist = t.nodelist
                     new_context = context.new(_dict)
                     # Copy across the CSRF token, if present, because
diff --git a/django/template/context.py b/django/template/context.py
index 01d21a159a..b4983d1909 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -123,7 +123,7 @@ class Context(BaseContext):
     "A stack container for variable context"
     def __init__(self, dict_=None, autoescape=True,
             current_app=_current_app_undefined,
-            use_l10n=None, use_tz=None, engine=None):
+            use_l10n=None, use_tz=None):
         if current_app is not _current_app_undefined:
             warnings.warn(
                 "The current_app argument of Context is deprecated. Use "
@@ -133,8 +133,10 @@ class Context(BaseContext):
         self._current_app = current_app
         self.use_l10n = use_l10n
         self.use_tz = use_tz
-        self.engine = engine
         self.render_context = RenderContext()
+        # Set to the original template during rendering -- as opposed to
+        # extended or included templates
+        self.template = None
         super(Context, self).__init__(dict_)
 
     @property
@@ -192,11 +194,11 @@ class RequestContext(Context):
     """
     def __init__(self, request, dict_=None, processors=None,
             current_app=_current_app_undefined,
-            use_l10n=None, use_tz=None, engine=None):
+            use_l10n=None, use_tz=None):
         # current_app isn't passed here to avoid triggering the deprecation
         # warning in Context.__init__.
         super(RequestContext, self).__init__(
-            dict_, use_l10n=use_l10n, use_tz=use_tz, engine=engine)
+            dict_, use_l10n=use_l10n, use_tz=use_tz)
         if current_app is not _current_app_undefined:
             warnings.warn(
                 "The current_app argument of RequestContext is deprecated. "
@@ -207,23 +209,27 @@ class RequestContext(Context):
         self._processors = () if processors is None else tuple(processors)
         self._processors_index = len(self.dicts)
         self.update({})         # placeholder for context processors output
-        self.engine = engine    # re-run the setter in case engine is not None
 
     @property
-    def engine(self):
-        return self._engine
+    def template(self):
+        return self._template
 
-    @engine.setter
-    def engine(self, engine):
-        self._engine = engine
+    @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 engine is None:
+            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 engine.template_context_processors + self._processors:
+                for processor in processors:
                     updates.update(processor(self.request))
                 self.dicts[self._processors_index] = updates
 
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 0dc6735ad6..fc8baf9611 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -211,7 +211,7 @@ class ForNode(Node):
                     context[self.loopvars[0]] = item
                 # In debug mode provide the source of the node which raised
                 # the exception
-                if context.engine.debug:
+                if context.template.engine.debug:
                     for node in self.nodelist_loop:
                         try:
                             nodelist.append(node.render(context))
@@ -392,7 +392,7 @@ class SsiNode(Node):
     def render(self, context):
         filepath = self.filepath.resolve(context)
 
-        if not include_is_allowed(filepath, context.engine.allowed_include_roots):
+        if not include_is_allowed(filepath, context.template.engine.allowed_include_roots):
             if settings.DEBUG:
                 return "[Didn't have permission to include file]"
             else:
@@ -404,7 +404,7 @@ class SsiNode(Node):
             output = ''
         if self.parsed:
             try:
-                t = Template(output, name=filepath, engine=context.engine)
+                t = Template(output, name=filepath, engine=context.template.engine)
                 return t.render(context)
             except TemplateSyntaxError as e:
                 if settings.DEBUG:
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
index e6635258a7..398914b6b7 100644
--- a/django/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -107,7 +107,7 @@ class ExtendsNode(Node):
         if isinstance(getattr(parent, 'template', None), Template):
             # parent is a django.template.backends.django.Template
             return parent.template
-        return context.engine.get_template(parent)
+        return context.template.engine.get_template(parent)
 
     def render(self, context):
         compiled_parent = self.get_parent(context)
@@ -148,7 +148,7 @@ class IncludeNode(Node):
             # Does this quack like a Template?
             if not callable(getattr(template, 'render', None)):
                 # If not, we'll try get_template
-                template = context.engine.get_template(template)
+                template = context.template.engine.get_template(template)
             values = {
                 name: var.resolve(context)
                 for name, var in six.iteritems(self.extra_context)
@@ -158,7 +158,7 @@ class IncludeNode(Node):
             with context.push(**values):
                 return template.render(context)
         except Exception:
-            if context.engine.debug:
+            if context.template.engine.debug:
                 raise
             return ''
 
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
index 2a2b433508..bb81c56fb9 100644
--- a/django/templatetags/i18n.py
+++ b/django/templatetags/i18n.py
@@ -149,7 +149,7 @@ class BlockTranslateNode(Node):
                 result = translation.pgettext(message_context, singular)
             else:
                 result = translation.ugettext(singular)
-        default_value = context.engine.string_if_invalid
+        default_value = context.template.engine.string_if_invalid
 
         def render_value(key):
             if key in context:
diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt
index 463c3bbfe5..bf385669c5 100644
--- a/docs/howto/custom-template-tags.txt
+++ b/docs/howto/custom-template-tags.txt
@@ -756,10 +756,11 @@ Notes:
 * The ``render()`` method is where the work actually happens.
 
 * ``render()`` should generally fail silently, particularly in a production
-  environment. In some cases however, particularly if ``context.engine.debug``
-  is ``True``, this method may raise an exception to make debugging easier.
-  For example, several core tags raise ``django.template.TemplateSyntaxError``
-  if they receive the wrong number or type of arguments.
+  environment. In some cases however, particularly if
+  ``context.template.engine.debug`` is ``True``, this method may raise an
+  exception to make debugging easier. For example, several core tags raise
+  ``django.template.TemplateSyntaxError`` if they receive the wrong number or
+  type of arguments.
 
 Ultimately, this decoupling of compilation and rendering results in an
 efficient template system, because a template can render multiple contexts
@@ -795,16 +796,17 @@ This is not a very common situation, but it's useful if you're rendering a
 template yourself. For example::
 
     def render(self, context):
-        t = context.engine.get_template('small_fragment.html')
+        t = context.template.engine.get_template('small_fragment.html')
         return t.render(Context({'var': obj}, autoescape=context.autoescape))
 
 .. versionchanged:: 1.8
 
-    The ``engine`` attribute of ``Context`` objects was added in Django 1.8.
-    :meth:`context.engine.get_template <django.template.Engine.get_template>`
-    must be used instead of :func:`django.template.loader.get_template`
-    because the latter now returns a wrapper whose ``render`` method doesn't
-    accept a :class:`~django.template.Context`.
+    The ``template`` attribute of ``Context`` objects was added in Django 1.8.
+    :meth:`context.template.engine.get_template
+    <django.template.Engine.get_template>` must be used instead of
+    :func:`django.template.loader.get_template` because the latter now returns
+    a wrapper whose ``render`` method doesn't accept a
+    :class:`~django.template.Context`.
 
 If we had neglected to pass in the current ``context.autoescape`` value to our
 new ``Context`` in this example, the results would have *always* been
diff --git a/docs/ref/templates/upgrading.txt b/docs/ref/templates/upgrading.txt
index b2ef23c4b8..6fdab53c40 100644
--- a/docs/ref/templates/upgrading.txt
+++ b/docs/ref/templates/upgrading.txt
@@ -162,7 +162,7 @@ instance in the ``render()`` method of a template tag, you can use the current
 
 You can write::
 
-    template = context.engine.get_template('included.html')
+    template = context.template.engine.get_template('included.html')
 
 This will load the template with the current engine without triggering the
 multiple template engines machinery, which is usually the desired behavior.
@@ -201,7 +201,7 @@ APIs. The multiple template engines machinery isn't involved here.
 Finally, if you have access to the current context, you can use the same trick
 as above::
 
-    template = context.engine.from_string(template_code)
+    template = context.template.engine.from_string(template_code)
 
 ``Template()``
 ==============
diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
index cf37e1868b..77115d061a 100644
--- a/tests/template_tests/tests.py
+++ b/tests/template_tests/tests.py
@@ -516,9 +516,6 @@ class RequestContextTests(unittest.TestCase):
         self.assertEqual(len(ctx.dicts), 3)
 
     def test_context_comparable(self):
-        # Create an engine without any context processors.
-        engine = Engine()
-
         test_data = {'x': 'y', 'v': 'z', 'd': {'o': object, 'a': 'b'}}
 
         # test comparing RequestContext to prevent problems if somebody
@@ -526,8 +523,8 @@ class RequestContextTests(unittest.TestCase):
         request = RequestFactory().get('/')
 
         self.assertEqual(
-            RequestContext(request, dict_=test_data, engine=engine),
-            RequestContext(request, dict_=test_data, engine=engine))
+            RequestContext(request, dict_=test_data),
+            RequestContext(request, dict_=test_data))
 
 
 @ignore_warnings(category=RemovedInDjango20Warning)