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.
This commit is contained in:
Aymeric Augustin 2015-02-17 22:49:59 +01:00
parent efb1f99f94
commit 1bfcc950ab
8 changed files with 53 additions and 48 deletions

View File

@ -204,19 +204,19 @@ class Template(object):
def render(self, context): def render(self, context):
"Display stage -- can be called many times" "Display stage -- can be called many times"
# Set engine attribute here to avoid changing the signature of either # Set context.template to the original template -- as opposed to
# Context.__init__ or Node.render. The engine is set only on the first # extended or included templates -- during rendering. This may be
# call to render. Further calls e.g. for includes don't override it. # used for accessing context.template.engine.
toplevel_render = context.engine is None toplevel_render = context.template is None
if toplevel_render: if toplevel_render:
context.engine = self.engine context.template = self
context.render_context.push() context.render_context.push()
try: try:
return self._render(context) return self._render(context)
finally: finally:
context.render_context.pop() context.render_context.pop()
if toplevel_render: if toplevel_render:
context.engine = None context.template = None
class Token(object): class Token(object):
@ -655,7 +655,7 @@ class FilterExpression(object):
if ignore_failures: if ignore_failures:
obj = None obj = None
else: else:
string_if_invalid = context.engine.string_if_invalid string_if_invalid = context.template.engine.string_if_invalid
if string_if_invalid: if string_if_invalid:
if '%s' in string_if_invalid: if '%s' in string_if_invalid:
return string_if_invalid % self.var return string_if_invalid % self.var
@ -847,7 +847,7 @@ class Variable(object):
if getattr(current, 'do_not_call_in_templates', False): if getattr(current, 'do_not_call_in_templates', False):
pass pass
elif getattr(current, 'alters_data', False): elif getattr(current, 'alters_data', False):
current = context.engine.string_if_invalid current = context.template.engine.string_if_invalid
else: else:
try: # method call (assuming no args required) try: # method call (assuming no args required)
current = current() current = current()
@ -855,12 +855,12 @@ class Variable(object):
try: try:
getcallargs(current) getcallargs(current)
except TypeError: # arguments *were* required 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: else:
raise raise
except Exception as e: except Exception as e:
if getattr(e, 'silent_variable_failure', False): if getattr(e, 'silent_variable_failure', False):
current = context.engine.string_if_invalid current = context.template.engine.string_if_invalid
else: else:
raise raise
@ -1257,9 +1257,9 @@ class Library(object):
elif isinstance(getattr(file_name, 'template', None), Template): elif isinstance(getattr(file_name, 'template', None), Template):
t = file_name.template t = file_name.template
elif not isinstance(file_name, six.string_types) and is_iterable(file_name): 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: else:
t = context.engine.get_template(file_name) t = context.template.engine.get_template(file_name)
self.nodelist = t.nodelist self.nodelist = t.nodelist
new_context = context.new(_dict) new_context = context.new(_dict)
# Copy across the CSRF token, if present, because # Copy across the CSRF token, if present, because

View File

