PageRenderTime 346ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/tests/regressiontests/forms/tests/formsets.py

https://code.google.com/p/mango-py/
Python | 911 lines | 886 code | 16 blank | 9 comment | 3 complexity | 405025b1a4dba096029ef748e44c6361 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. # -*- coding: utf-8 -*-
  2. from django.forms import Form, CharField, IntegerField, ValidationError, DateField
  3. from django.forms.formsets import formset_factory, BaseFormSet
  4. from django.utils.unittest import TestCase
  5. class Choice(Form):
  6. choice = CharField()
  7. votes = IntegerField()
  8. # FormSet allows us to use multiple instance of the same form on 1 page. For now,
  9. # the best way to create a FormSet is by using the formset_factory function.
  10. ChoiceFormSet = formset_factory(Choice)
  11. class FavoriteDrinkForm(Form):
  12. name = CharField()
  13. class BaseFavoriteDrinksFormSet(BaseFormSet):
  14. def clean(self):
  15. seen_drinks = []
  16. for drink in self.cleaned_data:
  17. if drink['name'] in seen_drinks:
  18. raise ValidationError('You may only specify a drink once.')
  19. seen_drinks.append(drink['name'])
  20. class EmptyFsetWontValidate(BaseFormSet):
  21. def clean(self):
  22. raise ValidationError("Clean method called")
  23. # Let's define a FormSet that takes a list of favorite drinks, but raises an
  24. # error if there are any duplicates. Used in ``test_clean_hook``,
  25. # ``test_regression_6926`` & ``test_regression_12878``.
  26. FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
  27. formset=BaseFavoriteDrinksFormSet, extra=3)
  28. class FormsFormsetTestCase(TestCase):
  29. def test_basic_formset(self):
  30. # A FormSet constructor takes the same arguments as Form. Let's create a FormSet
  31. # for adding data. By default, it displays 1 blank form. It can display more,
  32. # but we'll look at how to do so later.
  33. formset = ChoiceFormSet(auto_id=False, prefix='choices')
  34. self.assertEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" />
  35. <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
  36. <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>""")
  37. # On thing to note is that there needs to be a special value in the data. This
  38. # value tells the FormSet how many forms were displayed so it can tell how
  39. # many forms it needs to clean and validate. You could use javascript to create
  40. # new forms on the client side, but they won't get validated unless you increment
  41. # the TOTAL_FORMS field appropriately.
  42. data = {
  43. 'choices-TOTAL_FORMS': '1', # the number of forms rendered
  44. 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  45. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  46. 'choices-0-choice': 'Calexico',
  47. 'choices-0-votes': '100',
  48. }
  49. # We treat FormSet pretty much like we would treat a normal Form. FormSet has an
  50. # is_valid method, and a cleaned_data or errors attribute depending on whether all
  51. # the forms passed validation. However, unlike a Form instance, cleaned_data and
  52. # errors will be a list of dicts rather than just a single dict.
  53. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  54. self.assertTrue(formset.is_valid())
  55. self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}])
  56. # If a FormSet was not passed any data, its is_valid method should return False.
  57. formset = ChoiceFormSet()
  58. self.assertFalse(formset.is_valid())
  59. def test_formset_validation(self):
  60. # FormSet instances can also have an error attribute if validation failed for
  61. # any of the forms.
  62. data = {
  63. 'choices-TOTAL_FORMS': '1', # the number of forms rendered
  64. 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  65. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  66. 'choices-0-choice': 'Calexico',
  67. 'choices-0-votes': '',
  68. }
  69. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  70. self.assertFalse(formset.is_valid())
  71. self.assertEqual(formset.errors, [{'votes': [u'This field is required.']}])
  72. def test_formset_initial_data(self):
  73. # We can also prefill a FormSet with existing data by providing an ``initial``
  74. # argument to the constructor. ``initial`` should be a list of dicts. By default,
  75. # an extra blank form is included.
  76. initial = [{'choice': u'Calexico', 'votes': 100}]
  77. formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
  78. form_output = []
  79. for form in formset.forms:
  80. form_output.append(form.as_ul())
  81. self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
  82. <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
  83. <li>Choice: <input type="text" name="choices-1-choice" /></li>
  84. <li>Votes: <input type="text" name="choices-1-votes" /></li>""")
  85. # Let's simulate what would happen if we submitted this form.
  86. data = {
  87. 'choices-TOTAL_FORMS': '2', # the number of forms rendered
  88. 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
  89. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  90. 'choices-0-choice': 'Calexico',
  91. 'choices-0-votes': '100',
  92. 'choices-1-choice': '',
  93. 'choices-1-votes': '',
  94. }
  95. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  96. self.assertTrue(formset.is_valid())
  97. self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}, {}])
  98. def test_second_form_partially_filled(self):
  99. # But the second form was blank! Shouldn't we get some errors? No. If we display
  100. # a form as blank, it's ok for it to be submitted as blank. If we fill out even
  101. # one of the fields of a blank form though, it will be validated. We may want to
  102. # required that at least x number of forms are completed, but we'll show how to
  103. # handle that later.
  104. data = {
  105. 'choices-TOTAL_FORMS': '2', # the number of forms rendered
  106. 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
  107. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  108. 'choices-0-choice': 'Calexico',
  109. 'choices-0-votes': '100',
  110. 'choices-1-choice': 'The Decemberists',
  111. 'choices-1-votes': '', # missing value
  112. }
  113. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  114. self.assertFalse(formset.is_valid())
  115. self.assertEqual(formset.errors, [{}, {'votes': [u'This field is required.']}])
  116. def test_delete_prefilled_data(self):
  117. # If we delete data that was pre-filled, we should get an error. Simply removing
  118. # data from form fields isn't the proper way to delete it. We'll see how to
  119. # handle that case later.
  120. data = {
  121. 'choices-TOTAL_FORMS': '2', # the number of forms rendered
  122. 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
  123. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  124. 'choices-0-choice': '', # deleted value
  125. 'choices-0-votes': '', # deleted value
  126. 'choices-1-choice': '',
  127. 'choices-1-votes': '',
  128. }
  129. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  130. self.assertFalse(formset.is_valid())
  131. self.assertEqual(formset.errors, [{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}])
  132. def test_displaying_more_than_one_blank_form(self):
  133. # Displaying more than 1 blank form ###########################################
  134. # We can also display more than 1 empty form at a time. To do so, pass a
  135. # extra argument to formset_factory.
  136. ChoiceFormSet = formset_factory(Choice, extra=3)
  137. formset = ChoiceFormSet(auto_id=False, prefix='choices')
  138. form_output = []
  139. for form in formset.forms:
  140. form_output.append(form.as_ul())
  141. self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
  142. <li>Votes: <input type="text" name="choices-0-votes" /></li>
  143. <li>Choice: <input type="text" name="choices-1-choice" /></li>
  144. <li>Votes: <input type="text" name="choices-1-votes" /></li>
  145. <li>Choice: <input type="text" name="choices-2-choice" /></li>
  146. <li>Votes: <input type="text" name="choices-2-votes" /></li>""")
  147. # Since we displayed every form as blank, we will also accept them back as blank.
  148. # This may seem a little strange, but later we will show how to require a minimum
  149. # number of forms to be completed.
  150. data = {
  151. 'choices-TOTAL_FORMS': '3', # the number of forms rendered
  152. 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  153. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  154. 'choices-0-choice': '',
  155. 'choices-0-votes': '',
  156. 'choices-1-choice': '',
  157. 'choices-1-votes': '',
  158. 'choices-2-choice': '',
  159. 'choices-2-votes': '',
  160. }
  161. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  162. self.assertTrue(formset.is_valid())
  163. self.assertEqual([form.cleaned_data for form in formset.forms], [{}, {}, {}])
  164. def test_single_form_completed(self):
  165. # We can just fill out one of the forms.
  166. data = {
  167. 'choices-TOTAL_FORMS': '3', # the number of forms rendered
  168. 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  169. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  170. 'choices-0-choice': 'Calexico',
  171. 'choices-0-votes': '100',
  172. 'choices-1-choice': '',
  173. 'choices-1-votes': '',
  174. 'choices-2-choice': '',
  175. 'choices-2-votes': '',
  176. }
  177. ChoiceFormSet = formset_factory(Choice, extra=3)
  178. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  179. self.assertTrue(formset.is_valid())
  180. self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'choice': u'Calexico'}, {}, {}])
  181. def test_second_form_partially_filled_2(self):
  182. # And once again, if we try to partially complete a form, validation will fail.
  183. data = {
  184. 'choices-TOTAL_FORMS': '3', # the number of forms rendered
  185. 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  186. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  187. 'choices-0-choice': 'Calexico',
  188. 'choices-0-votes': '100',
  189. 'choices-1-choice': 'The Decemberists',
  190. 'choices-1-votes': '', # missing value
  191. 'choices-2-choice': '',
  192. 'choices-2-votes': '',
  193. }
  194. ChoiceFormSet = formset_factory(Choice, extra=3)
  195. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  196. self.assertFalse(formset.is_valid())
  197. self.assertEqual(formset.errors, [{}, {'votes': [u'This field is required.']}, {}])
  198. def test_more_initial_data(self):
  199. # The extra argument also works when the formset is pre-filled with initial
  200. # data.
  201. data = {
  202. 'choices-TOTAL_FORMS': '3', # the number of forms rendered
  203. 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  204. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  205. 'choices-0-choice': 'Calexico',
  206. 'choices-0-votes': '100',
  207. 'choices-1-choice': '',
  208. 'choices-1-votes': '', # missing value
  209. 'choices-2-choice': '',
  210. 'choices-2-votes': '',
  211. }
  212. initial = [{'choice': u'Calexico', 'votes': 100}]
  213. ChoiceFormSet = formset_factory(Choice, extra=3)
  214. formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
  215. form_output = []
  216. for form in formset.forms:
  217. form_output.append(form.as_ul())
  218. self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
  219. <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
  220. <li>Choice: <input type="text" name="choices-1-choice" /></li>
  221. <li>Votes: <input type="text" name="choices-1-votes" /></li>
  222. <li>Choice: <input type="text" name="choices-2-choice" /></li>
  223. <li>Votes: <input type="text" name="choices-2-votes" /></li>
  224. <li>Choice: <input type="text" name="choices-3-choice" /></li>
  225. <li>Votes: <input type="text" name="choices-3-votes" /></li>""")
  226. # Make sure retrieving an empty form works, and it shows up in the form list
  227. self.assertTrue(formset.empty_form.empty_permitted)
  228. self.assertEqual(formset.empty_form.as_ul(), """<li>Choice: <input type="text" name="choices-__prefix__-choice" /></li>
  229. <li>Votes: <input type="text" name="choices-__prefix__-votes" /></li>""")
  230. def test_formset_with_deletion(self):
  231. # FormSets with deletion ######################################################
  232. # We can easily add deletion ability to a FormSet with an argument to
  233. # formset_factory. This will add a boolean field to each form instance. When
  234. # that boolean field is True, the form will be in formset.deleted_forms
  235. ChoiceFormSet = formset_factory(Choice, can_delete=True)
  236. initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
  237. formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
  238. form_output = []
  239. for form in formset.forms:
  240. form_output.append(form.as_ul())
  241. self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
  242. <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
  243. <li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
  244. <li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
  245. <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
  246. <li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
  247. <li>Choice: <input type="text" name="choices-2-choice" /></li>
  248. <li>Votes: <input type="text" name="choices-2-votes" /></li>
  249. <li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>""")
  250. # To delete something, we just need to set that form's special delete field to
  251. # 'on'. Let's go ahead and delete Fergie.
  252. data = {
  253. 'choices-TOTAL_FORMS': '3', # the number of forms rendered
  254. 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
  255. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  256. 'choices-0-choice': 'Calexico',
  257. 'choices-0-votes': '100',
  258. 'choices-0-DELETE': '',
  259. 'choices-1-choice': 'Fergie',
  260. 'choices-1-votes': '900',
  261. 'choices-1-DELETE': 'on',
  262. 'choices-2-choice': '',
  263. 'choices-2-votes': '',
  264. 'choices-2-DELETE': '',
  265. }
  266. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  267. self.assertTrue(formset.is_valid())
  268. self.assertEqual([form.cleaned_data for form in formset.forms], [{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}])
  269. self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}])
  270. # If we fill a form with something and then we check the can_delete checkbox for
  271. # that form, that form's errors should not make the entire formset invalid since
  272. # it's going to be deleted.
  273. class CheckForm(Form):
  274. field = IntegerField(min_value=100)
  275. data = {
  276. 'check-TOTAL_FORMS': '3', # the number of forms rendered
  277. 'check-INITIAL_FORMS': '2', # the number of forms with initial data
  278. 'check-MAX_NUM_FORMS': '0', # max number of forms
  279. 'check-0-field': '200',
  280. 'check-0-DELETE': '',
  281. 'check-1-field': '50',
  282. 'check-1-DELETE': 'on',
  283. 'check-2-field': '',
  284. 'check-2-DELETE': '',
  285. }
  286. CheckFormSet = formset_factory(CheckForm, can_delete=True)
  287. formset = CheckFormSet(data, prefix='check')
  288. self.assertTrue(formset.is_valid())
  289. # If we remove the deletion flag now we will have our validation back.
  290. data['check-1-DELETE'] = ''
  291. formset = CheckFormSet(data, prefix='check')
  292. self.assertFalse(formset.is_valid())
  293. # Should be able to get deleted_forms from a valid formset even if a
  294. # deleted form would have been invalid.
  295. class Person(Form):
  296. name = CharField()
  297. PeopleForm = formset_factory(
  298. form=Person,
  299. can_delete=True)
  300. p = PeopleForm(
  301. {'form-0-name': u'', 'form-0-DELETE': u'on', # no name!
  302. 'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
  303. 'form-MAX_NUM_FORMS': 1})
  304. self.assertTrue(p.is_valid())
  305. self.assertEqual(len(p.deleted_forms), 1)
  306. def test_formsets_with_ordering(self):
  307. # FormSets with ordering ######################################################
  308. # We can also add ordering ability to a FormSet with an argument to
  309. # formset_factory. This will add a integer field to each form instance. When
  310. # form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct
  311. # order specified by the ordering fields. If a number is duplicated in the set
  312. # of ordering fields, for instance form 0 and form 3 are both marked as 1, then
  313. # the form index used as a secondary ordering criteria. In order to put
  314. # something at the front of the list, you'd need to set it's order to 0.
  315. ChoiceFormSet = formset_factory(Choice, can_order=True)
  316. initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}]
  317. formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
  318. form_output = []
  319. for form in formset.forms:
  320. form_output.append(form.as_ul())
  321. self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
  322. <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
  323. <li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
  324. <li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
  325. <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
  326. <li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
  327. <li>Choice: <input type="text" name="choices-2-choice" /></li>
  328. <li>Votes: <input type="text" name="choices-2-votes" /></li>
  329. <li>Order: <input type="text" name="choices-2-ORDER" /></li>""")
  330. data = {
  331. 'choices-TOTAL_FORMS': '3', # the number of forms rendered
  332. 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
  333. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  334. 'choices-0-choice': 'Calexico',
  335. 'choices-0-votes': '100',
  336. 'choices-0-ORDER': '1',
  337. 'choices-1-choice': 'Fergie',
  338. 'choices-1-votes': '900',
  339. 'choices-1-ORDER': '2',
  340. 'choices-2-choice': 'The Decemberists',
  341. 'choices-2-votes': '500',
  342. 'choices-2-ORDER': '0',
  343. }
  344. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  345. self.assertTrue(formset.is_valid())
  346. form_output = []
  347. for form in formset.ordered_forms:
  348. form_output.append(form.cleaned_data)
  349. self.assertEqual(form_output, [
  350. {'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'},
  351. {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'},
  352. {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'},
  353. ])
  354. def test_empty_ordered_fields(self):
  355. # Ordering fields are allowed to be left blank, and if they *are* left blank,
  356. # they will be sorted below everything else.
  357. data = {
  358. 'choices-TOTAL_FORMS': '4', # the number of forms rendered
  359. 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
  360. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  361. 'choices-0-choice': 'Calexico',
  362. 'choices-0-votes': '100',
  363. 'choices-0-ORDER': '1',
  364. 'choices-1-choice': 'Fergie',
  365. 'choices-1-votes': '900',
  366. 'choices-1-ORDER': '2',
  367. 'choices-2-choice': 'The Decemberists',
  368. 'choices-2-votes': '500',
  369. 'choices-2-ORDER': '',
  370. 'choices-3-choice': 'Basia Bulat',
  371. 'choices-3-votes': '50',
  372. 'choices-3-ORDER': '',
  373. }
  374. ChoiceFormSet = formset_factory(Choice, can_order=True)
  375. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  376. self.assertTrue(formset.is_valid())
  377. form_output = []
  378. for form in formset.ordered_forms:
  379. form_output.append(form.cleaned_data)
  380. self.assertEqual(form_output, [
  381. {'votes': 100, 'ORDER': 1, 'choice': u'Calexico'},
  382. {'votes': 900, 'ORDER': 2, 'choice': u'Fergie'},
  383. {'votes': 500, 'ORDER': None, 'choice': u'The Decemberists'},
  384. {'votes': 50, 'ORDER': None, 'choice': u'Basia Bulat'},
  385. ])
  386. def test_ordering_blank_fieldsets(self):
  387. # Ordering should work with blank fieldsets.
  388. data = {
  389. 'choices-TOTAL_FORMS': '3', # the number of forms rendered
  390. 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  391. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  392. }
  393. ChoiceFormSet = formset_factory(Choice, can_order=True)
  394. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  395. self.assertTrue(formset.is_valid())
  396. form_output = []
  397. for form in formset.ordered_forms:
  398. form_output.append(form.cleaned_data)
  399. self.assertEqual(form_output, [])
  400. def test_formset_with_ordering_and_deletion(self):
  401. # FormSets with ordering + deletion ###########################################
  402. # Let's try throwing ordering and deletion into the same form.
  403. ChoiceFormSet = formset_factory(Choice, can_order=True, can_delete=True)
  404. initial = [
  405. {'choice': u'Calexico', 'votes': 100},
  406. {'choice': u'Fergie', 'votes': 900},
  407. {'choice': u'The Decemberists', 'votes': 500},
  408. ]
  409. formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices')
  410. form_output = []
  411. for form in formset.forms:
  412. form_output.append(form.as_ul())
  413. self.assertEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
  414. <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
  415. <li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
  416. <li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
  417. <li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
  418. <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
  419. <li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
  420. <li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
  421. <li>Choice: <input type="text" name="choices-2-choice" value="The Decemberists" /></li>
  422. <li>Votes: <input type="text" name="choices-2-votes" value="500" /></li>
  423. <li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
  424. <li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
  425. <li>Choice: <input type="text" name="choices-3-choice" /></li>
  426. <li>Votes: <input type="text" name="choices-3-votes" /></li>
  427. <li>Order: <input type="text" name="choices-3-ORDER" /></li>
  428. <li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li>""")
  429. # Let's delete Fergie, and put The Decemberists ahead of Calexico.
  430. data = {
  431. 'choices-TOTAL_FORMS': '4', # the number of forms rendered
  432. 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
  433. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  434. 'choices-0-choice': 'Calexico',
  435. 'choices-0-votes': '100',
  436. 'choices-0-ORDER': '1',
  437. 'choices-0-DELETE': '',
  438. 'choices-1-choice': 'Fergie',
  439. 'choices-1-votes': '900',
  440. 'choices-1-ORDER': '2',
  441. 'choices-1-DELETE': 'on',
  442. 'choices-2-choice': 'The Decemberists',
  443. 'choices-2-votes': '500',
  444. 'choices-2-ORDER': '0',
  445. 'choices-2-DELETE': '',
  446. 'choices-3-choice': '',
  447. 'choices-3-votes': '',
  448. 'choices-3-ORDER': '',
  449. 'choices-3-DELETE': '',
  450. }
  451. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  452. self.assertTrue(formset.is_valid())
  453. form_output = []
  454. for form in formset.ordered_forms:
  455. form_output.append(form.cleaned_data)
  456. self.assertEqual(form_output, [
  457. {'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'},
  458. {'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'},
  459. ])
  460. self.assertEqual([form.cleaned_data for form in formset.deleted_forms], [{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}])
  461. def test_invalid_deleted_form_with_ordering(self):
  462. # Should be able to get ordered forms from a valid formset even if a
  463. # deleted form would have been invalid.
  464. class Person(Form):
  465. name = CharField()
  466. PeopleForm = formset_factory(form=Person, can_delete=True, can_order=True)
  467. p = PeopleForm({
  468. 'form-0-name': u'',
  469. 'form-0-DELETE': u'on', # no name!
  470. 'form-TOTAL_FORMS': 1,
  471. 'form-INITIAL_FORMS': 1,
  472. 'form-MAX_NUM_FORMS': 1
  473. })
  474. self.assertTrue(p.is_valid())
  475. self.assertEqual(p.ordered_forms, [])
  476. def test_clean_hook(self):
  477. # FormSet clean hook ##########################################################
  478. # FormSets have a hook for doing extra validation that shouldn't be tied to any
  479. # particular form. It follows the same pattern as the clean hook on Forms.
  480. # We start out with a some duplicate data.
  481. data = {
  482. 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
  483. 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
  484. 'drinks-MAX_NUM_FORMS': '0', # max number of forms
  485. 'drinks-0-name': 'Gin and Tonic',
  486. 'drinks-1-name': 'Gin and Tonic',
  487. }
  488. formset = FavoriteDrinksFormSet(data, prefix='drinks')
  489. self.assertFalse(formset.is_valid())
  490. # Any errors raised by formset.clean() are available via the
  491. # formset.non_form_errors() method.
  492. for error in formset.non_form_errors():
  493. self.assertEqual(str(error), 'You may only specify a drink once.')
  494. # Make sure we didn't break the valid case.
  495. data = {
  496. 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
  497. 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
  498. 'drinks-MAX_NUM_FORMS': '0', # max number of forms
  499. 'drinks-0-name': 'Gin and Tonic',
  500. 'drinks-1-name': 'Bloody Mary',
  501. }
  502. formset = FavoriteDrinksFormSet(data, prefix='drinks')
  503. self.assertTrue(formset.is_valid())
  504. self.assertEqual(formset.non_form_errors(), [])
  505. def test_limiting_max_forms(self):
  506. # Limiting the maximum number of forms ########################################
  507. # Base case for max_num.
  508. # When not passed, max_num will take its default value of None, i.e. unlimited
  509. # number of forms, only controlled by the value of the extra parameter.
  510. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3)
  511. formset = LimitedFavoriteDrinkFormSet()
  512. form_output = []
  513. for form in formset.forms:
  514. form_output.append(str(form))
  515. self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
  516. <tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>
  517. <tr><th><label for="id_form-2-name">Name:</label></th><td><input type="text" name="form-2-name" id="id_form-2-name" /></td></tr>""")
  518. # If max_num is 0 then no form is rendered at all.
  519. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=0)
  520. formset = LimitedFavoriteDrinkFormSet()
  521. form_output = []
  522. for form in formset.forms:
  523. form_output.append(str(form))
  524. self.assertEqual('\n'.join(form_output), "")
  525. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2)
  526. formset = LimitedFavoriteDrinkFormSet()
  527. form_output = []
  528. for form in formset.forms:
  529. form_output.append(str(form))
  530. self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>
  531. <tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>""")
  532. # Ensure that max_num has no effect when extra is less than max_num.
  533. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
  534. formset = LimitedFavoriteDrinkFormSet()
  535. form_output = []
  536. for form in formset.forms:
  537. form_output.append(str(form))
  538. self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>""")
  539. def test_max_num_with_initial_data(self):
  540. # max_num with initial data
  541. # When not passed, max_num will take its default value of None, i.e. unlimited
  542. # number of forms, only controlled by the values of the initial and extra
  543. # parameters.
  544. initial = [
  545. {'name': 'Fernet and Coke'},
  546. ]
  547. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1)
  548. formset = LimitedFavoriteDrinkFormSet(initial=initial)
  549. form_output = []
  550. for form in formset.forms:
  551. form_output.append(str(form))
  552. self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Fernet and Coke" id="id_form-0-name" /></td></tr>
  553. <tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>""")
  554. def test_max_num_zero(self):
  555. # If max_num is 0 then no form is rendered at all, even if extra and initial
  556. # are specified.
  557. initial = [
  558. {'name': 'Fernet and Coke'},
  559. {'name': 'Bloody Mary'},
  560. ]
  561. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0)
  562. formset = LimitedFavoriteDrinkFormSet(initial=initial)
  563. form_output = []
  564. for form in formset.forms:
  565. form_output.append(str(form))
  566. self.assertEqual('\n'.join(form_output), "")
  567. def test_more_initial_than_max_num(self):
  568. # More initial forms than max_num will result in only the first max_num of
  569. # them to be displayed with no extra forms.
  570. initial = [
  571. {'name': 'Gin Tonic'},
  572. {'name': 'Bloody Mary'},
  573. {'name': 'Jack and Coke'},
  574. ]
  575. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2)
  576. formset = LimitedFavoriteDrinkFormSet(initial=initial)
  577. form_output = []
  578. for form in formset.forms:
  579. form_output.append(str(form))
  580. self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
  581. <tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" value="Bloody Mary" id="id_form-1-name" /></td></tr>""")
  582. # One form from initial and extra=3 with max_num=2 should result in the one
  583. # initial form and one extra.
  584. initial = [
  585. {'name': 'Gin Tonic'},
  586. ]
  587. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=2)
  588. formset = LimitedFavoriteDrinkFormSet(initial=initial)
  589. form_output = []
  590. for form in formset.forms:
  591. form_output.append(str(form))
  592. self.assertEqual('\n'.join(form_output), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name" /></td></tr>
  593. <tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>""")
  594. def test_regression_6926(self):
  595. # Regression test for #6926 ##################################################
  596. # Make sure the management form has the correct prefix.
  597. formset = FavoriteDrinksFormSet()
  598. self.assertEqual(formset.management_form.prefix, 'form')
  599. data = {
  600. 'form-TOTAL_FORMS': '2',
  601. 'form-INITIAL_FORMS': '0',
  602. 'form-MAX_NUM_FORMS': '0',
  603. }
  604. formset = FavoriteDrinksFormSet(data=data)
  605. self.assertEqual(formset.management_form.prefix, 'form')
  606. formset = FavoriteDrinksFormSet(initial={})
  607. self.assertEqual(formset.management_form.prefix, 'form')
  608. def test_regression_12878(self):
  609. # Regression test for #12878 #################################################
  610. data = {
  611. 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
  612. 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
  613. 'drinks-MAX_NUM_FORMS': '0', # max number of forms
  614. 'drinks-0-name': 'Gin and Tonic',
  615. 'drinks-1-name': 'Gin and Tonic',
  616. }
  617. formset = FavoriteDrinksFormSet(data, prefix='drinks')
  618. self.assertFalse(formset.is_valid())
  619. self.assertEqual(formset.non_form_errors(), [u'You may only specify a drink once.'])
  620. def test_formset_iteration(self):
  621. # Regression tests for #16455 -- formset instances are iterable
  622. ChoiceFormset = formset_factory(Choice, extra=3)
  623. formset = ChoiceFormset()
  624. # confirm iterated formset yields formset.forms
  625. forms = list(formset)
  626. self.assertEqual(forms, formset.forms)
  627. self.assertEqual(len(formset), len(forms))
  628. # confirm indexing of formset
  629. self.assertEqual(formset[0], forms[0])
  630. try:
  631. formset[3]
  632. self.fail('Requesting an invalid formset index should raise an exception')
  633. except IndexError:
  634. pass
  635. # Formets can override the default iteration order
  636. class BaseReverseFormSet(BaseFormSet):
  637. def __iter__(self):
  638. for form in reversed(self.forms):
  639. yield form
  640. ReverseChoiceFormset = formset_factory(Choice, BaseReverseFormSet, extra=3)
  641. reverse_formset = ReverseChoiceFormset()
  642. # confirm that __iter__ modifies rendering order
  643. # compare forms from "reverse" formset with forms from original formset
  644. self.assertEqual(str(reverse_formset[0]), str(forms[-1]))
  645. self.assertEqual(str(reverse_formset[1]), str(forms[-2]))
  646. self.assertEqual(len(reverse_formset), len(forms))
  647. data = {
  648. 'choices-TOTAL_FORMS': '1', # the number of forms rendered
  649. 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
  650. 'choices-MAX_NUM_FORMS': '0', # max number of forms
  651. 'choices-0-choice': 'Calexico',
  652. 'choices-0-votes': '100',
  653. }
  654. class Choice(Form):
  655. choice = CharField()
  656. votes = IntegerField()
  657. ChoiceFormSet = formset_factory(Choice)
  658. class FormsetAsFooTests(TestCase):
  659. def test_as_table(self):
  660. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  661. self.assertEqual(formset.as_table(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
  662. <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
  663. <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" value="100" /></td></tr>""")
  664. def test_as_p(self):
  665. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  666. self.assertEqual(formset.as_p(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
  667. <p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
  668. <p>Votes: <input type="text" name="choices-0-votes" value="100" /></p>""")
  669. def test_as_ul(self):
  670. formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
  671. self.assertEqual(formset.as_ul(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
  672. <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
  673. <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
  674. # Regression test for #11418 #################################################
  675. class ArticleForm(Form):
  676. title = CharField()
  677. pub_date = DateField()
  678. ArticleFormSet = formset_factory(ArticleForm)
  679. class TestIsBoundBehavior(TestCase):
  680. def test_no_data_raises_validation_error(self):
  681. self.assertRaises(ValidationError, ArticleFormSet, {})
  682. def test_with_management_data_attrs_work_fine(self):
  683. data = {
  684. 'form-TOTAL_FORMS': u'1',
  685. 'form-INITIAL_FORMS': u'0',
  686. }
  687. formset = ArticleFormSet(data)
  688. self.assertEqual(0, formset.initial_form_count())
  689. self.assertEqual(1, formset.total_form_count())
  690. self.assertTrue(formset.is_bound)
  691. self.assertTrue(formset.forms[0].is_bound)
  692. self.assertTrue(formset.is_valid())
  693. self.assertTrue(formset.forms[0].is_valid())
  694. self.assertEqual([{}], formset.cleaned_data)
  695. def test_form_errors_are_cought_by_formset(self):
  696. data = {
  697. 'form-TOTAL_FORMS': u'2',
  698. 'form-INITIAL_FORMS': u'0',
  699. 'form-0-title': u'Test',
  700. 'form-0-pub_date': u'1904-06-16',
  701. 'form-1-title': u'Test',
  702. 'form-1-pub_date': u'', # <-- this date is missing but required
  703. }
  704. formset = ArticleFormSet(data)
  705. self.assertFalse(formset.is_valid())
  706. self.assertEqual([{}, {'pub_date': [u'This field is required.']}], formset.errors)
  707. def test_empty_forms_are_unbound(self):
  708. data = {
  709. 'form-TOTAL_FORMS': u'1',
  710. 'form-INITIAL_FORMS': u'0',
  711. 'form-0-title': u'Test',
  712. 'form-0-pub_date': u'1904-06-16',
  713. }
  714. unbound_formset = ArticleFormSet()
  715. bound_formset = ArticleFormSet(data)
  716. empty_forms = []
  717. empty_forms.append(unbound_formset.empty_form)
  718. empty_forms.append(bound_formset.empty_form)
  719. # Empty forms should be unbound
  720. self.assertFalse(empty_forms[0].is_bound)
  721. self.assertFalse(empty_forms[1].is_bound)
  722. # The empty forms should be equal.
  723. self.assertEqual(empty_forms[0].as_p(), empty_forms[1].as_p())
  724. class TestEmptyFormSet(TestCase):
  725. "Test that an empty formset still calls clean()"
  726. def test_empty_formset_is_valid(self):
  727. EmptyFsetWontValidateFormset = formset_factory(FavoriteDrinkForm, extra=0, formset=EmptyFsetWontValidate)
  728. formset = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'0'},prefix="form")
  729. formset2 = EmptyFsetWontValidateFormset(data={'form-INITIAL_FORMS':'0', 'form-TOTAL_FORMS':'1', 'form-0-name':'bah' },prefix="form")
  730. self.assertFalse(formset.is_valid())
  731. self.assertFalse(formset2.is_valid())