test_select.py 16 KB

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