test_select.py 14 KB

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