Ticket #15791: do_not_call_with_tests.diff

File do_not_call_with_tests.diff, 7.7 KB (added by Ethan Jucovy, 13 years ago)

same patch, with tests

  • docs/ref/templates/api.txt

     
    195195    * A variable can only be called if it has no required arguments. Otherwise,
    196196      the system will return an empty string.
    197197
     198    * Occasionally you may want to turn off this feature, and tell the template
     199      system to leave a variable un-called no matter what.  To disable this feature,
     200      set a ``do_not_call_in_templates`` attribute on the callable variable.  The
     201      template system then will act as if your variable is not callable.
     202
    198203    * Obviously, there can be side effects when calling some variables, and
    199204      it'd be either foolish or a security hole to allow the template system
    200205      to access them.
     
    207212
    208213      To prevent this, set an ``alters_data`` attribute on the callable
    209214      variable. The template system won't call a variable if it has
    210       ``alters_data=True`` set. The dynamically-generated
     215      ``alters_data=True`` set, and will instead replace the variable with an
     216      empty string, unconditionally.  The dynamically-generated
    211217      :meth:`~django.db.models.Model.delete` and
    212218      :meth:`~django.db.models.Model.save` methods on Django model objects get
    213219      ``alters_data=True`` automatically. Example::
  • django/template/base.py

     
    692692                                ):
    693693                            raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
    694694                if callable(current):
    695                     if getattr(current, 'alters_data', False):
     695                    if getattr(current, 'do_not_call_in_templates', False):
     696                        pass
     697                    elif getattr(current, 'alters_data', False):
    696698                        current = settings.TEMPLATE_STRING_IF_INVALID
    697699                    else:
    698700                        try: # method call (assuming no args required)
  • tests/regressiontests/templates/tests.py

     
    2424from django.utils.safestring import mark_safe
    2525from django.utils.tzinfo import LocalTimezone
    2626
     27from callables import *
    2728from context import ContextTests
    2829from custom import CustomTagTests, CustomFilterTests
    2930from parser import ParserTests
  • tests/regressiontests/templates/callables.py

     
     1from django import template
     2from django.utils.unittest import TestCase
     3
     4class CallableVariablesTests(TestCase):
     5    def test_callable(self):
     6
     7        class Doodad(object):
     8            def __init__(self, value):
     9                self.num_calls = 0
     10                self.value = value
     11            def __call__(self):
     12                self.num_calls += 1
     13                return {"the_value": self.value}
     14
     15        my_doodad = Doodad(42)
     16        c = template.Context({"my_doodad": my_doodad})
     17
     18        # We can't access ``my_doodad.value`` in the template,
     19        # because ``my_doodad.__call__`` will be invoked first.
     20        # We end up with the empty string, because calling the
     21        # variable yields a dict, which has no key ``value``.
     22        t = template.Template('{{my_doodad.value}}')
     23        self.assertEqual(t.render(c), u'')
     24
     25        # We can confirm that the doodad has been called,
     26        # because we set a simple incrementing counter:
     27        self.assertEqual(my_doodad.num_calls, 1)
     28
     29        # But we can access keys on the dict that's returned
     30        # by the implicit ``__call__``, instead.
     31        t = template.Template('{{my_doodad.the_value}}')
     32        self.assertEqual(t.render(c), u'42')
     33        self.assertEqual(my_doodad.num_calls, 2)
     34
     35    def test_alters_data(self):
     36
     37        class Doodad(object):
     38            alters_data = True
     39            def __init__(self, value):
     40                self.num_calls = 0
     41                self.value = value
     42            def __call__(self):
     43                self.num_calls += 1
     44                return {"the_value": self.value}
     45
     46        my_doodad = Doodad(42)
     47        c = template.Context({"my_doodad": my_doodad})
     48
     49        # Since ``my_doodad.alters_data`` is True,
     50        # the template system will not try to call our doodad.
     51        # Instead it will replace it with the empty string.
     52        # It won't even return the dict.
     53        t = template.Template('{{my_doodad.value}}')
     54        self.assertEqual(t.render(c), u'')
     55        t = template.Template('{{my_doodad.the_value}}')
     56        self.assertEqual(t.render(c), u'')
     57
     58        # And let's just double-check that the object was
     59        # really never called during the template rendering.
     60        self.assertEqual(my_doodad.num_calls, 0)
     61
     62    def test_do_not_call(self):
     63        class Doodad(object):
     64            do_not_call_in_templates = True
     65            def __init__(self, value):
     66                self.num_calls = 0
     67                self.value = value
     68            def __call__(self):
     69                self.num_calls += 1
     70                return {"the_value": self.value}
     71
     72        my_doodad = Doodad(42)
     73        c = template.Context({"my_doodad": my_doodad})
     74
     75        # Since ``my_doodad.do_not_call_in_templates`` is True,
     76        # the template system will not try to call our doodad.
     77        # Instead it will leave it alone.  We can access its
     78        # attributes as normal, and we don't have access to
     79        # the dict that it returns when called.
     80        t = template.Template('{{my_doodad.value}}')
     81        self.assertEqual(t.render(c), u'42')
     82        t = template.Template('{{my_doodad.the_value}}')
     83        self.assertEqual(t.render(c), u'')
     84       
     85        # And let's just double-check that the object was
     86        # really never called during the template rendering.
     87        self.assertEqual(my_doodad.num_calls, 0)
     88
     89    def test_do_not_call_and_alters_data(self):
     90
     91        # If we combine ``alters_data`` and ``do_not_call_in_templates``
     92        # the ``alters_data`` attribute will not make any difference in
     93        # the template system's behavior.  The fact that calling the object
     94        # alters data shouldn't matter, because the template system knows
     95        # never to call it anyway.
     96        class Doodad(object):
     97            do_not_call_in_templates = True
     98            alters_data = True
     99            def __init__(self, value):
     100                self.num_calls = 0
     101                self.value = value
     102            def __call__(self):
     103                self.num_calls += 1
     104                return {"the_value": self.value}
     105
     106        my_doodad = Doodad(42)
     107        c = template.Context({"my_doodad": my_doodad})
     108
     109        # Since ``my_doodad.do_not_call_in_templates`` is True,
     110        # the template system will not try to call our doodad.
     111        # Instead it will leave it alone.  We can access its
     112        # attributes as normal, and we don't have access to
     113        # the dict that it returns when called.
     114        t = template.Template('{{my_doodad.value}}')
     115        self.assertEqual(t.render(c), u'42')
     116        t = template.Template('{{my_doodad.the_value}}')
     117        self.assertEqual(t.render(c), u'')
     118       
     119        # And let's just double-check that the object was
     120        # really never called during the template rendering.
     121        self.assertEqual(my_doodad.num_calls, 0)
Back to Top