tests.py 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216
  1. from __future__ import absolute_import, unicode_literals
  2. import datetime
  3. import re
  4. from datetime import date
  5. from decimal import Decimal
  6. from django import forms
  7. from django.db import models
  8. from django.forms.models import (_get_foreign_key, inlineformset_factory,
  9. modelformset_factory)
  10. from django.test import TestCase, skipUnlessDBFeature
  11. from django.utils import six
  12. from .models import (Author, BetterAuthor, Book, BookWithCustomPK,
  13. BookWithOptionalAltEditor, AlternateBook, AuthorMeeting, CustomPrimaryKey,
  14. Place, Owner, Location, OwnerProfile, Restaurant, Product, Price,
  15. MexicanRestaurant, ClassyMexicanRestaurant, Repository, Revision,
  16. Person, Membership, Team, Player, Poet, Poem, Post)
  17. class DeletionTests(TestCase):
  18. def test_deletion(self):
  19. PoetFormSet = modelformset_factory(Poet, can_delete=True)
  20. poet = Poet.objects.create(name='test')
  21. data = {
  22. 'form-TOTAL_FORMS': '1',
  23. 'form-INITIAL_FORMS': '1',
  24. 'form-MAX_NUM_FORMS': '0',
  25. 'form-0-id': str(poet.pk),
  26. 'form-0-name': 'test',
  27. 'form-0-DELETE': 'on',
  28. }
  29. formset = PoetFormSet(data, queryset=Poet.objects.all())
  30. formset.save()
  31. self.assertTrue(formset.is_valid())
  32. self.assertEqual(Poet.objects.count(), 0)
  33. def test_add_form_deletion_when_invalid(self):
  34. """
  35. Make sure that an add form that is filled out, but marked for deletion
  36. doesn't cause validation errors.
  37. """
  38. PoetFormSet = modelformset_factory(Poet, can_delete=True)
  39. data = {
  40. 'form-TOTAL_FORMS': '1',
  41. 'form-INITIAL_FORMS': '0',
  42. 'form-MAX_NUM_FORMS': '0',
  43. 'form-0-id': '',
  44. 'form-0-name': 'x' * 1000,
  45. }
  46. formset = PoetFormSet(data, queryset=Poet.objects.all())
  47. # Make sure this form doesn't pass validation.
  48. self.assertEqual(formset.is_valid(), False)
  49. self.assertEqual(Poet.objects.count(), 0)
  50. # Then make sure that it *does* pass validation and delete the object,
  51. # even though the data isn't actually valid.
  52. data['form-0-DELETE'] = 'on'
  53. formset = PoetFormSet(data, queryset=Poet.objects.all())
  54. self.assertEqual(formset.is_valid(), True)
  55. formset.save()
  56. self.assertEqual(Poet.objects.count(), 0)
  57. def test_change_form_deletion_when_invalid(self):
  58. """
  59. Make sure that an add form that is filled out, but marked for deletion
  60. doesn't cause validation errors.
  61. """
  62. PoetFormSet = modelformset_factory(Poet, can_delete=True)
  63. poet = Poet.objects.create(name='test')
  64. data = {
  65. 'form-TOTAL_FORMS': '1',
  66. 'form-INITIAL_FORMS': '1',
  67. 'form-MAX_NUM_FORMS': '0',
  68. 'form-0-id': six.text_type(poet.id),
  69. 'form-0-name': 'x' * 1000,
  70. }
  71. formset = PoetFormSet(data, queryset=Poet.objects.all())
  72. # Make sure this form doesn't pass validation.
  73. self.assertEqual(formset.is_valid(), False)
  74. self.assertEqual(Poet.objects.count(), 1)
  75. # Then make sure that it *does* pass validation and delete the object,
  76. # even though the data isn't actually valid.
  77. data['form-0-DELETE'] = 'on'
  78. formset = PoetFormSet(data, queryset=Poet.objects.all())
  79. self.assertEqual(formset.is_valid(), True)
  80. formset.save()
  81. self.assertEqual(Poet.objects.count(), 0)
  82. class ModelFormsetTest(TestCase):
  83. def test_simple_save(self):
  84. qs = Author.objects.all()
  85. AuthorFormSet = modelformset_factory(Author, extra=3)
  86. formset = AuthorFormSet(queryset=qs)
  87. self.assertEqual(len(formset.forms), 3)
  88. self.assertHTMLEqual(formset.forms[0].as_p(),
  89. '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>')
  90. self.assertHTMLEqual(formset.forms[1].as_p(),
  91. '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /><input type="hidden" name="form-1-id" id="id_form-1-id" /></p>')
  92. self.assertHTMLEqual(formset.forms[2].as_p(),
  93. '<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>')
  94. data = {
  95. 'form-TOTAL_FORMS': '3', # the number of forms rendered
  96. 'form-INITIAL_FORMS': '0', # the number of forms with initial data
  97. 'form-MAX_NUM_FORMS': '', # the max number of forms
  98. 'form-0-name': 'Charles Baudelaire',
  99. 'form-1-name': 'Arthur Rimbaud',
  100. 'form-2-name': '',
  101. }
  102. formset = AuthorFormSet(data=data, queryset=qs)
  103. self.assertTrue(formset.is_valid())
  104. saved = formset.save()
  105. self.assertEqual(len(saved), 2)
  106. author1, author2 = saved
  107. self.assertEqual(author1, Author.objects.get(name='Charles Baudelaire'))
  108. self.assertEqual(author2, Author.objects.get(name='Arthur Rimbaud'))
  109. authors = list(Author.objects.order_by('name'))
  110. self.assertEqual(authors, [author2, author1])
  111. # Gah! We forgot Paul Verlaine. Let's create a formset to edit the
  112. # existing authors with an extra form to add him. We *could* pass in a
  113. # queryset to restrict the Author objects we edit, but in this case
  114. # we'll use it to display them in alphabetical order by name.
  115. qs = Author.objects.order_by('name')
  116. AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=False)
  117. formset = AuthorFormSet(queryset=qs)
  118. self.assertEqual(len(formset.forms), 3)
  119. self.assertHTMLEqual(formset.forms[0].as_p(),
  120. '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /></p>' % author2.id)
  121. self.assertHTMLEqual(formset.forms[1].as_p(),
  122. '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" /></p>' % author1.id)
  123. self.assertHTMLEqual(formset.forms[2].as_p(),
  124. '<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>')
  125. data = {
  126. 'form-TOTAL_FORMS': '3', # the number of forms rendered
  127. 'form-INITIAL_FORMS': '2', # the number of forms with initial data
  128. 'form-MAX_NUM_FORMS': '', # the max number of forms
  129. 'form-0-id': str(author2.id),
  130. 'form-0-name': 'Arthur Rimbaud',
  131. 'form-1-id': str(author1.id),
  132. 'form-1-name': 'Charles Baudelaire',
  133. 'form-2-name': 'Paul Verlaine',
  134. }
  135. formset = AuthorFormSet(data=data, queryset=qs)
  136. self.assertTrue(formset.is_valid())
  137. # Only changed or new objects are returned from formset.save()
  138. saved = formset.save()
  139. self.assertEqual(len(saved), 1)
  140. author3 = saved[0]
  141. self.assertEqual(author3, Author.objects.get(name='Paul Verlaine'))
  142. authors = list(Author.objects.order_by('name'))
  143. self.assertEqual(authors, [author2, author1, author3])
  144. # This probably shouldn't happen, but it will. If an add form was
  145. # marked for deletion, make sure we don't save that form.
  146. qs = Author.objects.order_by('name')
  147. AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=True)
  148. formset = AuthorFormSet(queryset=qs)
  149. self.assertEqual(len(formset.forms), 4)
  150. self.assertHTMLEqual(formset.forms[0].as_p(),
  151. '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>\n'
  152. '<p><label for="id_form-0-DELETE">Delete:</label> <input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /><input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /></p>' % author2.id)
  153. self.assertHTMLEqual(formset.forms[1].as_p(),
  154. '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /></p>\n'
  155. '<p><label for="id_form-1-DELETE">Delete:</label> <input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" /></p>' % author1.id)
  156. self.assertHTMLEqual(formset.forms[2].as_p(),
  157. '<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" value="Paul Verlaine" maxlength="100" /></p>\n'
  158. '<p><label for="id_form-2-DELETE">Delete:</label> <input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /><input type="hidden" name="form-2-id" value="%d" id="id_form-2-id" /></p>' % author3.id)
  159. self.assertHTMLEqual(formset.forms[3].as_p(),
  160. '<p><label for="id_form-3-name">Name:</label> <input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /></p>\n'
  161. '<p><label for="id_form-3-DELETE">Delete:</label> <input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></p>')
  162. data = {
  163. 'form-TOTAL_FORMS': '4', # the number of forms rendered
  164. 'form-INITIAL_FORMS': '3', # the number of forms with initial data
  165. 'form-MAX_NUM_FORMS': '', # the max number of forms
  166. 'form-0-id': str(author2.id),
  167. 'form-0-name': 'Arthur Rimbaud',
  168. 'form-1-id': str(author1.id),
  169. 'form-1-name': 'Charles Baudelaire',
  170. 'form-2-id': str(author3.id),
  171. 'form-2-name': 'Paul Verlaine',
  172. 'form-3-name': 'Walt Whitman',
  173. 'form-3-DELETE': 'on',
  174. }
  175. formset = AuthorFormSet(data=data, queryset=qs)
  176. self.assertTrue(formset.is_valid())
  177. # No objects were changed or saved so nothing will come back.
  178. self.assertEqual(formset.save(), [])
  179. authors = list(Author.objects.order_by('name'))
  180. self.assertEqual(authors, [author2, author1, author3])
  181. # Let's edit a record to ensure save only returns that one record.
  182. data = {
  183. 'form-TOTAL_FORMS': '4', # the number of forms rendered
  184. 'form-INITIAL_FORMS': '3', # the number of forms with initial data
  185. 'form-MAX_NUM_FORMS': '', # the max number of forms
  186. 'form-0-id': str(author2.id),
  187. 'form-0-name': 'Walt Whitman',
  188. 'form-1-id': str(author1.id),
  189. 'form-1-name': 'Charles Baudelaire',
  190. 'form-2-id': str(author3.id),
  191. 'form-2-name': 'Paul Verlaine',
  192. 'form-3-name': '',
  193. 'form-3-DELETE': '',
  194. }
  195. formset = AuthorFormSet(data=data, queryset=qs)
  196. self.assertTrue(formset.is_valid())
  197. # One record has changed.
  198. saved = formset.save()
  199. self.assertEqual(len(saved), 1)
  200. self.assertEqual(saved[0], Author.objects.get(name='Walt Whitman'))
  201. def test_commit_false(self):
  202. # Test the behavior of commit=False and save_m2m
  203. author1 = Author.objects.create(name='Charles Baudelaire')
  204. author2 = Author.objects.create(name='Paul Verlaine')
  205. author3 = Author.objects.create(name='Walt Whitman')
  206. meeting = AuthorMeeting.objects.create(created=date.today())
  207. meeting.authors = Author.objects.all()
  208. # create an Author instance to add to the meeting.
  209. author4 = Author.objects.create(name='John Steinbeck')
  210. AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, extra=1, can_delete=True)
  211. data = {
  212. 'form-TOTAL_FORMS': '2', # the number of forms rendered
  213. 'form-INITIAL_FORMS': '1', # the number of forms with initial data
  214. 'form-MAX_NUM_FORMS': '', # the max number of forms
  215. 'form-0-id': str(meeting.id),
  216. 'form-0-name': '2nd Tuesday of the Week Meeting',
  217. 'form-0-authors': [author2.id, author1.id, author3.id, author4.id],
  218. 'form-1-name': '',
  219. 'form-1-authors': '',
  220. 'form-1-DELETE': '',
  221. }
  222. formset = AuthorMeetingFormSet(data=data, queryset=AuthorMeeting.objects.all())
  223. self.assertTrue(formset.is_valid())
  224. instances = formset.save(commit=False)
  225. for instance in instances:
  226. instance.created = date.today()
  227. instance.save()
  228. formset.save_m2m()
  229. self.assertQuerysetEqual(instances[0].authors.all(), [
  230. '<Author: Charles Baudelaire>',
  231. '<Author: John Steinbeck>',
  232. '<Author: Paul Verlaine>',
  233. '<Author: Walt Whitman>',
  234. ])
  235. def test_max_num(self):
  236. # Test the behavior of max_num with model formsets. It should allow
  237. # all existing related objects/inlines for a given object to be
  238. # displayed, but not allow the creation of new inlines beyond max_num.
  239. author1 = Author.objects.create(name='Charles Baudelaire')
  240. author2 = Author.objects.create(name='Paul Verlaine')
  241. author3 = Author.objects.create(name='Walt Whitman')
  242. qs = Author.objects.order_by('name')
  243. AuthorFormSet = modelformset_factory(Author, max_num=None, extra=3)
  244. formset = AuthorFormSet(queryset=qs)
  245. self.assertEqual(len(formset.forms), 6)
  246. self.assertEqual(len(formset.extra_forms), 3)
  247. AuthorFormSet = modelformset_factory(Author, max_num=4, extra=3)
  248. formset = AuthorFormSet(queryset=qs)
  249. self.assertEqual(len(formset.forms), 4)
  250. self.assertEqual(len(formset.extra_forms), 1)
  251. AuthorFormSet = modelformset_factory(Author, max_num=0, extra=3)
  252. formset = AuthorFormSet(queryset=qs)
  253. self.assertEqual(len(formset.forms), 3)
  254. self.assertEqual(len(formset.extra_forms), 0)
  255. AuthorFormSet = modelformset_factory(Author, max_num=None)
  256. formset = AuthorFormSet(queryset=qs)
  257. self.assertQuerysetEqual(formset.get_queryset(), [
  258. '<Author: Charles Baudelaire>',
  259. '<Author: Paul Verlaine>',
  260. '<Author: Walt Whitman>',
  261. ])
  262. AuthorFormSet = modelformset_factory(Author, max_num=0)
  263. formset = AuthorFormSet(queryset=qs)
  264. self.assertQuerysetEqual(formset.get_queryset(), [
  265. '<Author: Charles Baudelaire>',
  266. '<Author: Paul Verlaine>',
  267. '<Author: Walt Whitman>',
  268. ])
  269. AuthorFormSet = modelformset_factory(Author, max_num=4)
  270. formset = AuthorFormSet(queryset=qs)
  271. self.assertQuerysetEqual(formset.get_queryset(), [
  272. '<Author: Charles Baudelaire>',
  273. '<Author: Paul Verlaine>',
  274. '<Author: Walt Whitman>',
  275. ])
  276. def test_custom_save_method(self):
  277. class PoetForm(forms.ModelForm):
  278. def save(self, commit=True):
  279. # change the name to "Vladimir Mayakovsky" just to be a jerk.
  280. author = super(PoetForm, self).save(commit=False)
  281. author.name = "Vladimir Mayakovsky"
  282. if commit:
  283. author.save()
  284. return author
  285. PoetFormSet = modelformset_factory(Poet, form=PoetForm)
  286. data = {
  287. 'form-TOTAL_FORMS': '3', # the number of forms rendered
  288. 'form-INITIAL_FORMS': '0', # the number of forms with initial data
  289. 'form-MAX_NUM_FORMS': '', # the max number of forms
  290. 'form-0-name': 'Walt Whitman',
  291. 'form-1-name': 'Charles Baudelaire',
  292. 'form-2-name': '',
  293. }
  294. qs = Poet.objects.all()
  295. formset = PoetFormSet(data=data, queryset=qs)
  296. self.assertTrue(formset.is_valid())
  297. poets = formset.save()
  298. self.assertEqual(len(poets), 2)
  299. poet1, poet2 = poets
  300. self.assertEqual(poet1.name, 'Vladimir Mayakovsky')
  301. self.assertEqual(poet2.name, 'Vladimir Mayakovsky')
  302. def test_custom_form(self):
  303. """ Test that model_formset respects fields and exclude parameters of
  304. custom form
  305. """
  306. class PostForm1(forms.ModelForm):
  307. class Meta:
  308. model = Post
  309. fields = ('title', 'posted')
  310. class PostForm2(forms.ModelForm):
  311. class Meta:
  312. model = Post
  313. exclude = ('subtitle',)
  314. PostFormSet = modelformset_factory(Post, form=PostForm1)
  315. formset = PostFormSet()
  316. self.assertFalse("subtitle" in formset.forms[0].fields)
  317. PostFormSet = modelformset_factory(Post, form=PostForm2)
  318. formset = PostFormSet()
  319. self.assertFalse("subtitle" in formset.forms[0].fields)
  320. def test_model_inheritance(self):
  321. BetterAuthorFormSet = modelformset_factory(BetterAuthor)
  322. formset = BetterAuthorFormSet()
  323. self.assertEqual(len(formset.forms), 1)
  324. self.assertHTMLEqual(formset.forms[0].as_p(),
  325. '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>\n'
  326. '<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" id="id_form-0-author_ptr" /></p>')
  327. data = {
  328. 'form-TOTAL_FORMS': '1', # the number of forms rendered
  329. 'form-INITIAL_FORMS': '0', # the number of forms with initial data
  330. 'form-MAX_NUM_FORMS': '', # the max number of forms
  331. 'form-0-author_ptr': '',
  332. 'form-0-name': 'Ernest Hemingway',
  333. 'form-0-write_speed': '10',
  334. }
  335. formset = BetterAuthorFormSet(data)
  336. self.assertTrue(formset.is_valid())
  337. saved = formset.save()
  338. self.assertEqual(len(saved), 1)
  339. author1, = saved
  340. self.assertEqual(author1, BetterAuthor.objects.get(name='Ernest Hemingway'))
  341. hemingway_id = BetterAuthor.objects.get(name="Ernest Hemingway").pk
  342. formset = BetterAuthorFormSet()
  343. self.assertEqual(len(formset.forms), 2)
  344. self.assertHTMLEqual(formset.forms[0].as_p(),
  345. '<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Ernest Hemingway" maxlength="100" /></p>\n'
  346. '<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" value="%d" id="id_form-0-author_ptr" /></p>' % hemingway_id)
  347. self.assertHTMLEqual(formset.forms[1].as_p(),
  348. '<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>\n'
  349. '<p><label for="id_form-1-write_speed">Write speed:</label> <input type="text" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr" id="id_form-1-author_ptr" /></p>')
  350. data = {
  351. 'form-TOTAL_FORMS': '2', # the number of forms rendered
  352. 'form-INITIAL_FORMS': '1', # the number of forms with initial data
  353. 'form-MAX_NUM_FORMS': '', # the max number of forms
  354. 'form-0-author_ptr': hemingway_id,
  355. 'form-0-name': 'Ernest Hemingway',
  356. 'form-0-write_speed': '10',
  357. 'form-1-author_ptr': '',
  358. 'form-1-name': '',
  359. 'form-1-write_speed': '',
  360. }
  361. formset = BetterAuthorFormSet(data)
  362. self.assertTrue(formset.is_valid())
  363. self.assertEqual(formset.save(), [])
  364. def test_inline_formsets(self):
  365. # We can also create a formset that is tied to a parent model. This is
  366. # how the admin system's edit inline functionality works.
  367. AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3)
  368. author = Author.objects.create(name='Charles Baudelaire')
  369. formset = AuthorBooksFormSet(instance=author)
  370. self.assertEqual(len(formset.forms), 3)
  371. self.assertHTMLEqual(formset.forms[0].as_p(),
  372. '<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-author" value="%d" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>' % author.id)
  373. self.assertHTMLEqual(formset.forms[1].as_p(),
  374. '<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-author" value="%d" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>' % author.id)
  375. self.assertHTMLEqual(formset.forms[2].as_p(),
  376. '<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-author" value="%d" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>' % author.id)
  377. data = {
  378. 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
  379. 'book_set-INITIAL_FORMS': '0', # the number of forms with initial data
  380. 'book_set-MAX_NUM_FORMS': '', # the max number of forms
  381. 'book_set-0-title': 'Les Fleurs du Mal',
  382. 'book_set-1-title': '',
  383. 'book_set-2-title': '',
  384. }
  385. formset = AuthorBooksFormSet(data, instance=author)
  386. self.assertTrue(formset.is_valid())
  387. saved = formset.save()
  388. self.assertEqual(len(saved), 1)
  389. book1, = saved
  390. self.assertEqual(book1, Book.objects.get(title='Les Fleurs du Mal'))
  391. self.assertQuerysetEqual(author.book_set.all(), ['<Book: Les Fleurs du Mal>'])
  392. # Now that we've added a book to Charles Baudelaire, let's try adding
  393. # another one. This time though, an edit form will be available for
  394. # every existing book.
  395. AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
  396. author = Author.objects.get(name='Charles Baudelaire')
  397. formset = AuthorBooksFormSet(instance=author)
  398. self.assertEqual(len(formset.forms), 3)
  399. self.assertHTMLEqual(formset.forms[0].as_p(),
  400. '<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-author" value="%d" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" value="%d" id="id_book_set-0-id" /></p>' % (author.id, book1.id))
  401. self.assertHTMLEqual(formset.forms[1].as_p(),
  402. '<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-author" value="%d" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>' % author.id)
  403. self.assertHTMLEqual(formset.forms[2].as_p(),
  404. '<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-author" value="%d" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>' % author.id)
  405. data = {
  406. 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
  407. 'book_set-INITIAL_FORMS': '1', # the number of forms with initial data
  408. 'book_set-MAX_NUM_FORMS': '', # the max number of forms
  409. 'book_set-0-id': str(book1.id),
  410. 'book_set-0-title': 'Les Fleurs du Mal',
  411. 'book_set-1-title': 'Les Paradis Artificiels',
  412. 'book_set-2-title': '',
  413. }
  414. formset = AuthorBooksFormSet(data, instance=author)
  415. self.assertTrue(formset.is_valid())
  416. saved = formset.save()
  417. self.assertEqual(len(saved), 1)
  418. book2, = saved
  419. self.assertEqual(book2, Book.objects.get(title='Les Paradis Artificiels'))
  420. # As you can see, 'Les Paradis Artificiels' is now a book belonging to
  421. # Charles Baudelaire.
  422. self.assertQuerysetEqual(author.book_set.order_by('title'), [
  423. '<Book: Les Fleurs du Mal>',
  424. '<Book: Les Paradis Artificiels>',
  425. ])
  426. def test_inline_formsets_save_as_new(self):
  427. # The save_as_new parameter lets you re-associate the data to a new
  428. # instance. This is used in the admin for save_as functionality.
  429. AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
  430. author = Author.objects.create(name='Charles Baudelaire')
  431. data = {
  432. 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
  433. 'book_set-INITIAL_FORMS': '2', # the number of forms with initial data
  434. 'book_set-MAX_NUM_FORMS': '', # the max number of forms
  435. 'book_set-0-id': '1',
  436. 'book_set-0-title': 'Les Fleurs du Mal',
  437. 'book_set-1-id': '2',
  438. 'book_set-1-title': 'Les Paradis Artificiels',
  439. 'book_set-2-title': '',
  440. }
  441. formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
  442. self.assertTrue(formset.is_valid())
  443. new_author = Author.objects.create(name='Charles Baudelaire')
  444. formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True)
  445. saved = formset.save()
  446. self.assertEqual(len(saved), 2)
  447. book1, book2 = saved
  448. self.assertEqual(book1.title, 'Les Fleurs du Mal')
  449. self.assertEqual(book2.title, 'Les Paradis Artificiels')
  450. # Test using a custom prefix on an inline formset.
  451. formset = AuthorBooksFormSet(prefix="test")
  452. self.assertEqual(len(formset.forms), 2)
  453. self.assertHTMLEqual(formset.forms[0].as_p(),
  454. '<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-author" id="id_test-0-author" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>')
  455. self.assertHTMLEqual(formset.forms[1].as_p(),
  456. '<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-author" id="id_test-1-author" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>')
  457. def test_inline_formsets_with_custom_pk(self):
  458. # Test inline formsets where the inline-edited object has a custom
  459. # primary key that is not the fk to the parent object.
  460. AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1)
  461. author = Author.objects.create(pk=1, name='Charles Baudelaire')
  462. formset = AuthorBooksFormSet2(instance=author)
  463. self.assertEqual(len(formset.forms), 1)
  464. self.assertHTMLEqual(formset.forms[0].as_p(),
  465. '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input type="text" name="bookwithcustompk_set-0-my_pk" id="id_bookwithcustompk_set-0-my_pk" /></p>\n'
  466. '<p><label for="id_bookwithcustompk_set-0-title">Title:</label> <input id="id_bookwithcustompk_set-0-title" type="text" name="bookwithcustompk_set-0-title" maxlength="100" /><input type="hidden" name="bookwithcustompk_set-0-author" value="1" id="id_bookwithcustompk_set-0-author" /></p>')
  467. data = {
  468. 'bookwithcustompk_set-TOTAL_FORMS': '1', # the number of forms rendered
  469. 'bookwithcustompk_set-INITIAL_FORMS': '0', # the number of forms with initial data
  470. 'bookwithcustompk_set-MAX_NUM_FORMS': '', # the max number of forms
  471. 'bookwithcustompk_set-0-my_pk': '77777',
  472. 'bookwithcustompk_set-0-title': 'Les Fleurs du Mal',
  473. }
  474. formset = AuthorBooksFormSet2(data, instance=author)
  475. self.assertTrue(formset.is_valid())
  476. saved = formset.save()
  477. self.assertEqual(len(saved), 1)
  478. book1, = saved
  479. self.assertEqual(book1.pk, 77777)
  480. book1 = author.bookwithcustompk_set.get()
  481. self.assertEqual(book1.title, 'Les Fleurs du Mal')
  482. def test_inline_formsets_with_multi_table_inheritance(self):
  483. # Test inline formsets where the inline-edited object uses multi-table
  484. # inheritance, thus has a non AutoField yet auto-created primary key.
  485. AuthorBooksFormSet3 = inlineformset_factory(Author, AlternateBook, can_delete=False, extra=1)
  486. author = Author.objects.create(pk=1, name='Charles Baudelaire')
  487. formset = AuthorBooksFormSet3(instance=author)
  488. self.assertEqual(len(formset.forms), 1)
  489. self.assertHTMLEqual(formset.forms[0].as_p(),
  490. '<p><label for="id_alternatebook_set-0-title">Title:</label> <input id="id_alternatebook_set-0-title" type="text" name="alternatebook_set-0-title" maxlength="100" /></p>\n'
  491. '<p><label for="id_alternatebook_set-0-notes">Notes:</label> <input id="id_alternatebook_set-0-notes" type="text" name="alternatebook_set-0-notes" maxlength="100" /><input type="hidden" name="alternatebook_set-0-author" value="1" id="id_alternatebook_set-0-author" /><input type="hidden" name="alternatebook_set-0-book_ptr" id="id_alternatebook_set-0-book_ptr" /></p>')
  492. data = {
  493. 'alternatebook_set-TOTAL_FORMS': '1', # the number of forms rendered
  494. 'alternatebook_set-INITIAL_FORMS': '0', # the number of forms with initial data
  495. 'alternatebook_set-MAX_NUM_FORMS': '', # the max number of forms
  496. 'alternatebook_set-0-title': 'Flowers of Evil',
  497. 'alternatebook_set-0-notes': 'English translation of Les Fleurs du Mal'
  498. }
  499. formset = AuthorBooksFormSet3(data, instance=author)
  500. self.assertTrue(formset.is_valid())
  501. saved = formset.save()
  502. self.assertEqual(len(saved), 1)
  503. book1, = saved
  504. self.assertEqual(book1.title, 'Flowers of Evil')
  505. self.assertEqual(book1.notes, 'English translation of Les Fleurs du Mal')
  506. @skipUnlessDBFeature('ignores_nulls_in_unique_constraints')
  507. def test_inline_formsets_with_nullable_unique_together(self):
  508. # Test inline formsets where the inline-edited object has a
  509. # unique_together constraint with a nullable member
  510. AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2)
  511. author = Author.objects.create(pk=1, name='Charles Baudelaire')
  512. data = {
  513. 'bookwithoptionalalteditor_set-TOTAL_FORMS': '2', # the number of forms rendered
  514. 'bookwithoptionalalteditor_set-INITIAL_FORMS': '0', # the number of forms with initial data
  515. 'bookwithoptionalalteditor_set-MAX_NUM_FORMS': '', # the max number of forms
  516. 'bookwithoptionalalteditor_set-0-author': '1',
  517. 'bookwithoptionalalteditor_set-0-title': 'Les Fleurs du Mal',
  518. 'bookwithoptionalalteditor_set-1-author': '1',
  519. 'bookwithoptionalalteditor_set-1-title': 'Les Fleurs du Mal',
  520. }
  521. formset = AuthorBooksFormSet4(data, instance=author)
  522. self.assertTrue(formset.is_valid())
  523. saved = formset.save()
  524. self.assertEqual(len(saved), 2)
  525. book1, book2 = saved
  526. self.assertEqual(book1.author_id, 1)
  527. self.assertEqual(book1.title, 'Les Fleurs du Mal')
  528. self.assertEqual(book2.author_id, 1)
  529. self.assertEqual(book2.title, 'Les Fleurs du Mal')
  530. def test_inline_formsets_with_custom_save_method(self):
  531. AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
  532. author = Author.objects.create(pk=1, name='Charles Baudelaire')
  533. book1 = Book.objects.create(pk=1, author=author, title='Les Paradis Artificiels')
  534. book2 = Book.objects.create(pk=2, author=author, title='Les Fleurs du Mal')
  535. book3 = Book.objects.create(pk=3, author=author, title='Flowers of Evil')
  536. class PoemForm(forms.ModelForm):
  537. def save(self, commit=True):
  538. # change the name to "Brooklyn Bridge" just to be a jerk.
  539. poem = super(PoemForm, self).save(commit=False)
  540. poem.name = "Brooklyn Bridge"
  541. if commit:
  542. poem.save()
  543. return poem
  544. PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm)
  545. data = {
  546. 'poem_set-TOTAL_FORMS': '3', # the number of forms rendered
  547. 'poem_set-INITIAL_FORMS': '0', # the number of forms with initial data
  548. 'poem_set-MAX_NUM_FORMS': '', # the max number of forms
  549. 'poem_set-0-name': 'The Cloud in Trousers',
  550. 'poem_set-1-name': 'I',
  551. 'poem_set-2-name': '',
  552. }
  553. poet = Poet.objects.create(name='Vladimir Mayakovsky')
  554. formset = PoemFormSet(data=data, instance=poet)
  555. self.assertTrue(formset.is_valid())
  556. saved = formset.save()
  557. self.assertEqual(len(saved), 2)
  558. poem1, poem2 = saved
  559. self.assertEqual(poem1.name, 'Brooklyn Bridge')
  560. self.assertEqual(poem2.name, 'Brooklyn Bridge')
  561. # We can provide a custom queryset to our InlineFormSet:
  562. custom_qs = Book.objects.order_by('-title')
  563. formset = AuthorBooksFormSet(instance=author, queryset=custom_qs)
  564. self.assertEqual(len(formset.forms), 5)
  565. self.assertHTMLEqual(formset.forms[0].as_p(),
  566. '<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Paradis Artificiels" maxlength="100" /><input type="hidden" name="book_set-0-author" value="1" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>')
  567. self.assertHTMLEqual(formset.forms[1].as_p(),
  568. '<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-1-author" value="1" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" value="2" id="id_book_set-1-id" /></p>')
  569. self.assertHTMLEqual(formset.forms[2].as_p(),
  570. '<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" value="Flowers of Evil" maxlength="100" /><input type="hidden" name="book_set-2-author" value="1" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" value="3" id="id_book_set-2-id" /></p>')
  571. self.assertHTMLEqual(formset.forms[3].as_p(),
  572. '<p><label for="id_book_set-3-title">Title:</label> <input id="id_book_set-3-title" type="text" name="book_set-3-title" maxlength="100" /><input type="hidden" name="book_set-3-author" value="1" id="id_book_set-3-author" /><input type="hidden" name="book_set-3-id" id="id_book_set-3-id" /></p>')
  573. self.assertHTMLEqual(formset.forms[4].as_p(),
  574. '<p><label for="id_book_set-4-title">Title:</label> <input id="id_book_set-4-title" type="text" name="book_set-4-title" maxlength="100" /><input type="hidden" name="book_set-4-author" value="1" id="id_book_set-4-author" /><input type="hidden" name="book_set-4-id" id="id_book_set-4-id" /></p>')
  575. data = {
  576. 'book_set-TOTAL_FORMS': '5', # the number of forms rendered
  577. 'book_set-INITIAL_FORMS': '3', # the number of forms with initial data
  578. 'book_set-MAX_NUM_FORMS': '', # the max number of forms
  579. 'book_set-0-id': str(book1.id),
  580. 'book_set-0-title': 'Les Paradis Artificiels',
  581. 'book_set-1-id': str(book2.id),
  582. 'book_set-1-title': 'Les Fleurs du Mal',
  583. 'book_set-2-id': str(book3.id),
  584. 'book_set-2-title': 'Flowers of Evil',
  585. 'book_set-3-title': 'Revue des deux mondes',
  586. 'book_set-4-title': '',
  587. }
  588. formset = AuthorBooksFormSet(data, instance=author, queryset=custom_qs)
  589. self.assertTrue(formset.is_valid())
  590. custom_qs = Book.objects.filter(title__startswith='F')
  591. formset = AuthorBooksFormSet(instance=author, queryset=custom_qs)
  592. self.assertHTMLEqual(formset.forms[0].as_p(),
  593. '<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Flowers of Evil" maxlength="100" /><input type="hidden" name="book_set-0-author" value="1" id="id_book_set-0-author" /><input type="hidden" name="book_set-0-id" value="3" id="id_book_set-0-id" /></p>')
  594. self.assertHTMLEqual(formset.forms[1].as_p(),
  595. '<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-author" value="1" id="id_book_set-1-author" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>')
  596. self.assertHTMLEqual(formset.forms[2].as_p(),
  597. '<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-author" value="1" id="id_book_set-2-author" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>')
  598. data = {
  599. 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
  600. 'book_set-INITIAL_FORMS': '1', # the number of forms with initial data
  601. 'book_set-MAX_NUM_FORMS': '', # the max number of forms
  602. 'book_set-0-id': str(book3.id),
  603. 'book_set-0-title': 'Flowers of Evil',
  604. 'book_set-1-title': 'Revue des deux mondes',
  605. 'book_set-2-title': '',
  606. }
  607. formset = AuthorBooksFormSet(data, instance=author, queryset=custom_qs)
  608. self.assertTrue(formset.is_valid())
  609. def test_custom_pk(self):
  610. # We need to ensure that it is displayed
  611. CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey)
  612. formset = CustomPrimaryKeyFormSet()
  613. self.assertEqual(len(formset.forms), 1)
  614. self.assertHTMLEqual(formset.forms[0].as_p(),
  615. '<p><label for="id_form-0-my_pk">My pk:</label> <input id="id_form-0-my_pk" type="text" name="form-0-my_pk" maxlength="10" /></p>\n'
  616. '<p><label for="id_form-0-some_field">Some field:</label> <input id="id_form-0-some_field" type="text" name="form-0-some_field" maxlength="100" /></p>')
  617. # Custom primary keys with ForeignKey, OneToOneField and AutoField ############
  618. place = Place.objects.create(pk=1, name='Giordanos', city='Chicago')
  619. FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False)
  620. formset = FormSet(instance=place)
  621. self.assertEqual(len(formset.forms), 2)
  622. self.assertHTMLEqual(formset.forms[0].as_p(),
  623. '<p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" maxlength="100" /><input type="hidden" name="owner_set-0-place" value="1" id="id_owner_set-0-place" /><input type="hidden" name="owner_set-0-auto_id" id="id_owner_set-0-auto_id" /></p>')
  624. self.assertHTMLEqual(formset.forms[1].as_p(),
  625. '<p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-place" value="1" id="id_owner_set-1-place" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p>')
  626. data = {
  627. 'owner_set-TOTAL_FORMS': '2',
  628. 'owner_set-INITIAL_FORMS': '0',
  629. 'owner_set-MAX_NUM_FORMS': '',
  630. 'owner_set-0-auto_id': '',
  631. 'owner_set-0-name': 'Joe Perry',
  632. 'owner_set-1-auto_id': '',
  633. 'owner_set-1-name': '',
  634. }
  635. formset = FormSet(data, instance=place)
  636. self.assertTrue(formset.is_valid())
  637. saved = formset.save()
  638. self.assertEqual(len(saved), 1)
  639. owner1, = saved
  640. self.assertEqual(owner1.name, 'Joe Perry')
  641. self.assertEqual(owner1.place.name, 'Giordanos')
  642. formset = FormSet(instance=place)
  643. self.assertEqual(len(formset.forms), 3)
  644. self.assertHTMLEqual(formset.forms[0].as_p(),
  645. '<p><label for="id_owner_set-0-name">Name:</label> <input id="id_owner_set-0-name" type="text" name="owner_set-0-name" value="Joe Perry" maxlength="100" /><input type="hidden" name="owner_set-0-place" value="1" id="id_owner_set-0-place" /><input type="hidden" name="owner_set-0-auto_id" value="%d" id="id_owner_set-0-auto_id" /></p>'
  646. % owner1.auto_id)
  647. self.assertHTMLEqual(formset.forms[1].as_p(),
  648. '<p><label for="id_owner_set-1-name">Name:</label> <input id="id_owner_set-1-name" type="text" name="owner_set-1-name" maxlength="100" /><input type="hidden" name="owner_set-1-place" value="1" id="id_owner_set-1-place" /><input type="hidden" name="owner_set-1-auto_id" id="id_owner_set-1-auto_id" /></p>')
  649. self.assertHTMLEqual(formset.forms[2].as_p(),
  650. '<p><label for="id_owner_set-2-name">Name:</label> <input id="id_owner_set-2-name" type="text" name="owner_set-2-name" maxlength="100" /><input type="hidden" name="owner_set-2-place" value="1" id="id_owner_set-2-place" /><input type="hidden" name="owner_set-2-auto_id" id="id_owner_set-2-auto_id" /></p>')
  651. data = {
  652. 'owner_set-TOTAL_FORMS': '3',
  653. 'owner_set-INITIAL_FORMS': '1',
  654. 'owner_set-MAX_NUM_FORMS': '',
  655. 'owner_set-0-auto_id': six.text_type(owner1.auto_id),
  656. 'owner_set-0-name': 'Joe Perry',
  657. 'owner_set-1-auto_id': '',
  658. 'owner_set-1-name': 'Jack Berry',
  659. 'owner_set-2-auto_id': '',
  660. 'owner_set-2-name': '',
  661. }
  662. formset = FormSet(data, instance=place)
  663. self.assertTrue(formset.is_valid())
  664. saved = formset.save()
  665. self.assertEqual(len(saved), 1)
  666. owner2, = saved
  667. self.assertEqual(owner2.name, 'Jack Berry')
  668. self.assertEqual(owner2.place.name, 'Giordanos')
  669. # Ensure a custom primary key that is a ForeignKey or OneToOneField get rendered for the user to choose.
  670. FormSet = modelformset_factory(OwnerProfile)
  671. formset = FormSet()
  672. self.assertHTMLEqual(formset.forms[0].as_p(),
  673. '<p><label for="id_form-0-owner">Owner:</label> <select name="form-0-owner" id="id_form-0-owner">\n'
  674. '<option value="" selected="selected">---------</option>\n'
  675. '<option value="%d">Joe Perry at Giordanos</option>\n'
  676. '<option value="%d">Jack Berry at Giordanos</option>\n'
  677. '</select></p>\n'
  678. '<p><label for="id_form-0-age">Age:</label> <input type="text" name="form-0-age" id="id_form-0-age" /></p>'
  679. % (owner1.auto_id, owner2.auto_id))
  680. owner1 = Owner.objects.get(name='Joe Perry')
  681. FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False)
  682. self.assertEqual(FormSet.max_num, 1)
  683. formset = FormSet(instance=owner1)
  684. self.assertEqual(len(formset.forms), 1)
  685. self.assertHTMLEqual(formset.forms[0].as_p(),
  686. '<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>'
  687. % owner1.auto_id)
  688. data = {
  689. 'ownerprofile-TOTAL_FORMS': '1',
  690. 'ownerprofile-INITIAL_FORMS': '0',
  691. 'ownerprofile-MAX_NUM_FORMS': '1',
  692. 'ownerprofile-0-owner': '',
  693. 'ownerprofile-0-age': '54',
  694. }
  695. formset = FormSet(data, instance=owner1)
  696. self.assertTrue(formset.is_valid())
  697. saved = formset.save()
  698. self.assertEqual(len(saved), 1)
  699. profile1, = saved
  700. self.assertEqual(profile1.owner, owner1)
  701. self.assertEqual(profile1.age, 54)
  702. formset = FormSet(instance=owner1)
  703. self.assertEqual(len(formset.forms), 1)
  704. self.assertHTMLEqual(formset.forms[0].as_p(),
  705. '<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" value="54" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>'
  706. % owner1.auto_id)
  707. data = {
  708. 'ownerprofile-TOTAL_FORMS': '1',
  709. 'ownerprofile-INITIAL_FORMS': '1',
  710. 'ownerprofile-MAX_NUM_FORMS': '1',
  711. 'ownerprofile-0-owner': six.text_type(owner1.auto_id),
  712. 'ownerprofile-0-age': '55',
  713. }
  714. formset = FormSet(data, instance=owner1)
  715. self.assertTrue(formset.is_valid())
  716. saved = formset.save()
  717. self.assertEqual(len(saved), 1)
  718. profile1, = saved
  719. self.assertEqual(profile1.owner, owner1)
  720. self.assertEqual(profile1.age, 55)
  721. def test_unique_true_enforces_max_num_one(self):
  722. # ForeignKey with unique=True should enforce max_num=1
  723. place = Place.objects.create(pk=1, name='Giordanos', city='Chicago')
  724. FormSet = inlineformset_factory(Place, Location, can_delete=False)
  725. self.assertEqual(FormSet.max_num, 1)
  726. formset = FormSet(instance=place)
  727. self.assertEqual(len(formset.forms), 1)
  728. self.assertHTMLEqual(formset.forms[0].as_p(),
  729. '<p><label for="id_location_set-0-lat">Lat:</label> <input id="id_location_set-0-lat" type="text" name="location_set-0-lat" maxlength="100" /></p>\n'
  730. '<p><label for="id_location_set-0-lon">Lon:</label> <input id="id_location_set-0-lon" type="text" name="location_set-0-lon" maxlength="100" /><input type="hidden" name="location_set-0-place" value="1" id="id_location_set-0-place" /><input type="hidden" name="location_set-0-id" id="id_location_set-0-id" /></p>')
  731. def test_foreign_keys_in_parents(self):
  732. self.assertEqual(type(_get_foreign_key(Restaurant, Owner)), models.ForeignKey)
  733. self.assertEqual(type(_get_foreign_key(MexicanRestaurant, Owner)), models.ForeignKey)
  734. def test_unique_validation(self):
  735. FormSet = modelformset_factory(Product, extra=1)
  736. data = {
  737. 'form-TOTAL_FORMS': '1',
  738. 'form-INITIAL_FORMS': '0',
  739. 'form-MAX_NUM_FORMS': '',
  740. 'form-0-slug': 'car-red',
  741. }
  742. formset = FormSet(data)
  743. self.assertTrue(formset.is_valid())
  744. saved = formset.save()
  745. self.assertEqual(len(saved), 1)
  746. product1, = saved
  747. self.assertEqual(product1.slug, 'car-red')
  748. data = {
  749. 'form-TOTAL_FORMS': '1',
  750. 'form-INITIAL_FORMS': '0',
  751. 'form-MAX_NUM_FORMS': '',
  752. 'form-0-slug': 'car-red',
  753. }
  754. formset = FormSet(data)
  755. self.assertFalse(formset.is_valid())
  756. self.assertEqual(formset.errors, [{'slug': ['Product with this Slug already exists.']}])
  757. def test_unique_together_validation(self):
  758. FormSet = modelformset_factory(Price, extra=1)
  759. data = {
  760. 'form-TOTAL_FORMS': '1',
  761. 'form-INITIAL_FORMS': '0',
  762. 'form-MAX_NUM_FORMS': '',
  763. 'form-0-price': '12.00',
  764. 'form-0-quantity': '1',
  765. }
  766. formset = FormSet(data)
  767. self.assertTrue(formset.is_valid())
  768. saved = formset.save()
  769. self.assertEqual(len(saved), 1)
  770. price1, = saved
  771. self.assertEqual(price1.price, Decimal('12.00'))
  772. self.assertEqual(price1.quantity, 1)
  773. data = {
  774. 'form-TOTAL_FORMS': '1',
  775. 'form-INITIAL_FORMS': '0',
  776. 'form-MAX_NUM_FORMS': '',
  777. 'form-0-price': '12.00',
  778. 'form-0-quantity': '1',
  779. }
  780. formset = FormSet(data)
  781. self.assertFalse(formset.is_valid())
  782. self.assertEqual(formset.errors, [{'__all__': ['Price with this Price and Quantity already exists.']}])
  783. def test_unique_together_with_inlineformset_factory(self):
  784. # Also see bug #8882.
  785. repository = Repository.objects.create(name='Test Repo')
  786. FormSet = inlineformset_factory(Repository, Revision, extra=1)
  787. data = {
  788. 'revision_set-TOTAL_FORMS': '1',
  789. 'revision_set-INITIAL_FORMS': '0',
  790. 'revision_set-MAX_NUM_FORMS': '',
  791. 'revision_set-0-repository': repository.pk,
  792. 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
  793. 'revision_set-0-DELETE': '',
  794. }
  795. formset = FormSet(data, instance=repository)
  796. self.assertTrue(formset.is_valid())
  797. saved = formset.save()
  798. self.assertEqual(len(saved), 1)
  799. revision1, = saved
  800. self.assertEqual(revision1.repository, repository)
  801. self.assertEqual(revision1.revision, '146239817507f148d448db38840db7c3cbf47c76')
  802. # attempt to save the same revision against against the same repo.
  803. data = {
  804. 'revision_set-TOTAL_FORMS': '1',
  805. 'revision_set-INITIAL_FORMS': '0',
  806. 'revision_set-MAX_NUM_FORMS': '',
  807. 'revision_set-0-repository': repository.pk,
  808. 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
  809. 'revision_set-0-DELETE': '',
  810. }
  811. formset = FormSet(data, instance=repository)
  812. self.assertFalse(formset.is_valid())
  813. self.assertEqual(formset.errors, [{'__all__': ['Revision with this Repository and Revision already exists.']}])
  814. # unique_together with inlineformset_factory with overridden form fields
  815. # Also see #9494
  816. FormSet = inlineformset_factory(Repository, Revision, fields=('revision',), extra=1)
  817. data = {
  818. 'revision_set-TOTAL_FORMS': '1',
  819. 'revision_set-INITIAL_FORMS': '0',
  820. 'revision_set-MAX_NUM_FORMS': '',
  821. 'revision_set-0-repository': repository.pk,
  822. 'revision_set-0-revision': '146239817507f148d448db38840db7c3cbf47c76',
  823. 'revision_set-0-DELETE': '',
  824. }
  825. formset = FormSet(data, instance=repository)
  826. self.assertFalse(formset.is_valid())
  827. def test_callable_defaults(self):
  828. # Use of callable defaults (see bug #7975).
  829. person = Person.objects.create(name='Ringo')
  830. FormSet = inlineformset_factory(Person, Membership, can_delete=False, extra=1)
  831. formset = FormSet(instance=person)
  832. # Django will render a hidden field for model fields that have a callable
  833. # default. This is required to ensure the value is tested for change correctly
  834. # when determine what extra forms have changed to save.
  835. self.assertEqual(len(formset.forms), 1) # this formset only has one form
  836. form = formset.forms[0]
  837. now = form.fields['date_joined'].initial()
  838. result = form.as_p()
  839. result = re.sub(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?', '__DATETIME__', result)
  840. self.assertHTMLEqual(result,
  841. '<p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="__DATETIME__" id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="__DATETIME__" id="initial-membership_set-0-id_membership_set-0-date_joined" /></p>\n'
  842. '<p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="%d" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p>'
  843. % person.id)
  844. # test for validation with callable defaults. Validations rely on hidden fields
  845. data = {
  846. 'membership_set-TOTAL_FORMS': '1',
  847. 'membership_set-INITIAL_FORMS': '0',
  848. 'membership_set-MAX_NUM_FORMS': '',
  849. 'membership_set-0-date_joined': six.text_type(now.strftime('%Y-%m-%d %H:%M:%S')),
  850. 'initial-membership_set-0-date_joined': six.text_type(now.strftime('%Y-%m-%d %H:%M:%S')),
  851. 'membership_set-0-karma': '',
  852. }
  853. formset = FormSet(data, instance=person)
  854. self.assertTrue(formset.is_valid())
  855. # now test for when the data changes
  856. one_day_later = now + datetime.timedelta(days=1)
  857. filled_data = {
  858. 'membership_set-TOTAL_FORMS': '1',
  859. 'membership_set-INITIAL_FORMS': '0',
  860. 'membership_set-MAX_NUM_FORMS': '',
  861. 'membership_set-0-date_joined': six.text_type(one_day_later.strftime('%Y-%m-%d %H:%M:%S')),
  862. 'initial-membership_set-0-date_joined': six.text_type(now.strftime('%Y-%m-%d %H:%M:%S')),
  863. 'membership_set-0-karma': '',
  864. }
  865. formset = FormSet(filled_data, instance=person)
  866. self.assertFalse(formset.is_valid())
  867. # now test with split datetime fields
  868. class MembershipForm(forms.ModelForm):
  869. date_joined = forms.SplitDateTimeField(initial=now)
  870. class Meta:
  871. model = Membership
  872. def __init__(self, **kwargs):
  873. super(MembershipForm, self).__init__(**kwargs)
  874. self.fields['date_joined'].widget = forms.SplitDateTimeWidget()
  875. FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, can_delete=False, extra=1)
  876. data = {
  877. 'membership_set-TOTAL_FORMS': '1',
  878. 'membership_set-INITIAL_FORMS': '0',
  879. 'membership_set-MAX_NUM_FORMS': '',
  880. 'membership_set-0-date_joined_0': six.text_type(now.strftime('%Y-%m-%d')),
  881. 'membership_set-0-date_joined_1': six.text_type(now.strftime('%H:%M:%S')),
  882. 'initial-membership_set-0-date_joined': six.text_type(now.strftime('%Y-%m-%d %H:%M:%S')),
  883. 'membership_set-0-karma': '',
  884. }
  885. formset = FormSet(data, instance=person)
  886. self.assertTrue(formset.is_valid())
  887. def test_inlineformset_factory_with_null_fk(self):
  888. # inlineformset_factory tests with fk having null=True. see #9462.
  889. # create some data that will exbit the issue
  890. team = Team.objects.create(name="Red Vipers")
  891. Player(name="Timmy").save()
  892. Player(name="Bobby", team=team).save()
  893. PlayerInlineFormSet = inlineformset_factory(Team, Player)
  894. formset = PlayerInlineFormSet()
  895. self.assertQuerysetEqual(formset.get_queryset(), [])
  896. formset = PlayerInlineFormSet(instance=team)
  897. players = formset.get_queryset()
  898. self.assertEqual(len(players), 1)
  899. player1, = players
  900. self.assertEqual(player1.team, team)
  901. self.assertEqual(player1.name, 'Bobby')
  902. def test_model_formset_with_custom_pk(self):
  903. # a formset for a Model that has a custom primary key that still needs to be
  904. # added to the formset automatically
  905. FormSet = modelformset_factory(ClassyMexicanRestaurant, fields=["tacos_are_yummy"])
  906. self.assertEqual(sorted(FormSet().forms[0].fields.keys()), ['restaurant', 'tacos_are_yummy'])
  907. def test_prevent_duplicates_from_with_the_same_formset(self):
  908. FormSet = modelformset_factory(Product, extra=2)
  909. data = {
  910. 'form-TOTAL_FORMS': 2,
  911. 'form-INITIAL_FORMS': 0,
  912. 'form-MAX_NUM_FORMS': '',
  913. 'form-0-slug': 'red_car',
  914. 'form-1-slug': 'red_car',
  915. }
  916. formset = FormSet(data)
  917. self.assertFalse(formset.is_valid())
  918. self.assertEqual(formset._non_form_errors,
  919. ['Please correct the duplicate data for slug.'])
  920. FormSet = modelformset_factory(Price, extra=2)
  921. data = {
  922. 'form-TOTAL_FORMS': 2,
  923. 'form-INITIAL_FORMS': 0,
  924. 'form-MAX_NUM_FORMS': '',
  925. 'form-0-price': '25',
  926. 'form-0-quantity': '7',
  927. 'form-1-price': '25',
  928. 'form-1-quantity': '7',
  929. }
  930. formset = FormSet(data)
  931. self.assertFalse(formset.is_valid())
  932. self.assertEqual(formset._non_form_errors,
  933. ['Please correct the duplicate data for price and quantity, which must be unique.'])
  934. # Only the price field is specified, this should skip any unique checks since
  935. # the unique_together is not fulfilled. This will fail with a KeyError if broken.
  936. FormSet = modelformset_factory(Price, fields=("price",), extra=2)
  937. data = {
  938. 'form-TOTAL_FORMS': '2',
  939. 'form-INITIAL_FORMS': '0',
  940. 'form-MAX_NUM_FORMS': '',
  941. 'form-0-price': '24',
  942. 'form-1-price': '24',
  943. }
  944. formset = FormSet(data)
  945. self.assertTrue(formset.is_valid())
  946. FormSet = inlineformset_factory(Author, Book, extra=0)
  947. author = Author.objects.create(pk=1, name='Charles Baudelaire')
  948. book1 = Book.objects.create(pk=1, author=author, title='Les Paradis Artificiels')
  949. book2 = Book.objects.create(pk=2, author=author, title='Les Fleurs du Mal')
  950. book3 = Book.objects.create(pk=3, author=author, title='Flowers of Evil')
  951. book_ids = author.book_set.order_by('id').values_list('id', flat=True)
  952. data = {
  953. 'book_set-TOTAL_FORMS': '2',
  954. 'book_set-INITIAL_FORMS': '2',
  955. 'book_set-MAX_NUM_FORMS': '',
  956. 'book_set-0-title': 'The 2008 Election',
  957. 'book_set-0-author': str(author.id),
  958. 'book_set-0-id': str(book_ids[0]),
  959. 'book_set-1-title': 'The 2008 Election',
  960. 'book_set-1-author': str(author.id),
  961. 'book_set-1-id': str(book_ids[1]),
  962. }
  963. formset = FormSet(data=data, instance=author)
  964. self.assertFalse(formset.is_valid())
  965. self.assertEqual(formset._non_form_errors,
  966. ['Please correct the duplicate data for title.'])
  967. self.assertEqual(formset.errors,
  968. [{}, {'__all__': ['Please correct the duplicate values below.']}])
  969. FormSet = modelformset_factory(Post, extra=2)
  970. data = {
  971. 'form-TOTAL_FORMS': '2',
  972. 'form-INITIAL_FORMS': '0',
  973. 'form-MAX_NUM_FORMS': '',
  974. 'form-0-title': 'blah',
  975. 'form-0-slug': 'Morning',
  976. 'form-0-subtitle': 'foo',
  977. 'form-0-posted': '2009-01-01',
  978. 'form-1-title': 'blah',
  979. 'form-1-slug': 'Morning in Prague',
  980. 'form-1-subtitle': 'rawr',
  981. 'form-1-posted': '2009-01-01'
  982. }
  983. formset = FormSet(data)
  984. self.assertFalse(formset.is_valid())
  985. self.assertEqual(formset._non_form_errors,
  986. ['Please correct the duplicate data for title which must be unique for the date in posted.'])
  987. self.assertEqual(formset.errors,
  988. [{}, {'__all__': ['Please correct the duplicate values below.']}])
  989. data = {
  990. 'form-TOTAL_FORMS': '2',
  991. 'form-INITIAL_FORMS': '0',
  992. 'form-MAX_NUM_FORMS': '',
  993. 'form-0-title': 'foo',
  994. 'form-0-slug': 'Morning in Prague',
  995. 'form-0-subtitle': 'foo',
  996. 'form-0-posted': '2009-01-01',
  997. 'form-1-title': 'blah',
  998. 'form-1-slug': 'Morning in Prague',
  999. 'form-1-subtitle': 'rawr',
  1000. 'form-1-posted': '2009-08-02'
  1001. }
  1002. formset = FormSet(data)
  1003. self.assertFalse(formset.is_valid())
  1004. self.assertEqual(formset._non_form_errors,
  1005. ['Please correct the duplicate data for slug which must be unique for the year in posted.'])
  1006. data = {
  1007. 'form-TOTAL_FORMS': '2',
  1008. 'form-INITIAL_FORMS': '0',
  1009. 'form-MAX_NUM_FORMS': '',
  1010. 'form-0-title': 'foo',
  1011. 'form-0-slug': 'Morning in Prague',
  1012. 'form-0-subtitle': 'rawr',
  1013. 'form-0-posted': '2008-08-01',
  1014. 'form-1-title': 'blah',
  1015. 'form-1-slug': 'Prague',
  1016. 'form-1-subtitle': 'rawr',
  1017. 'form-1-posted': '2009-08-02'
  1018. }
  1019. formset = FormSet(data)
  1020. self.assertFalse(formset.is_valid())
  1021. self.assertEqual(formset._non_form_errors,
  1022. ['Please correct the duplicate data for subtitle which must be unique for the month in posted.'])
  1023. class TestModelFormsetWidgets(TestCase):
  1024. def test_modelformset_factory_widgets(self):
  1025. widgets = {
  1026. 'name': forms.TextInput(attrs={'class': 'poet'})
  1027. }
  1028. PoetFormSet = modelformset_factory(Poet, widgets=widgets)
  1029. form = PoetFormSet.form()
  1030. self.assertHTMLEqual(
  1031. "%s" % form['name'],
  1032. '<input id="id_name" maxlength="100" type="text" class="poet" name="name" />'
  1033. )
  1034. def test_inlineformset_factory_widgets(self):
  1035. widgets = {
  1036. 'title': forms.TextInput(attrs={'class': 'book'})
  1037. }
  1038. BookFormSet = inlineformset_factory(Author, Book, widgets=widgets)
  1039. form = BookFormSet.form()
  1040. self.assertHTMLEqual(
  1041. "%s" % form['title'],
  1042. '<input class="book" id="id_title" maxlength="100" name="title" type="text" />'
  1043. )