Fixed #15791 - method to signal that callable objects should not be called in templates
Thanks to ejucovy for the suggestion and patch! git-svn-id: http://code.djangoproject.com/svn/django/trunk@16045 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
9587235530
commit
1286d78311
|
@ -692,7 +692,9 @@ class Variable(object):
|
|||
):
|
||||
raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
|
||||
if callable(current):
|
||||
if getattr(current, 'alters_data', False):
|
||||
if getattr(current, 'do_not_call_in_templates', False):
|
||||
pass
|
||||
elif getattr(current, 'alters_data', False):
|
||||
current = settings.TEMPLATE_STRING_IF_INVALID
|
||||
else:
|
||||
try: # method call (assuming no args required)
|
||||
|
|
|
@ -207,8 +207,9 @@ straight lookups. Here are some things to keep in mind:
|
|||
|
||||
To prevent this, set an ``alters_data`` attribute on the callable
|
||||
variable. The template system won't call a variable if it has
|
||||
``alters_data=True`` set. The dynamically-generated
|
||||
:meth:`~django.db.models.Model.delete` and
|
||||
``alters_data=True`` set, and will instead replace the variable with
|
||||
:setting:`TEMPLATE_STRING_IF_INVALID`, unconditionally. The
|
||||
dynamically-generated :meth:`~django.db.models.Model.delete` and
|
||||
:meth:`~django.db.models.Model.save` methods on Django model objects get
|
||||
``alters_data=True`` automatically. Example::
|
||||
|
||||
|
@ -216,6 +217,15 @@ straight lookups. Here are some things to keep in mind:
|
|||
self.database_record.delete()
|
||||
sensitive_function.alters_data = True
|
||||
|
||||
* .. versionadded:: 1.4
|
||||
Occasionally you may want to turn off this feature for other reasons,
|
||||
and tell the template system to leave a variable un-called no matter
|
||||
what. To do so, set a ``do_not_call_in_templates`` attribute on the
|
||||
callable with the value ``True``. The template system then will act as
|
||||
if your variable is not callable (allowing you to access attributes of
|
||||
the callable, for example).
|
||||
|
||||
|
||||
.. _invalid-template-variables:
|
||||
|
||||
How invalid variables are handled
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
from django import template
|
||||
from django.utils.unittest import TestCase
|
||||
|
||||
class CallableVariablesTests(TestCase):
|
||||
|
||||
def test_callable(self):
|
||||
|
||||
class Doodad(object):
|
||||
def __init__(self, value):
|
||||
self.num_calls = 0
|
||||
self.value = value
|
||||
def __call__(self):
|
||||
self.num_calls += 1
|
||||
return {"the_value": self.value}
|
||||
|
||||
my_doodad = Doodad(42)
|
||||
c = template.Context({"my_doodad": my_doodad})
|
||||
|
||||
# We can't access ``my_doodad.value`` in the template, because
|
||||
# ``my_doodad.__call__`` will be invoked first, yielding a dictionary
|
||||
# without a key ``value``.
|
||||
t = template.Template('{{ my_doodad.value }}')
|
||||
self.assertEqual(t.render(c), u'')
|
||||
|
||||
# We can confirm that the doodad has been called
|
||||
self.assertEqual(my_doodad.num_calls, 1)
|
||||
|
||||
# But we can access keys on the dict that's returned
|
||||
# by ``__call__``, instead.
|
||||
t = template.Template('{{ my_doodad.the_value }}')
|
||||
self.assertEqual(t.render(c), u'42')
|
||||
self.assertEqual(my_doodad.num_calls, 2)
|
||||
|
||||
def test_alters_data(self):
|
||||
|
||||
class Doodad(object):
|
||||
alters_data = True
|
||||
def __init__(self, value):
|
||||
self.num_calls = 0
|
||||
self.value = value
|
||||
def __call__(self):
|
||||
self.num_calls += 1
|
||||
return {"the_value": self.value}
|
||||
|
||||
my_doodad = Doodad(42)
|
||||
c = template.Context({"my_doodad": my_doodad})
|
||||
|
||||
# Since ``my_doodad.alters_data`` is True, the template system will not
|
||||
# try to call our doodad but will use TEMPLATE_STRING_IF_INVALID
|
||||
t = template.Template('{{ my_doodad.value }}')
|
||||
self.assertEqual(t.render(c), u'')
|
||||
t = template.Template('{{ my_doodad.the_value }}')
|
||||
self.assertEqual(t.render(c), u'')
|
||||
|
||||
# Double-check that the object was really never called during the
|
||||
# template rendering.
|
||||
self.assertEqual(my_doodad.num_calls, 0)
|
||||
|
||||
def test_do_not_call(self):
|
||||
|
||||
class Doodad(object):
|
||||
do_not_call_in_templates = True
|
||||
def __init__(self, value):
|
||||
self.num_calls = 0
|
||||
self.value = value
|
||||
def __call__(self):
|
||||
self.num_calls += 1
|
||||
return {"the_value": self.value}
|
||||
|
||||
my_doodad = Doodad(42)
|
||||
c = template.Context({"my_doodad": my_doodad})
|
||||
|
||||
# Since ``my_doodad.do_not_call_in_templates`` is True, the template
|
||||
# system will not try to call our doodad. We can access its attributes
|
||||
# as normal, and we don't have access to the dict that it returns when
|
||||
# called.
|
||||
t = template.Template('{{ my_doodad.value }}')
|
||||
self.assertEqual(t.render(c), u'42')
|
||||
t = template.Template('{{ my_doodad.the_value }}')
|
||||
self.assertEqual(t.render(c), u'')
|
||||
|
||||
# Double-check that the object was really never called during the
|
||||
# template rendering.
|
||||
self.assertEqual(my_doodad.num_calls, 0)
|
||||
|
||||
def test_do_not_call_and_alters_data(self):
|
||||
# If we combine ``alters_data`` and ``do_not_call_in_templates``, the
|
||||
# ``alters_data`` attribute will not make any difference in the
|
||||
# template system's behavior.
|
||||
|
||||
class Doodad(object):
|
||||
do_not_call_in_templates = True
|
||||
alters_data = True
|
||||
def __init__(self, value):
|
||||
self.num_calls = 0
|
||||
self.value = value
|
||||
def __call__(self):
|
||||
self.num_calls += 1
|
||||
return {"the_value": self.value}
|
||||
|
||||
my_doodad = Doodad(42)
|
||||
c = template.Context({"my_doodad": my_doodad})
|
||||
|
||||
t = template.Template('{{ my_doodad.value }}')
|
||||
self.assertEqual(t.render(c), u'42')
|
||||
t = template.Template('{{ my_doodad.the_value }}')
|
||||
self.assertEqual(t.render(c), u'')
|
||||
|
||||
# Double-check that the object was really never called during the
|
||||
# template rendering.
|
||||
self.assertEqual(my_doodad.num_calls, 0)
|
|
@ -25,6 +25,7 @@ from django.utils.translation import activate, deactivate, ugettext as _
|
|||
from django.utils.safestring import mark_safe
|
||||
from django.utils.tzinfo import LocalTimezone
|
||||
|
||||
from callables import *
|
||||
from context import ContextTests
|
||||
from custom import CustomTagTests, CustomFilterTests
|
||||
from parser import ParserTests
|
||||
|
|
Loading…
Reference in New Issue