@ -123,7 +123,7 @@ class Context(BaseContext):
"A stack container for variable context" "A stack container for variable context"
def __init__(self, dict_=None, autoescape=True, def __init__(self, dict_=None, autoescape=True,
current_app=_current_app_undefined, 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: if current_app is not _current_app_undefined:
warnings.warn( warnings.warn(
"The current_app argument of Context is deprecated. Use " "The current_app argument of Context is deprecated. Use "
@ -133,8 +133,10 @@ class Context(BaseContext):
self._current_app = current_app self._current_app = current_app
self.use_l10n = use_l10n self.use_l10n = use_l10n
self.use_tz = use_tz self.use_tz = use_tz
self.engine = engine
self.render_context = RenderContext() 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_) super(Context, self).__init__(dict_)
@property @property
@ -192,11 +194,11 @@ class RequestContext(Context):
""" """
def __init__(self, request, dict_=None, processors=None, def __init__(self, request, dict_=None, processors=None,
current_app=_current_app_undefined, 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 # current_app isn't passed here to avoid triggering the deprecation
# warning in Context.__init__. # warning in Context.__init__.
super(RequestContext, self).__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: if current_app is not _current_app_undefined:
warnings.warn( warnings.warn(
"The current_app argument of RequestContext is deprecated. " "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 = () if processors is None else tuple(processors)
self._processors_index = len(self.dicts) self._processors_index = len(self.dicts)
self.update({}) # placeholder for context processors output self.update({}) # placeholder for context processors output
self.engine = engine # re-run the setter in case engine is not None
@property @property
def engine(self): def template(self):
return self._engine return self._template
@engine.setter @template.setter
def engine(self, engine): def template(self, template):
self._engine = engine # 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 hasattr(self, '_processors_index'):
if engine is None: if template is None:
# Unset context processors. # Unset context processors.
self.dicts[self._processors_index] = {} self.dicts[self._processors_index] = {}
else: else:
# Set context processors for this engine. # Set context processors for this engine.
processors = (template.engine.template_context_processors +
self._processors)
updates = {} updates = {}
for processor in engine.template_context_processors + self._processors: for processor in processors:
updates.update(processor(self.request)) updates.update(processor(self.request))
self.dicts[self._processors_index] = updates self.dicts[self._processors_index] = updates

View File

@ -211,7 +211,7 @@ class ForNode(Node):
context[self.loopvars[0]] = item context[self.loopvars[0]] = item
# In debug mode provide the source of the node which raised # In debug mode provide the source of the node which raised
# the exception # the exception
if context.engine.debug: if context.template.engine.debug:
for node in self.nodelist_loop: for node in self.nodelist_loop:
try: try:
nodelist.append(node.render(context)) nodelist.append(node.render(context))
@ -392,7 +392,7 @@ class SsiNode(Node):
def render(self, context): def render(self, context):
filepath = self.filepath.resolve(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: if settings.DEBUG:
return "[Didn't have permission to include file]" return "[Didn't have permission to include file]"
else: else:
@ -404,7 +404,7 @@ class SsiNode(Node):
output = '' output = ''
if self.parsed: if self.parsed:
try: try:
t = Template(output, name=filepath, engine=context.engine) t = Template(output, name=filepath, engine=context.template.engine)
return t.render(context) return t.render(context)
except TemplateSyntaxError as e: except TemplateSyntaxError as e:
if settings.DEBUG: if settings.DEBUG:

View File

@ -107,7 +107,7 @@ class ExtendsNode(Node):
if isinstance(getattr(parent, 'template', None), Template): if isinstance(getattr(parent, 'template', None), Template):
# parent is a django.template.backends.django.Template # parent is a django.template.backends.django.Template
return parent.template return parent.template
return context.engine.get_template(parent) return context.template.engine.get_template(parent)
def render(self, context): def render(self, context):
compiled_parent = self.get_parent(context) compiled_parent = self.get_parent(context)
@ -148,7 +148,7 @@ class IncludeNode(Node):
# Does this quack like a Template? # Does this quack like a Template?
if not callable(getattr(template, 'render', None)): if not callable(getattr(template, 'render', None)):
# If not, we'll try get_template # If not, we'll try get_template
template = context.engine.get_template(template) template = context.template.engine.get_template(template)
values = { values = {
name: var.resolve(context) name: var.resolve(context)
for name, var in six.iteritems(self.extra_context) for name, var in six.iteritems(self.extra_context)
@ -158,7 +158,7 @@ class IncludeNode(Node):
with context.push(**values): with context.push(**values):
return template.render(context) return template.render(context)
except Exception: except Exception:
if context.engine.debug: if context.template.engine.debug:
raise raise
return '' return ''

View File

@ -149,7 +149,7 @@ class BlockTranslateNode(Node):
result = translation.pgettext(message_context, singular) result = translation.pgettext(message_context, singular)
else: else:
result = translation.ugettext(singular) result = translation.ugettext(singular)
default_value = context.engine.string_if_invalid default_value = context.template.engine.string_if_invalid
def render_value(key): def render_value(key):
if key in context: if key in context:

View File

@ -756,10 +756,11 @@ Notes:
* The ``render()`` method is where the work actually happens. * The ``render()`` method is where the work actually happens.
* ``render()`` should generally fail silently, particularly in a production * ``render()`` should generally fail silently, particularly in a production
environment. In some cases however, particularly if ``context.engine.debug`` environment. In some cases however, particularly if
is ``True``, this method may raise an exception to make debugging easier. ``context.template.engine.debug`` is ``True``, this method may raise an
For example, several core tags raise ``django.template.TemplateSyntaxError`` exception to make debugging easier. For example, several core tags raise
if they receive the wrong number or type of arguments. ``django.template.TemplateSyntaxError`` if they receive the wrong number or
type of arguments.
Ultimately, this decoupling of compilation and rendering results in an Ultimately, this decoupling of compilation and rendering results in an
efficient template system, because a template can render multiple contexts 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:: template yourself. For example::
def render(self, context): 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)) return t.render(Context({'var': obj}, autoescape=context.autoescape))
.. versionchanged:: 1.8 .. versionchanged:: 1.8
The ``engine`` attribute of ``Context`` objects was added in Django 1.8. The ``template`` attribute of ``Context`` objects was added in Django 1.8.
:meth:`context.engine.get_template <django.template.Engine.get_template>` :meth:`context.template.engine.get_template
must be used instead of :func:`django.template.loader.get_template` <django.template.Engine.get_template>` must be used instead of
because the latter now returns a wrapper whose ``render`` method doesn't :func:`django.template.loader.get_template` because the latter now returns
accept a :class:`~django.template.Context`. 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 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 new ``Context`` in this example, the results would have *always* been

View File

@ -162,7 +162,7 @@ instance in the ``render()`` method of a template tag, you can use the current
You can write:: 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 This will load the template with the current engine without triggering the
multiple template engines machinery, which is usually the desired behavior. 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 Finally, if you have access to the current context, you can use the same trick
as above:: as above::
template = context.engine.from_string(template_code) template = context.template.engine.from_string(template_code)
``Template()`` ``Template()``
============== ==============

View File

@ -516,9 +516,6 @@ class RequestContextTests(unittest.TestCase):
self.assertEqual(len(ctx.dicts), 3) self.assertEqual(len(ctx.dicts), 3)
def test_context_comparable(self): 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_data = {'x': 'y', 'v': 'z', 'd': {'o': object, 'a': 'b'}}
# test comparing RequestContext to prevent problems if somebody # test comparing RequestContext to prevent problems if somebody
@ -526,8 +523,8 @@ class RequestContextTests(unittest.TestCase):
request = RequestFactory().get('/') request = RequestFactory().get('/')
self.assertEqual( self.assertEqual(
RequestContext(request, dict_=test_data, engine=engine), RequestContext(request, dict_=test_data),
RequestContext(request, dict_=test_data, engine=engine)) RequestContext(request, dict_=test_data))
@ignore_warnings(category=RemovedInDjango20Warning) @ignore_warnings(category=RemovedInDjango20Warning)