1. import copy
    
  2. import datetime
    
  3. 
    
  4. from django.forms import ChoiceField, Form, Select
    
  5. from django.test import override_settings
    
  6. from django.utils.safestring import mark_safe
    
  7. 
    
  8. from .base import WidgetTest
    
  9. 
    
  10. 
    
  11. class SelectTest(WidgetTest):
    
  12.     widget = Select
    
  13.     nested_widget = Select(
    
  14.         choices=(
    
  15.             ("outer1", "Outer 1"),
    
  16.             ('Group "1"', (("inner1", "Inner 1"), ("inner2", "Inner 2"))),
    
  17.         )
    
  18.     )
    
  19. 
    
  20.     def test_render(self):
    
  21.         self.check_html(
    
  22.             self.widget(choices=self.beatles),
    
  23.             "beatle",
    
  24.             "J",
    
  25.             html=(
    
  26.                 """<select name="beatle">
    
  27.             <option value="J" selected>John</option>
    
  28.             <option value="P">Paul</option>
    
  29.             <option value="G">George</option>
    
  30.             <option value="R">Ringo</option>
    
  31.             </select>"""
    
  32.             ),
    
  33.         )
    
  34. 
    
  35.     def test_render_none(self):
    
  36.         """
    
  37.         If the value is None, none of the options are selected.
    
  38.         """
    
  39.         self.check_html(
    
  40.             self.widget(choices=self.beatles),
    
  41.             "beatle",
    
  42.             None,
    
  43.             html=(
    
  44.                 """<select name="beatle">
    
  45.             <option value="J">John</option>
    
  46.             <option value="P">Paul</option>
    
  47.             <option value="G">George</option>
    
  48.             <option value="R">Ringo</option>
    
  49.             </select>"""
    
  50.             ),
    
  51.         )
    
  52. 
    
  53.     def test_render_label_value(self):
    
  54.         """
    
  55.         If the value corresponds to a label (but not to an option value), none
    
  56.         of the options are selected.
    
  57.         """
    
  58.         self.check_html(
    
  59.             self.widget(choices=self.beatles),
    
  60.             "beatle",
    
  61.             "John",
    
  62.             html=(
    
  63.                 """<select name="beatle">
    
  64.             <option value="J">John</option>
    
  65.             <option value="P">Paul</option>
    
  66.             <option value="G">George</option>
    
  67.             <option value="R">Ringo</option>
    
  68.             </select>"""
    
  69.             ),
    
  70.         )
    
  71. 
    
  72.     def test_render_selected(self):
    
  73.         """
    
  74.         Only one option can be selected (#8103).
    
  75.         """
    
  76.         choices = [("0", "0"), ("1", "1"), ("2", "2"), ("3", "3"), ("0", "extra")]
    
  77. 
    
  78.         self.check_html(
    
  79.             self.widget(choices=choices),
    
  80.             "choices",
    
  81.             "0",
    
  82.             html=(
    
  83.                 """<select name="choices">
    
  84.             <option value="0" selected>0</option>
    
  85.             <option value="1">1</option>
    
  86.             <option value="2">2</option>
    
  87.             <option value="3">3</option>
    
  88.             <option value="0">extra</option>
    
  89.             </select>"""
    
  90.             ),
    
  91.         )
    
  92. 
    
  93.     def test_constructor_attrs(self):
    
  94.         """
    
  95.         Select options shouldn't inherit the parent widget attrs.
    
  96.         """
    
  97.         widget = Select(
    
  98.             attrs={"class": "super", "id": "super"},
    
  99.             choices=[(1, 1), (2, 2), (3, 3)],
    
  100.         )
    
  101.         self.check_html(
    
  102.             widget,
    
  103.             "num",
    
  104.             2,
    
  105.             html=(
    
  106.                 """<select name="num" class="super" id="super">
    
  107.               <option value="1">1</option>
    
  108.               <option value="2" selected>2</option>
    
  109.               <option value="3">3</option>
    
  110.             </select>"""
    
  111.             ),
    
  112.         )
    
  113. 
    
  114.     def test_compare_to_str(self):
    
  115.         """
    
  116.         The value is compared to its str().
    
  117.         """
    
  118.         self.check_html(
    
  119.             self.widget(choices=[("1", "1"), ("2", "2"), ("3", "3")]),
    
  120.             "num",
    
  121.             2,
    
  122.             html=(
    
  123.                 """<select name="num">
    
  124.                 <option value="1">1</option>
    
  125.                 <option value="2" selected>2</option>
    
  126.                 <option value="3">3</option>
    
  127.                 </select>"""
    
  128.             ),
    
  129.         )
    
  130.         self.check_html(
    
  131.             self.widget(choices=[(1, 1), (2, 2), (3, 3)]),
    
  132.             "num",
    
  133.             "2",
    
  134.             html=(
    
  135.                 """<select name="num">
    
  136.                 <option value="1">1</option>
    
  137.                 <option value="2" selected>2</option>
    
  138.                 <option value="3">3</option>
    
  139.                 </select>"""
    
  140.             ),
    
  141.         )
    
  142.         self.check_html(
    
  143.             self.widget(choices=[(1, 1), (2, 2), (3, 3)]),
    
  144.             "num",
    
  145.             2,
    
  146.             html=(
    
  147.                 """<select name="num">
    
  148.                 <option value="1">1</option>
    
  149.                 <option value="2" selected>2</option>
    
  150.                 <option value="3">3</option>
    
  151.                 </select>"""
    
  152.             ),
    
  153.         )
    
  154. 
    
  155.     def test_choices_constructor(self):
    
  156.         widget = Select(choices=[(1, 1), (2, 2), (3, 3)])
    
  157.         self.check_html(
    
  158.             widget,
    
  159.             "num",
    
  160.             2,
    
  161.             html=(
    
  162.                 """<select name="num">
    
  163.             <option value="1">1</option>
    
  164.             <option value="2" selected>2</option>
    
  165.             <option value="3">3</option>
    
  166.             </select>"""
    
  167.             ),
    
  168.         )
    
  169. 
    
  170.     def test_choices_constructor_generator(self):
    
  171.         """
    
  172.         If choices is passed to the constructor and is a generator, it can be
    
  173.         iterated over multiple times without getting consumed.
    
  174.         """
    
  175. 
    
  176.         def get_choices():
    
  177.             for i in range(5):
    
  178.                 yield (i, i)
    
  179. 
    
  180.         widget = Select(choices=get_choices())
    
  181.         self.check_html(
    
  182.             widget,
    
  183.             "num",
    
  184.             2,
    
  185.             html=(
    
  186.                 """<select name="num">
    
  187.             <option value="0">0</option>
    
  188.             <option value="1">1</option>
    
  189.             <option value="2" selected>2</option>
    
  190.             <option value="3">3</option>
    
  191.             <option value="4">4</option>
    
  192.             </select>"""
    
  193.             ),
    
  194.         )
    
  195.         self.check_html(
    
  196.             widget,
    
  197.             "num",
    
  198.             3,
    
  199.             html=(
    
  200.                 """<select name="num">
    
  201.             <option value="0">0</option>
    
  202.             <option value="1">1</option>
    
  203.             <option value="2">2</option>
    
  204.             <option value="3" selected>3</option>
    
  205.             <option value="4">4</option>
    
  206.             </select>"""
    
  207.             ),
    
  208.         )
    
  209. 
    
  210.     def test_choices_escaping(self):
    
  211.         choices = (("bad", "you & me"), ("good", mark_safe("you &gt; me")))
    
  212.         self.check_html(
    
  213.             self.widget(choices=choices),
    
  214.             "escape",
    
  215.             None,
    
  216.             html=(
    
  217.                 """<select name="escape">
    
  218.             <option value="bad">you &amp; me</option>
    
  219.             <option value="good">you &gt; me</option>
    
  220.             </select>"""
    
  221.             ),
    
  222.         )
    
  223. 
    
  224.     def test_choices_unicode(self):
    
  225.         self.check_html(
    
  226.             self.widget(choices=[("ŠĐĆŽćžšđ", "ŠĐabcĆŽćžšđ"), ("ćžšđ", "abcćžšđ")]),
    
  227.             "email",
    
  228.             "ŠĐĆŽćžšđ",
    
  229.             html=(
    
  230.                 """
    
  231.                 <select name="email">
    
  232.                 <option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111"
    
  233.                     selected>
    
  234.                     \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111
    
  235.                 </option>
    
  236.                 <option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111
    
  237.                 </option>
    
  238.                 </select>
    
  239.                 """
    
  240.             ),
    
  241.         )
    
  242. 
    
  243.     def test_choices_optgroup(self):
    
  244.         """
    
  245.         Choices can be nested one level in order to create HTML optgroups.
    
  246.         """
    
  247.         self.check_html(
    
  248.             self.nested_widget,
    
  249.             "nestchoice",
    
  250.             None,
    
  251.             html=(
    
  252.                 """<select name="nestchoice">
    
  253.             <option value="outer1">Outer 1</option>
    
  254.             <optgroup label="Group &quot;1&quot;">
    
  255.             <option value="inner1">Inner 1</option>
    
  256.             <option value="inner2">Inner 2</option>
    
  257.             </optgroup>
    
  258.             </select>"""
    
  259.             ),
    
  260.         )
    
  261. 
    
  262.     def test_choices_select_outer(self):
    
  263.         self.check_html(
    
  264.             self.nested_widget,
    
  265.             "nestchoice",
    
  266.             "outer1",
    
  267.             html=(
    
  268.                 """<select name="nestchoice">
    
  269.             <option value="outer1" selected>Outer 1</option>
    
  270.             <optgroup label="Group &quot;1&quot;">
    
  271.             <option value="inner1">Inner 1</option>
    
  272.             <option value="inner2">Inner 2</option>
    
  273.             </optgroup>
    
  274.             </select>"""
    
  275.             ),
    
  276.         )
    
  277. 
    
  278.     def test_choices_select_inner(self):
    
  279.         self.check_html(
    
  280.             self.nested_widget,
    
  281.             "nestchoice",
    
  282.             "inner1",
    
  283.             html=(
    
  284.                 """<select name="nestchoice">
    
  285.             <option value="outer1">Outer 1</option>
    
  286.             <optgroup label="Group &quot;1&quot;">
    
  287.             <option value="inner1" selected>Inner 1</option>
    
  288.             <option value="inner2">Inner 2</option>
    
  289.             </optgroup>
    
  290.             </select>"""
    
  291.             ),
    
  292.         )
    
  293. 
    
  294.     @override_settings(USE_THOUSAND_SEPARATOR=True)
    
  295.     def test_doesnt_localize_option_value(self):
    
  296.         choices = [
    
  297.             (1, "One"),
    
  298.             (1000, "One thousand"),
    
  299.             (1000000, "One million"),
    
  300.         ]
    
  301.         html = """
    
  302.         <select name="number">
    
  303.         <option value="1">One</option>
    
  304.         <option value="1000">One thousand</option>
    
  305.         <option value="1000000">One million</option>
    
  306.         </select>
    
  307.         """
    
  308.         self.check_html(self.widget(choices=choices), "number", None, html=html)
    
  309. 
    
  310.         choices = [
    
  311.             (datetime.time(0, 0), "midnight"),
    
  312.             (datetime.time(12, 0), "noon"),
    
  313.         ]
    
  314.         html = """
    
  315.         <select name="time">
    
  316.         <option value="00:00:00">midnight</option>
    
  317.         <option value="12:00:00">noon</option>
    
  318.         </select>
    
  319.         """
    
  320.         self.check_html(self.widget(choices=choices), "time", None, html=html)
    
  321. 
    
  322.     def test_options(self):
    
  323.         options = list(
    
  324.             self.widget(choices=self.beatles).options(
    
  325.                 "name",
    
  326.                 ["J"],
    
  327.                 attrs={"class": "super"},
    
  328.             )
    
  329.         )
    
  330.         self.assertEqual(len(options), 4)
    
  331.         self.assertEqual(options[0]["name"], "name")
    
  332.         self.assertEqual(options[0]["value"], "J")
    
  333.         self.assertEqual(options[0]["label"], "John")
    
  334.         self.assertEqual(options[0]["index"], "0")
    
  335.         self.assertIs(options[0]["selected"], True)
    
  336.         # Template-related attributes
    
  337.         self.assertEqual(options[1]["name"], "name")
    
  338.         self.assertEqual(options[1]["value"], "P")
    
  339.         self.assertEqual(options[1]["label"], "Paul")
    
  340.         self.assertEqual(options[1]["index"], "1")
    
  341.         self.assertIs(options[1]["selected"], False)
    
  342. 
    
  343.     def test_optgroups(self):
    
  344.         choices = [
    
  345.             (
    
  346.                 "Audio",
    
  347.                 [
    
  348.                     ("vinyl", "Vinyl"),
    
  349.                     ("cd", "CD"),
    
  350.                 ],
    
  351.             ),
    
  352.             (
    
  353.                 "Video",
    
  354.                 [
    
  355.                     ("vhs", "VHS Tape"),
    
  356.                     ("dvd", "DVD"),
    
  357.                 ],
    
  358.             ),
    
  359.             ("unknown", "Unknown"),
    
  360.         ]
    
  361.         groups = list(
    
  362.             self.widget(choices=choices).optgroups(
    
  363.                 "name",
    
  364.                 ["vhs"],
    
  365.                 attrs={"class": "super"},
    
  366.             )
    
  367.         )
    
  368.         audio, video, unknown = groups
    
  369.         label, options, index = audio
    
  370.         self.assertEqual(label, "Audio")
    
  371.         self.assertEqual(
    
  372.             options,
    
  373.             [
    
  374.                 {
    
  375.                     "value": "vinyl",
    
  376.                     "type": "select",
    
  377.                     "attrs": {},
    
  378.                     "index": "0_0",
    
  379.                     "label": "Vinyl",
    
  380.                     "template_name": "django/forms/widgets/select_option.html",
    
  381.                     "name": "name",
    
  382.                     "selected": False,
    
  383.                     "wrap_label": True,
    
  384.                 },
    
  385.                 {
    
  386.                     "value": "cd",
    
  387.                     "type": "select",
    
  388.                     "attrs": {},
    
  389.                     "index": "0_1",
    
  390.                     "label": "CD",
    
  391.                     "template_name": "django/forms/widgets/select_option.html",
    
  392.                     "name": "name",
    
  393.                     "selected": False,
    
  394.                     "wrap_label": True,
    
  395.                 },
    
  396.             ],
    
  397.         )
    
  398.         self.assertEqual(index, 0)
    
  399.         label, options, index = video
    
  400.         self.assertEqual(label, "Video")
    
  401.         self.assertEqual(
    
  402.             options,
    
  403.             [
    
  404.                 {
    
  405.                     "value": "vhs",
    
  406.                     "template_name": "django/forms/widgets/select_option.html",
    
  407.                     "label": "VHS Tape",
    
  408.                     "attrs": {"selected": True},
    
  409.                     "index": "1_0",
    
  410.                     "name": "name",
    
  411.                     "selected": True,
    
  412.                     "type": "select",
    
  413.                     "wrap_label": True,
    
  414.                 },
    
  415.                 {
    
  416.                     "value": "dvd",
    
  417.                     "template_name": "django/forms/widgets/select_option.html",
    
  418.                     "label": "DVD",
    
  419.                     "attrs": {},
    
  420.                     "index": "1_1",
    
  421.                     "name": "name",
    
  422.                     "selected": False,
    
  423.                     "type": "select",
    
  424.                     "wrap_label": True,
    
  425.                 },
    
  426.             ],
    
  427.         )
    
  428.         self.assertEqual(index, 1)
    
  429.         label, options, index = unknown
    
  430.         self.assertIsNone(label)
    
  431.         self.assertEqual(
    
  432.             options,
    
  433.             [
    
  434.                 {
    
  435.                     "value": "unknown",
    
  436.                     "selected": False,
    
  437.                     "template_name": "django/forms/widgets/select_option.html",
    
  438.                     "label": "Unknown",
    
  439.                     "attrs": {},
    
  440.                     "index": "2",
    
  441.                     "name": "name",
    
  442.                     "type": "select",
    
  443.                     "wrap_label": True,
    
  444.                 }
    
  445.             ],
    
  446.         )
    
  447.         self.assertEqual(index, 2)
    
  448. 
    
  449.     def test_optgroups_integer_choices(self):
    
  450.         """The option 'value' is the same type as what's in `choices`."""
    
  451.         groups = list(
    
  452.             self.widget(choices=[[0, "choice text"]]).optgroups("name", ["vhs"])
    
  453.         )
    
  454.         label, options, index = groups[0]
    
  455.         self.assertEqual(options[0]["value"], 0)
    
  456. 
    
  457.     def test_deepcopy(self):
    
  458.         """
    
  459.         __deepcopy__() should copy all attributes properly (#25085).
    
  460.         """
    
  461.         widget = Select()
    
  462.         obj = copy.deepcopy(widget)
    
  463.         self.assertIsNot(widget, obj)
    
  464.         self.assertEqual(widget.choices, obj.choices)
    
  465.         self.assertIsNot(widget.choices, obj.choices)
    
  466.         self.assertEqual(widget.attrs, obj.attrs)
    
  467.         self.assertIsNot(widget.attrs, obj.attrs)
    
  468. 
    
  469.     def test_doesnt_render_required_when_impossible_to_select_empty_field(self):
    
  470.         widget = self.widget(choices=[("J", "John"), ("P", "Paul")])
    
  471.         self.assertIs(widget.use_required_attribute(initial=None), False)
    
  472. 
    
  473.     def test_renders_required_when_possible_to_select_empty_field_str(self):
    
  474.         widget = self.widget(choices=[("", "select please"), ("P", "Paul")])
    
  475.         self.assertIs(widget.use_required_attribute(initial=None), True)
    
  476. 
    
  477.     def test_renders_required_when_possible_to_select_empty_field_list(self):
    
  478.         widget = self.widget(choices=[["", "select please"], ["P", "Paul"]])
    
  479.         self.assertIs(widget.use_required_attribute(initial=None), True)
    
  480. 
    
  481.     def test_renders_required_when_possible_to_select_empty_field_none(self):
    
  482.         widget = self.widget(choices=[(None, "select please"), ("P", "Paul")])
    
  483.         self.assertIs(widget.use_required_attribute(initial=None), True)
    
  484. 
    
  485.     def test_doesnt_render_required_when_no_choices_are_available(self):
    
  486.         widget = self.widget(choices=[])
    
  487.         self.assertIs(widget.use_required_attribute(initial=None), False)
    
  488. 
    
  489.     def test_fieldset(self):
    
  490.         class TestForm(Form):
    
  491.             template_name = "forms_tests/use_fieldset.html"
    
  492.             field = ChoiceField(widget=self.widget, choices=self.beatles)
    
  493. 
    
  494.         form = TestForm()
    
  495.         self.assertIs(self.widget.use_fieldset, False)
    
  496.         self.assertHTMLEqual(
    
  497.             '<div><label for="id_field">Field:</label>'
    
  498.             '<select name="field" id="id_field">'
    
  499.             '<option value="J">John</option>  '
    
  500.             '<option value="P">Paul</option>'
    
  501.             '<option value="G">George</option>'
    
  502.             '<option value="R">Ringo</option></select></div>',
    
  503.             form.render(),
    
  504.         )