tests.py 20 KB

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