2
0

test_select.py 14 KB

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