tests.py 15 KB

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