1. from django.template import TemplateSyntaxError
    
  2. from django.template.defaulttags import ForNode
    
  3. from django.test import SimpleTestCase
    
  4. 
    
  5. from ..utils import setup
    
  6. 
    
  7. 
    
  8. class ForTagTests(SimpleTestCase):
    
  9.     libraries = {"custom": "template_tests.templatetags.custom"}
    
  10. 
    
  11.     @setup({"for-tag01": "{% for val in values %}{{ val }}{% endfor %}"})
    
  12.     def test_for_tag01(self):
    
  13.         output = self.engine.render_to_string("for-tag01", {"values": [1, 2, 3]})
    
  14.         self.assertEqual(output, "123")
    
  15. 
    
  16.     @setup({"for-tag02": "{% for val in values reversed %}{{ val }}{% endfor %}"})
    
  17.     def test_for_tag02(self):
    
  18.         output = self.engine.render_to_string("for-tag02", {"values": [1, 2, 3]})
    
  19.         self.assertEqual(output, "321")
    
  20. 
    
  21.     @setup(
    
  22.         {"for-tag-vars01": "{% for val in values %}{{ forloop.counter }}{% endfor %}"}
    
  23.     )
    
  24.     def test_for_tag_vars01(self):
    
  25.         output = self.engine.render_to_string("for-tag-vars01", {"values": [6, 6, 6]})
    
  26.         self.assertEqual(output, "123")
    
  27. 
    
  28.     @setup(
    
  29.         {"for-tag-vars02": "{% for val in values %}{{ forloop.counter0 }}{% endfor %}"}
    
  30.     )
    
  31.     def test_for_tag_vars02(self):
    
  32.         output = self.engine.render_to_string("for-tag-vars02", {"values": [6, 6, 6]})
    
  33.         self.assertEqual(output, "012")
    
  34. 
    
  35.     @setup(
    
  36.         {
    
  37.             "for-tag-vars03": (
    
  38.                 "{% for val in values %}{{ forloop.revcounter }}{% endfor %}"
    
  39.             )
    
  40.         }
    
  41.     )
    
  42.     def test_for_tag_vars03(self):
    
  43.         output = self.engine.render_to_string("for-tag-vars03", {"values": [6, 6, 6]})
    
  44.         self.assertEqual(output, "321")
    
  45. 
    
  46.     @setup(
    
  47.         {
    
  48.             "for-tag-vars04": (
    
  49.                 "{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}"
    
  50.             )
    
  51.         }
    
  52.     )
    
  53.     def test_for_tag_vars04(self):
    
  54.         output = self.engine.render_to_string("for-tag-vars04", {"values": [6, 6, 6]})
    
  55.         self.assertEqual(output, "210")
    
  56. 
    
  57.     @setup(
    
  58.         {
    
  59.             "for-tag-vars05": "{% for val in values %}"
    
  60.             "{% if forloop.first %}f{% else %}x{% endif %}{% endfor %}"
    
  61.         }
    
  62.     )
    
  63.     def test_for_tag_vars05(self):
    
  64.         output = self.engine.render_to_string("for-tag-vars05", {"values": [6, 6, 6]})
    
  65.         self.assertEqual(output, "fxx")
    
  66. 
    
  67.     @setup(
    
  68.         {
    
  69.             "for-tag-vars06": "{% for val in values %}"
    
  70.             "{% if forloop.last %}l{% else %}x{% endif %}{% endfor %}"
    
  71.         }
    
  72.     )
    
  73.     def test_for_tag_vars06(self):
    
  74.         output = self.engine.render_to_string("for-tag-vars06", {"values": [6, 6, 6]})
    
  75.         self.assertEqual(output, "xxl")
    
  76. 
    
  77.     @setup(
    
  78.         {
    
  79.             "for-tag-unpack01": (
    
  80.                 "{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}"
    
  81.             )
    
  82.         }
    
  83.     )
    
  84.     def test_for_tag_unpack01(self):
    
  85.         output = self.engine.render_to_string(
    
  86.             "for-tag-unpack01", {"items": (("one", 1), ("two", 2))}
    
  87.         )
    
  88.         self.assertEqual(output, "one:1/two:2/")
    
  89. 
    
  90.     @setup(
    
  91.         {
    
  92.             "for-tag-unpack03": (
    
  93.                 "{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}"
    
  94.             )
    
  95.         }
    
  96.     )
    
  97.     def test_for_tag_unpack03(self):
    
  98.         output = self.engine.render_to_string(
    
  99.             "for-tag-unpack03", {"items": (("one", 1), ("two", 2))}
    
  100.         )
    
  101.         self.assertEqual(output, "one:1/two:2/")
    
  102. 
    
  103.     @setup(
    
  104.         {
    
  105.             "for-tag-unpack04": (
    
  106.                 "{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}"
    
  107.             )
    
  108.         }
    
  109.     )
    
  110.     def test_for_tag_unpack04(self):
    
  111.         output = self.engine.render_to_string(
    
  112.             "for-tag-unpack04", {"items": (("one", 1), ("two", 2))}
    
  113.         )
    
  114.         self.assertEqual(output, "one:1/two:2/")
    
  115. 
    
  116.     @setup(
    
  117.         {
    
  118.             "for-tag-unpack05": (
    
  119.                 "{% for key ,value in items %}{{ key }}:{{ value }}/{% endfor %}"
    
  120.             )
    
  121.         }
    
  122.     )
    
  123.     def test_for_tag_unpack05(self):
    
  124.         output = self.engine.render_to_string(
    
  125.             "for-tag-unpack05", {"items": (("one", 1), ("two", 2))}
    
  126.         )
    
  127.         self.assertEqual(output, "one:1/two:2/")
    
  128. 
    
  129.     @setup(
    
  130.         {
    
  131.             "for-tag-unpack06": (
    
  132.                 "{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}"
    
  133.             )
    
  134.         }
    
  135.     )
    
  136.     def test_for_tag_unpack06(self):
    
  137.         msg = "'for' tag received an invalid argument: for key value in items"
    
  138.         with self.assertRaisesMessage(TemplateSyntaxError, msg):
    
  139.             self.engine.render_to_string(
    
  140.                 "for-tag-unpack06", {"items": (("one", 1), ("two", 2))}
    
  141.             )
    
  142. 
    
  143.     @setup(
    
  144.         {
    
  145.             "for-tag-unpack07": (
    
  146.                 "{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}"
    
  147.             )
    
  148.         }
    
  149.     )
    
  150.     def test_for_tag_unpack07(self):
    
  151.         msg = "'for' tag received an invalid argument: for key,,value in items"
    
  152.         with self.assertRaisesMessage(TemplateSyntaxError, msg):
    
  153.             self.engine.render_to_string(
    
  154.                 "for-tag-unpack07", {"items": (("one", 1), ("two", 2))}
    
  155.             )
    
  156. 
    
  157.     @setup(
    
  158.         {
    
  159.             "for-tag-unpack08": (
    
  160.                 "{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}"
    
  161.             )
    
  162.         }
    
  163.     )
    
  164.     def test_for_tag_unpack08(self):
    
  165.         msg = "'for' tag received an invalid argument: for key,value, in items"
    
  166.         with self.assertRaisesMessage(TemplateSyntaxError, msg):
    
  167.             self.engine.render_to_string(
    
  168.                 "for-tag-unpack08", {"items": (("one", 1), ("two", 2))}
    
  169.             )
    
  170. 
    
  171.     @setup({"double-quote": '{% for "k" in items %}{{ "k" }}/{% endfor %}'})
    
  172.     def test_unpack_double_quote(self):
    
  173.         msg = """'for' tag received an invalid argument: for "k" in items"""
    
  174.         with self.assertRaisesMessage(TemplateSyntaxError, msg):
    
  175.             self.engine.render_to_string("double-quote", {"items": (1, 2)})
    
  176. 
    
  177.     @setup({"single-quote": "{% for 'k' in items %}{{ k }}/{% endfor %}"})
    
  178.     def test_unpack_single_quote(self):
    
  179.         msg = """'for' tag received an invalid argument: for 'k' in items"""
    
  180.         with self.assertRaisesMessage(TemplateSyntaxError, msg):
    
  181.             self.engine.render_to_string("single-quote", {"items": (1, 2)})
    
  182. 
    
  183.     @setup({"vertical-bar": "{% for k|upper in items %}{{ k|upper }}/{% endfor %}"})
    
  184.     def test_unpack_vertical_bar(self):
    
  185.         msg = "'for' tag received an invalid argument: for k|upper in items"
    
  186.         with self.assertRaisesMessage(TemplateSyntaxError, msg):
    
  187.             self.engine.render_to_string("vertical-bar", {"items": (1, 2)})
    
  188. 
    
  189.     @setup(
    
  190.         {
    
  191.             "for-tag-unpack09": (
    
  192.                 "{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}"
    
  193.             )
    
  194.         }
    
  195.     )
    
  196.     def test_for_tag_unpack09(self):
    
  197.         """
    
  198.         A single loopvar doesn't truncate the list in val.
    
  199.         """
    
  200.         output = self.engine.render_to_string(
    
  201.             "for-tag-unpack09", {"items": (("one", 1), ("two", 2))}
    
  202.         )
    
  203.         self.assertEqual(output, "one:1/two:2/")
    
  204. 
    
  205.     @setup(
    
  206.         {
    
  207.             "for-tag-unpack13": (
    
  208.                 "{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}"
    
  209.             )
    
  210.         }
    
  211.     )
    
  212.     def test_for_tag_unpack13(self):
    
  213.         output = self.engine.render_to_string(
    
  214.             "for-tag-unpack13", {"items": (("one", 1, "carrot"), ("two", 2, "cheese"))}
    
  215.         )
    
  216.         if self.engine.string_if_invalid:
    
  217.             self.assertEqual(output, "one:1,carrot/two:2,cheese/")
    
  218.         else:
    
  219.             self.assertEqual(output, "one:1,carrot/two:2,cheese/")
    
  220. 
    
  221.     @setup(
    
  222.         {
    
  223.             "for-tag-empty01": (
    
  224.                 "{% for val in values %}{{ val }}{% empty %}empty text{% endfor %}"
    
  225.             )
    
  226.         }
    
  227.     )
    
  228.     def test_for_tag_empty01(self):
    
  229.         output = self.engine.render_to_string("for-tag-empty01", {"values": [1, 2, 3]})
    
  230.         self.assertEqual(output, "123")
    
  231. 
    
  232.     @setup(
    
  233.         {
    
  234.             "for-tag-empty02": (
    
  235.                 "{% for val in values %}{{ val }}{% empty %}values array empty"
    
  236.                 "{% endfor %}"
    
  237.             )
    
  238.         }
    
  239.     )
    
  240.     def test_for_tag_empty02(self):
    
  241.         output = self.engine.render_to_string("for-tag-empty02", {"values": []})
    
  242.         self.assertEqual(output, "values array empty")
    
  243. 
    
  244.     @setup(
    
  245.         {
    
  246.             "for-tag-empty03": "{% for val in values %}"
    
  247.             "{{ val }}{% empty %}values array not found{% endfor %}"
    
  248.         }
    
  249.     )
    
  250.     def test_for_tag_empty03(self):
    
  251.         output = self.engine.render_to_string("for-tag-empty03")
    
  252.         self.assertEqual(output, "values array not found")
    
  253. 
    
  254.     @setup(
    
  255.         {
    
  256.             "for-tag-filter-ws": (
    
  257.                 "{% load custom %}{% for x in s|noop:'x y' %}{{ x }}{% endfor %}"
    
  258.             )
    
  259.         }
    
  260.     )
    
  261.     def test_for_tag_filter_ws(self):
    
  262.         """
    
  263.         #19882
    
  264.         """
    
  265.         output = self.engine.render_to_string("for-tag-filter-ws", {"s": "abc"})
    
  266.         self.assertEqual(output, "abc")
    
  267. 
    
  268.     @setup(
    
  269.         {"for-tag-unpack-strs": "{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}"}
    
  270.     )
    
  271.     def test_for_tag_unpack_strs(self):
    
  272.         output = self.engine.render_to_string(
    
  273.             "for-tag-unpack-strs", {"items": ("ab", "ac")}
    
  274.         )
    
  275.         self.assertEqual(output, "a:b/a:c/")
    
  276. 
    
  277.     @setup({"for-tag-unpack10": "{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}"})
    
  278.     def test_for_tag_unpack10(self):
    
  279.         with self.assertRaisesMessage(
    
  280.             ValueError, "Need 2 values to unpack in for loop; got 3."
    
  281.         ):
    
  282.             self.engine.render_to_string(
    
  283.                 "for-tag-unpack10",
    
  284.                 {"items": (("one", 1, "carrot"), ("two", 2, "orange"))},
    
  285.             )
    
  286. 
    
  287.     @setup(
    
  288.         {
    
  289.             "for-tag-unpack11": (
    
  290.                 "{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}"
    
  291.             )
    
  292.         }
    
  293.     )
    
  294.     def test_for_tag_unpack11(self):
    
  295.         with self.assertRaisesMessage(
    
  296.             ValueError, "Need 3 values to unpack in for loop; got 2."
    
  297.         ):
    
  298.             self.engine.render_to_string(
    
  299.                 "for-tag-unpack11",
    
  300.                 {"items": (("one", 1), ("two", 2))},
    
  301.             )
    
  302. 
    
  303.     @setup(
    
  304.         {
    
  305.             "for-tag-unpack12": (
    
  306.                 "{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}"
    
  307.             )
    
  308.         }
    
  309.     )
    
  310.     def test_for_tag_unpack12(self):
    
  311.         with self.assertRaisesMessage(
    
  312.             ValueError, "Need 3 values to unpack in for loop; got 2."
    
  313.         ):
    
  314.             self.engine.render_to_string(
    
  315.                 "for-tag-unpack12", {"items": (("one", 1, "carrot"), ("two", 2))}
    
  316.             )
    
  317. 
    
  318.     @setup({"for-tag-unpack14": "{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}"})
    
  319.     def test_for_tag_unpack14(self):
    
  320.         with self.assertRaisesMessage(
    
  321.             ValueError, "Need 2 values to unpack in for loop; got 1."
    
  322.         ):
    
  323.             self.engine.render_to_string("for-tag-unpack14", {"items": (1, 2)})
    
  324. 
    
  325.     @setup(
    
  326.         {
    
  327.             "main": '{% with alpha=alpha.values %}{% include "base" %}{% endwith %}_'
    
  328.             '{% with alpha=alpha.extra %}{% include "base" %}{% endwith %}',
    
  329.             "base": "{% for x, y in alpha %}{{ x }}:{{ y }},{% endfor %}",
    
  330.         }
    
  331.     )
    
  332.     def test_for_tag_context(self):
    
  333.         """
    
  334.         ForNode.render() pops the values it pushes to the context (#28001).
    
  335.         """
    
  336.         output = self.engine.render_to_string(
    
  337.             "main",
    
  338.             {
    
  339.                 "alpha": {
    
  340.                     "values": [("two", 2), ("four", 4)],
    
  341.                     "extra": [("six", 6), ("eight", 8)],
    
  342.                 },
    
  343.             },
    
  344.         )
    
  345.         self.assertEqual(output, "two:2,four:4,_six:6,eight:8,")
    
  346. 
    
  347.     @setup({"invalid_for_loop": "{% for x items %}{{ x }}{% endfor %}"})
    
  348.     def test_invalid_arg(self):
    
  349.         msg = "'for' statements should have at least four words: for x items"
    
  350.         with self.assertRaisesMessage(TemplateSyntaxError, msg):
    
  351.             self.engine.render_to_string("invalid_for_loop", {"items": (1, 2)})
    
  352. 
    
  353.     @setup({"invalid_for_loop": "{% for x from items %}{{ x }}{% endfor %}"})
    
  354.     def test_invalid_in_keyword(self):
    
  355.         msg = "'for' statements should use the format 'for x in y': for x from items"
    
  356.         with self.assertRaisesMessage(TemplateSyntaxError, msg):
    
  357.             self.engine.render_to_string("invalid_for_loop", {"items": (1, 2)})
    
  358. 
    
  359. 
    
  360. class ForNodeTests(SimpleTestCase):
    
  361.     def test_repr(self):
    
  362.         node = ForNode(
    
  363.             "x",
    
  364.             "sequence",
    
  365.             is_reversed=True,
    
  366.             nodelist_loop=["val"],
    
  367.             nodelist_empty=["val2"],
    
  368.         )
    
  369.         self.assertEqual(
    
  370.             repr(node), "<ForNode: for x in sequence, tail_len: 1 reversed>"
    
  371.         )