test_radioselect.py 17 KB

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