tests.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. from __future__ import absolute_import, unicode_literals
  2. from django import forms
  3. from django.forms.formsets import BaseFormSet, DELETION_FIELD_NAME
  4. from django.forms.util import ErrorDict, ErrorList
  5. from django.forms.models import modelform_factory, inlineformset_factory, modelformset_factory, BaseModelFormSet
  6. from django.test import TestCase
  7. from django.utils import six
  8. from .models import User, UserSite, Restaurant, Manager, Network, Host
  9. class InlineFormsetTests(TestCase):
  10. def test_formset_over_to_field(self):
  11. "A formset over a ForeignKey with a to_field can be saved. Regression for #10243"
  12. Form = modelform_factory(User, fields="__all__")
  13. FormSet = inlineformset_factory(User, UserSite, fields="__all__")
  14. # Instantiate the Form and FormSet to prove
  15. # you can create a form with no data
  16. form = Form()
  17. form_set = FormSet(instance=User())
  18. # Now create a new User and UserSite instance
  19. data = {
  20. 'serial': '1',
  21. 'username': 'apollo13',
  22. 'usersite_set-TOTAL_FORMS': '1',
  23. 'usersite_set-INITIAL_FORMS': '0',
  24. 'usersite_set-MAX_NUM_FORMS': '0',
  25. 'usersite_set-0-data': '10',
  26. 'usersite_set-0-user': 'apollo13'
  27. }
  28. user = User()
  29. form = Form(data)
  30. if form.is_valid():
  31. user = form.save()
  32. else:
  33. self.fail('Errors found on form:%s' % form_set)
  34. form_set = FormSet(data, instance=user)
  35. if form_set.is_valid():
  36. form_set.save()
  37. usersite = UserSite.objects.all().values()
  38. self.assertEqual(usersite[0]['data'], 10)
  39. self.assertEqual(usersite[0]['user_id'], 'apollo13')
  40. else:
  41. self.fail('Errors found on formset:%s' % form_set.errors)
  42. # Now update the UserSite instance
  43. data = {
  44. 'usersite_set-TOTAL_FORMS': '1',
  45. 'usersite_set-INITIAL_FORMS': '1',
  46. 'usersite_set-MAX_NUM_FORMS': '0',
  47. 'usersite_set-0-id': six.text_type(usersite[0]['id']),
  48. 'usersite_set-0-data': '11',
  49. 'usersite_set-0-user': 'apollo13'
  50. }
  51. form_set = FormSet(data, instance=user)
  52. if form_set.is_valid():
  53. form_set.save()
  54. usersite = UserSite.objects.all().values()
  55. self.assertEqual(usersite[0]['data'], 11)
  56. self.assertEqual(usersite[0]['user_id'], 'apollo13')
  57. else:
  58. self.fail('Errors found on formset:%s' % form_set.errors)
  59. # Now add a new UserSite instance
  60. data = {
  61. 'usersite_set-TOTAL_FORMS': '2',
  62. 'usersite_set-INITIAL_FORMS': '1',
  63. 'usersite_set-MAX_NUM_FORMS': '0',
  64. 'usersite_set-0-id': six.text_type(usersite[0]['id']),
  65. 'usersite_set-0-data': '11',
  66. 'usersite_set-0-user': 'apollo13',
  67. 'usersite_set-1-data': '42',
  68. 'usersite_set-1-user': 'apollo13'
  69. }
  70. form_set = FormSet(data, instance=user)
  71. if form_set.is_valid():
  72. form_set.save()
  73. usersite = UserSite.objects.all().values().order_by('data')
  74. self.assertEqual(usersite[0]['data'], 11)
  75. self.assertEqual(usersite[0]['user_id'], 'apollo13')
  76. self.assertEqual(usersite[1]['data'], 42)
  77. self.assertEqual(usersite[1]['user_id'], 'apollo13')
  78. else:
  79. self.fail('Errors found on formset:%s' % form_set.errors)
  80. def test_formset_over_inherited_model(self):
  81. "A formset over a ForeignKey with a to_field can be saved. Regression for #11120"
  82. Form = modelform_factory(Restaurant, fields="__all__")
  83. FormSet = inlineformset_factory(Restaurant, Manager, fields="__all__")
  84. # Instantiate the Form and FormSet to prove
  85. # you can create a form with no data
  86. form = Form()
  87. form_set = FormSet(instance=Restaurant())
  88. # Now create a new Restaurant and Manager instance
  89. data = {
  90. 'name': "Guido's House of Pasta",
  91. 'manager_set-TOTAL_FORMS': '1',
  92. 'manager_set-INITIAL_FORMS': '0',
  93. 'manager_set-MAX_NUM_FORMS': '0',
  94. 'manager_set-0-name': 'Guido Van Rossum'
  95. }
  96. restaurant = User()
  97. form = Form(data)
  98. if form.is_valid():
  99. restaurant = form.save()
  100. else:
  101. self.fail('Errors found on form:%s' % form_set)
  102. form_set = FormSet(data, instance=restaurant)
  103. if form_set.is_valid():
  104. form_set.save()
  105. manager = Manager.objects.all().values()
  106. self.assertEqual(manager[0]['name'], 'Guido Van Rossum')
  107. else:
  108. self.fail('Errors found on formset:%s' % form_set.errors)
  109. # Now update the Manager instance
  110. data = {
  111. 'manager_set-TOTAL_FORMS': '1',
  112. 'manager_set-INITIAL_FORMS': '1',
  113. 'manager_set-MAX_NUM_FORMS': '0',
  114. 'manager_set-0-id': six.text_type(manager[0]['id']),
  115. 'manager_set-0-name': 'Terry Gilliam'
  116. }
  117. form_set = FormSet(data, instance=restaurant)
  118. if form_set.is_valid():
  119. form_set.save()
  120. manager = Manager.objects.all().values()
  121. self.assertEqual(manager[0]['name'], 'Terry Gilliam')
  122. else:
  123. self.fail('Errors found on formset:%s' % form_set.errors)
  124. # Now add a new Manager instance
  125. data = {
  126. 'manager_set-TOTAL_FORMS': '2',
  127. 'manager_set-INITIAL_FORMS': '1',
  128. 'manager_set-MAX_NUM_FORMS': '0',
  129. 'manager_set-0-id': six.text_type(manager[0]['id']),
  130. 'manager_set-0-name': 'Terry Gilliam',
  131. 'manager_set-1-name': 'John Cleese'
  132. }
  133. form_set = FormSet(data, instance=restaurant)
  134. if form_set.is_valid():
  135. form_set.save()
  136. manager = Manager.objects.all().values().order_by('name')
  137. self.assertEqual(manager[0]['name'], 'John Cleese')
  138. self.assertEqual(manager[1]['name'], 'Terry Gilliam')
  139. else:
  140. self.fail('Errors found on formset:%s' % form_set.errors)
  141. def test_formset_with_none_instance(self):
  142. "A formset with instance=None can be created. Regression for #11872"
  143. Form = modelform_factory(User, fields="__all__")
  144. FormSet = inlineformset_factory(User, UserSite, fields="__all__")
  145. # Instantiate the Form and FormSet to prove
  146. # you can create a formset with an instance of None
  147. form = Form(instance=None)
  148. formset = FormSet(instance=None)
  149. def test_empty_fields_on_modelformset(self):
  150. "No fields passed to modelformset_factory should result in no fields on returned forms except for the id. See #14119."
  151. UserFormSet = modelformset_factory(User, fields=())
  152. formset = UserFormSet()
  153. for form in formset.forms:
  154. self.assertTrue('id' in form.fields)
  155. self.assertEqual(len(form.fields), 1)
  156. def test_save_as_new_with_new_inlines(self):
  157. """
  158. Existing and new inlines are saved with save_as_new.
  159. Regression for #14938.
  160. """
  161. efnet = Network.objects.create(name="EFNet")
  162. host1 = Host.objects.create(hostname="irc.he.net", network=efnet)
  163. HostFormSet = inlineformset_factory(Network, Host, fields="__all__")
  164. # Add a new host, modify previous host, and save-as-new
  165. data = {
  166. 'host_set-TOTAL_FORMS': '2',
  167. 'host_set-INITIAL_FORMS': '1',
  168. 'host_set-MAX_NUM_FORMS': '0',
  169. 'host_set-0-id': six.text_type(host1.id),
  170. 'host_set-0-hostname': 'tranquility.hub.dal.net',
  171. 'host_set-1-hostname': 'matrix.de.eu.dal.net'
  172. }
  173. # To save a formset as new, it needs a new hub instance
  174. dalnet = Network.objects.create(name="DALnet")
  175. formset = HostFormSet(data, instance=dalnet, save_as_new=True)
  176. self.assertTrue(formset.is_valid())
  177. formset.save()
  178. self.assertQuerysetEqual(
  179. dalnet.host_set.order_by("hostname"),
  180. ["<Host: matrix.de.eu.dal.net>", "<Host: tranquility.hub.dal.net>"]
  181. )
  182. def test_initial_data(self):
  183. user = User.objects.create(username="bibi", serial=1)
  184. UserSite.objects.create(user=user, data=7)
  185. FormSet = inlineformset_factory(User, UserSite, extra=2, fields="__all__")
  186. formset = FormSet(instance=user, initial=[{'data': 41}, {'data': 42}])
  187. self.assertEqual(formset.forms[0].initial['data'], 7)
  188. self.assertEqual(formset.extra_forms[0].initial['data'], 41)
  189. self.assertTrue('value="42"' in formset.extra_forms[1].as_p())
  190. class FormsetTests(TestCase):
  191. def test_error_class(self):
  192. '''
  193. Test the type of Formset and Form error attributes
  194. '''
  195. Formset = modelformset_factory(User, fields="__all__")
  196. data = {
  197. 'form-TOTAL_FORMS': '2',
  198. 'form-INITIAL_FORMS': '0',
  199. 'form-MAX_NUM_FORMS': '0',
  200. 'form-0-id': '',
  201. 'form-0-username': 'apollo13',
  202. 'form-0-serial': '1',
  203. 'form-1-id': '',
  204. 'form-1-username': 'apollo13',
  205. 'form-1-serial': '2',
  206. }
  207. formset = Formset(data)
  208. # check if the returned error classes are correct
  209. # note: formset.errors returns a list as documented
  210. self.assertTrue(isinstance(formset.errors, list))
  211. self.assertTrue(isinstance(formset.non_form_errors(), ErrorList))
  212. for form in formset.forms:
  213. self.assertTrue(isinstance(form.errors, ErrorDict))
  214. self.assertTrue(isinstance(form.non_field_errors(), ErrorList))
  215. def test_initial_data(self):
  216. User.objects.create(username="bibi", serial=1)
  217. Formset = modelformset_factory(User, fields="__all__", extra=2)
  218. formset = Formset(initial=[{'username': 'apollo11'}, {'username': 'apollo12'}])
  219. self.assertEqual(formset.forms[0].initial['username'], "bibi")
  220. self.assertEqual(formset.extra_forms[0].initial['username'], "apollo11")
  221. self.assertTrue('value="apollo12"' in formset.extra_forms[1].as_p())
  222. def test_extraneous_query_is_not_run(self):
  223. Formset = modelformset_factory(Network, fields="__all__")
  224. data = {'test-TOTAL_FORMS': '1',
  225. 'test-INITIAL_FORMS': '0',
  226. 'test-MAX_NUM_FORMS': '',
  227. 'test-0-name': 'Random Place', }
  228. with self.assertNumQueries(1):
  229. formset = Formset(data, prefix="test")
  230. formset.save()
  231. class CustomWidget(forms.widgets.TextInput):
  232. pass
  233. class UserSiteForm(forms.ModelForm):
  234. class Meta:
  235. model = UserSite
  236. fields = "__all__"
  237. widgets = {
  238. 'id': CustomWidget,
  239. 'data': CustomWidget,
  240. }
  241. localized_fields = ('data',)
  242. class Callback(object):
  243. def __init__(self):
  244. self.log = []
  245. def __call__(self, db_field, **kwargs):
  246. self.log.append((db_field, kwargs))
  247. return db_field.formfield(**kwargs)
  248. class FormfieldCallbackTests(TestCase):
  249. """
  250. Regression for #13095 and #17683: Using base forms with widgets
  251. defined in Meta should not raise errors and BaseModelForm should respect
  252. the specified pk widget.
  253. """
  254. def test_inlineformset_factory_default(self):
  255. Formset = inlineformset_factory(User, UserSite, form=UserSiteForm, fields="__all__")
  256. form = Formset().forms[0]
  257. self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
  258. self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
  259. self.assertFalse(form.fields['id'].localize)
  260. self.assertTrue(form.fields['data'].localize)
  261. def test_modelformset_factory_default(self):
  262. Formset = modelformset_factory(UserSite, form=UserSiteForm)
  263. form = Formset().forms[0]
  264. self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
  265. self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
  266. self.assertFalse(form.fields['id'].localize)
  267. self.assertTrue(form.fields['data'].localize)
  268. def assertCallbackCalled(self, callback):
  269. id_field, user_field, data_field = UserSite._meta.fields
  270. expected_log = [
  271. (id_field, {'widget': CustomWidget}),
  272. (user_field, {}),
  273. (data_field, {'widget': CustomWidget, 'localize': True}),
  274. ]
  275. self.assertEqual(callback.log, expected_log)
  276. def test_inlineformset_custom_callback(self):
  277. callback = Callback()
  278. inlineformset_factory(User, UserSite, form=UserSiteForm,
  279. formfield_callback=callback, fields="__all__")
  280. self.assertCallbackCalled(callback)
  281. def test_modelformset_custom_callback(self):
  282. callback = Callback()
  283. modelformset_factory(UserSite, form=UserSiteForm,
  284. formfield_callback=callback)
  285. self.assertCallbackCalled(callback)
  286. class BaseCustomDeleteFormSet(BaseFormSet):
  287. """
  288. A formset mix-in that lets a form decide if it's to be deleted.
  289. Works for BaseFormSets. Also works for ModelFormSets with #14099 fixed.
  290. form.should_delete() is called. The formset delete field is also suppressed.
  291. """
  292. def add_fields(self, form, index):
  293. super(BaseCustomDeleteFormSet, self).add_fields(form, index)
  294. self.can_delete = True
  295. if DELETION_FIELD_NAME in form.fields:
  296. del form.fields[DELETION_FIELD_NAME]
  297. def _should_delete_form(self, form):
  298. return hasattr(form, 'should_delete') and form.should_delete()
  299. class FormfieldShouldDeleteFormTests(TestCase):
  300. """
  301. Regression for #14099: BaseModelFormSet should use ModelFormSet method _should_delete_form
  302. """
  303. class BaseCustomDeleteModelFormSet(BaseModelFormSet, BaseCustomDeleteFormSet):
  304. """ Model FormSet with CustomDelete MixIn """
  305. class CustomDeleteUserForm(forms.ModelForm):
  306. """ A model form with a 'should_delete' method """
  307. class Meta:
  308. model = User
  309. fields = "__all__"
  310. def should_delete(self):
  311. """ delete form if odd PK """
  312. return self.instance.pk % 2 != 0
  313. NormalFormset = modelformset_factory(User, form=CustomDeleteUserForm, can_delete=True)
  314. DeleteFormset = modelformset_factory(User, form=CustomDeleteUserForm, formset=BaseCustomDeleteModelFormSet)
  315. data = {
  316. 'form-TOTAL_FORMS': '4',
  317. 'form-INITIAL_FORMS': '0',
  318. 'form-MAX_NUM_FORMS': '4',
  319. 'form-0-username': 'John',
  320. 'form-0-serial': '1',
  321. 'form-1-username': 'Paul',
  322. 'form-1-serial': '2',
  323. 'form-2-username': 'George',
  324. 'form-2-serial': '3',
  325. 'form-3-username': 'Ringo',
  326. 'form-3-serial': '5',
  327. }
  328. delete_all_ids = {
  329. 'form-0-DELETE': '1',
  330. 'form-1-DELETE': '1',
  331. 'form-2-DELETE': '1',
  332. 'form-3-DELETE': '1',
  333. }
  334. def test_init_database(self):
  335. """ Add test data to database via formset """
  336. formset = self.NormalFormset(self.data)
  337. self.assertTrue(formset.is_valid())
  338. self.assertEqual(len(formset.save()), 4)
  339. def test_no_delete(self):
  340. """ Verify base formset doesn't modify database """
  341. # reload database
  342. self.test_init_database()
  343. # pass standard data dict & see none updated
  344. data = dict(self.data)
  345. data['form-INITIAL_FORMS'] = 4
  346. data.update(dict(
  347. ('form-%d-id' % i, user.pk)
  348. for i,user in enumerate(User.objects.all())
  349. ))
  350. formset = self.NormalFormset(data, queryset=User.objects.all())
  351. self.assertTrue(formset.is_valid())
  352. self.assertEqual(len(formset.save()), 0)
  353. self.assertEqual(len(User.objects.all()), 4)
  354. def test_all_delete(self):
  355. """ Verify base formset honors DELETE field """
  356. # reload database
  357. self.test_init_database()
  358. # create data dict with all fields marked for deletion
  359. data = dict(self.data)
  360. data['form-INITIAL_FORMS'] = 4
  361. data.update(dict(
  362. ('form-%d-id' % i, user.pk)
  363. for i,user in enumerate(User.objects.all())
  364. ))
  365. data.update(self.delete_all_ids)
  366. formset = self.NormalFormset(data, queryset=User.objects.all())
  367. self.assertTrue(formset.is_valid())
  368. self.assertEqual(len(formset.save()), 0)
  369. self.assertEqual(len(User.objects.all()), 0)
  370. def test_custom_delete(self):
  371. """ Verify DeleteFormset ignores DELETE field and uses form method """
  372. # reload database
  373. self.test_init_database()
  374. # Create formset with custom Delete function
  375. # create data dict with all fields marked for deletion
  376. data = dict(self.data)
  377. data['form-INITIAL_FORMS'] = 4
  378. data.update(dict(
  379. ('form-%d-id' % i, user.pk)
  380. for i,user in enumerate(User.objects.all())
  381. ))
  382. data.update(self.delete_all_ids)
  383. formset = self.DeleteFormset(data, queryset=User.objects.all())
  384. # verify two were deleted
  385. self.assertTrue(formset.is_valid())
  386. self.assertEqual(len(formset.save()), 0)
  387. self.assertEqual(len(User.objects.all()), 2)
  388. # verify no "odd" PKs left
  389. odd_ids = [user.pk for user in User.objects.all() if user.pk % 2]
  390. self.assertEqual(len(odd_ids), 0)