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