models.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. import datetime
  2. from django import forms
  3. from django.db import models
  4. try:
  5. sorted
  6. except NameError:
  7. from django.utils.itercompat import sorted
  8. class Author(models.Model):
  9. name = models.CharField(max_length=100)
  10. class Meta:
  11. ordering = ('name',)
  12. def __unicode__(self):
  13. return self.name
  14. class BetterAuthor(Author):
  15. write_speed = models.IntegerField()
  16. class Book(models.Model):
  17. author = models.ForeignKey(Author)
  18. title = models.CharField(max_length=100)
  19. def __unicode__(self):
  20. return self.title
  21. class AuthorMeeting(models.Model):
  22. name = models.CharField(max_length=100)
  23. authors = models.ManyToManyField(Author)
  24. created = models.DateField(editable=False)
  25. def __unicode__(self):
  26. return self.name
  27. class CustomPrimaryKey(models.Model):
  28. my_pk = models.CharField(max_length=10, primary_key=True)
  29. some_field = models.CharField(max_length=100)
  30. # models for inheritance tests.
  31. class Place(models.Model):
  32. name = models.CharField(max_length=50)
  33. city = models.CharField(max_length=50)
  34. def __unicode__(self):
  35. return self.name
  36. class Owner(models.Model):
  37. auto_id = models.AutoField(primary_key=True)
  38. name = models.CharField(max_length=100)
  39. place = models.ForeignKey(Place)
  40. def __unicode__(self):
  41. return "%s at %s" % (self.name, self.place)
  42. class Location(models.Model):
  43. place = models.ForeignKey(Place, unique=True)
  44. # this is purely for testing the data doesn't matter here :)
  45. lat = models.CharField(max_length=100)
  46. lon = models.CharField(max_length=100)
  47. class OwnerProfile(models.Model):
  48. owner = models.OneToOneField(Owner, primary_key=True)
  49. age = models.PositiveIntegerField()
  50. def __unicode__(self):
  51. return "%s is %d" % (self.owner.name, self.age)
  52. class Restaurant(Place):
  53. serves_pizza = models.BooleanField()
  54. def __unicode__(self):
  55. return self.name
  56. class Product(models.Model):
  57. slug = models.SlugField(unique=True)
  58. def __unicode__(self):
  59. return self.slug
  60. class Price(models.Model):
  61. price = models.DecimalField(max_digits=10, decimal_places=2)
  62. quantity = models.PositiveIntegerField()
  63. def __unicode__(self):
  64. return u"%s for %s" % (self.quantity, self.price)
  65. class Meta:
  66. unique_together = (('price', 'quantity'),)
  67. class MexicanRestaurant(Restaurant):
  68. serves_tacos = models.BooleanField()
  69. # models for testing callable defaults (see bug #7975). If you define a model
  70. # with a callable default value, you cannot rely on the initial value in a
  71. # form.
  72. class Person(models.Model):
  73. name = models.CharField(max_length=128)
  74. class Membership(models.Model):
  75. person = models.ForeignKey(Person)
  76. date_joined = models.DateTimeField(default=datetime.datetime.now)
  77. karma = models.IntegerField()
  78. __test__ = {'API_TESTS': """
  79. >>> from datetime import date
  80. >>> from django.forms.models import modelformset_factory
  81. >>> qs = Author.objects.all()
  82. >>> AuthorFormSet = modelformset_factory(Author, extra=3)
  83. >>> formset = AuthorFormSet(queryset=qs)
  84. >>> for form in formset.forms:
  85. ... print form.as_p()
  86. <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>
  87. <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>
  88. <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>
  89. >>> data = {
  90. ... 'form-TOTAL_FORMS': '3', # the number of forms rendered
  91. ... 'form-INITIAL_FORMS': '0', # the number of forms with initial data
  92. ... 'form-0-name': 'Charles Baudelaire',
  93. ... 'form-1-name': 'Arthur Rimbaud',
  94. ... 'form-2-name': '',
  95. ... }
  96. >>> formset = AuthorFormSet(data=data, queryset=qs)
  97. >>> formset.is_valid()
  98. True
  99. >>> formset.save()
  100. [<Author: Charles Baudelaire>, <Author: Arthur Rimbaud>]
  101. >>> for author in Author.objects.order_by('name'):
  102. ... print author.name
  103. Arthur Rimbaud
  104. Charles Baudelaire
  105. Gah! We forgot Paul Verlaine. Let's create a formset to edit the existing
  106. authors with an extra form to add him. We *could* pass in a queryset to
  107. restrict the Author objects we edit, but in this case we'll use it to display
  108. them in alphabetical order by name.
  109. >>> qs = Author.objects.order_by('name')
  110. >>> AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=False)
  111. >>> formset = AuthorFormSet(queryset=qs)
  112. >>> for form in formset.forms:
  113. ... print form.as_p()
  114. <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="2" id="id_form-0-id" /></p>
  115. <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="1" id="id_form-1-id" /></p>
  116. <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>
  117. >>> data = {
  118. ... 'form-TOTAL_FORMS': '3', # the number of forms rendered
  119. ... 'form-INITIAL_FORMS': '2', # the number of forms with initial data
  120. ... 'form-0-id': '2',
  121. ... 'form-0-name': 'Arthur Rimbaud',
  122. ... 'form-1-id': '1',
  123. ... 'form-1-name': 'Charles Baudelaire',
  124. ... 'form-2-name': 'Paul Verlaine',
  125. ... }
  126. >>> formset = AuthorFormSet(data=data, queryset=qs)
  127. >>> formset.is_valid()
  128. True
  129. # Only changed or new objects are returned from formset.save()
  130. >>> formset.save()
  131. [<Author: Paul Verlaine>]
  132. >>> for author in Author.objects.order_by('name'):
  133. ... print author.name
  134. Arthur Rimbaud
  135. Charles Baudelaire
  136. Paul Verlaine
  137. This probably shouldn't happen, but it will. If an add form was marked for
  138. deltetion, make sure we don't save that form.
  139. >>> qs = Author.objects.order_by('name')
  140. >>> AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=True)
  141. >>> formset = AuthorFormSet(queryset=qs)
  142. >>> for form in formset.forms:
  143. ... print form.as_p()
  144. <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>
  145. <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="2" id="id_form-0-id" /></p>
  146. <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>
  147. <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="1" id="id_form-1-id" /></p>
  148. <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>
  149. <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="3" id="id_form-2-id" /></p>
  150. <p><label for="id_form-3-name">Name:</label> <input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /></p>
  151. <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>
  152. >>> data = {
  153. ... 'form-TOTAL_FORMS': '4', # the number of forms rendered
  154. ... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
  155. ... 'form-0-id': '2',
  156. ... 'form-0-name': 'Arthur Rimbaud',
  157. ... 'form-1-id': '1',
  158. ... 'form-1-name': 'Charles Baudelaire',
  159. ... 'form-2-id': '3',
  160. ... 'form-2-name': 'Paul Verlaine',
  161. ... 'form-3-name': 'Walt Whitman',
  162. ... 'form-3-DELETE': 'on',
  163. ... }
  164. >>> formset = AuthorFormSet(data=data, queryset=qs)
  165. >>> formset.is_valid()
  166. True
  167. # No objects were changed or saved so nothing will come back.
  168. >>> formset.save()
  169. []
  170. >>> for author in Author.objects.order_by('name'):
  171. ... print author.name
  172. Arthur Rimbaud
  173. Charles Baudelaire
  174. Paul Verlaine
  175. Let's edit a record to ensure save only returns that one record.
  176. >>> data = {
  177. ... 'form-TOTAL_FORMS': '4', # the number of forms rendered
  178. ... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
  179. ... 'form-0-id': '2',
  180. ... 'form-0-name': 'Walt Whitman',
  181. ... 'form-1-id': '1',
  182. ... 'form-1-name': 'Charles Baudelaire',
  183. ... 'form-2-id': '3',
  184. ... 'form-2-name': 'Paul Verlaine',
  185. ... 'form-3-name': '',
  186. ... 'form-3-DELETE': '',
  187. ... }
  188. >>> formset = AuthorFormSet(data=data, queryset=qs)
  189. >>> formset.is_valid()
  190. True
  191. # One record has changed.
  192. >>> formset.save()
  193. [<Author: Walt Whitman>]
  194. Test the behavior of commit=False and save_m2m
  195. >>> meeting = AuthorMeeting.objects.create(created=date.today())
  196. >>> meeting.authors = Author.objects.all()
  197. # create an Author instance to add to the meeting.
  198. >>> new_author = Author.objects.create(name=u'John Steinbeck')
  199. >>> AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, extra=1, can_delete=True)
  200. >>> data = {
  201. ... 'form-TOTAL_FORMS': '2', # the number of forms rendered
  202. ... 'form-INITIAL_FORMS': '1', # the number of forms with initial data
  203. ... 'form-0-id': '1',
  204. ... 'form-0-name': '2nd Tuesday of the Week Meeting',
  205. ... 'form-0-authors': [2, 1, 3, 4],
  206. ... 'form-1-name': '',
  207. ... 'form-1-authors': '',
  208. ... 'form-1-DELETE': '',
  209. ... }
  210. >>> formset = AuthorMeetingFormSet(data=data, queryset=AuthorMeeting.objects.all())
  211. >>> formset.is_valid()
  212. True
  213. >>> instances = formset.save(commit=False)
  214. >>> for instance in instances:
  215. ... instance.created = date.today()
  216. ... instance.save()
  217. >>> formset.save_m2m()
  218. >>> instances[0].authors.all()
  219. [<Author: Charles Baudelaire>, <Author: John Steinbeck>, <Author: Paul Verlaine>, <Author: Walt Whitman>]
  220. # delete the author we created to allow later tests to continue working.
  221. >>> new_author.delete()
  222. Test the behavior of max_num with model formsets. It should properly limit
  223. the queryset to reduce the amount of objects being pulled in when not being
  224. used.
  225. >>> qs = Author.objects.order_by('name')
  226. >>> AuthorFormSet = modelformset_factory(Author, max_num=2)
  227. >>> formset = AuthorFormSet(queryset=qs)
  228. >>> [sorted(x.items()) for x in formset.initial]
  229. [[('id', 1), ('name', u'Charles Baudelaire')], [('id', 3), ('name', u'Paul Verlaine')]]
  230. >>> AuthorFormSet = modelformset_factory(Author, max_num=3)
  231. >>> formset = AuthorFormSet(queryset=qs)
  232. >>> [sorted(x.items()) for x in formset.initial]
  233. [[('id', 1), ('name', u'Charles Baudelaire')], [('id', 3), ('name', u'Paul Verlaine')], [('id', 2), ('name', u'Walt Whitman')]]
  234. # Model inheritance in model formsets ########################################
  235. >>> BetterAuthorFormSet = modelformset_factory(BetterAuthor)
  236. >>> formset = BetterAuthorFormSet()
  237. >>> for form in formset.forms:
  238. ... print form.as_p()
  239. <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>
  240. <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>
  241. >>> data = {
  242. ... 'form-TOTAL_FORMS': '1', # the number of forms rendered
  243. ... 'form-INITIAL_FORMS': '0', # the number of forms with initial data
  244. ... 'form-0-author_ptr': '',
  245. ... 'form-0-name': 'Ernest Hemingway',
  246. ... 'form-0-write_speed': '10',
  247. ... }
  248. >>> formset = BetterAuthorFormSet(data)
  249. >>> formset.is_valid()
  250. True
  251. >>> formset.save()
  252. [<BetterAuthor: Ernest Hemingway>]
  253. >>> hemingway_id = BetterAuthor.objects.get(name="Ernest Hemingway").pk
  254. >>> formset = BetterAuthorFormSet()
  255. >>> for form in formset.forms:
  256. ... print form.as_p()
  257. <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>
  258. <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="..." id="id_form-0-author_ptr" /></p>
  259. <p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>
  260. <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>
  261. >>> data = {
  262. ... 'form-TOTAL_FORMS': '2', # the number of forms rendered
  263. ... 'form-INITIAL_FORMS': '1', # the number of forms with initial data
  264. ... 'form-0-author_ptr': hemingway_id,
  265. ... 'form-0-name': 'Ernest Hemingway',
  266. ... 'form-0-write_speed': '10',
  267. ... 'form-1-author_ptr': '',
  268. ... 'form-1-name': '',
  269. ... 'form-1-write_speed': '',
  270. ... }
  271. >>> formset = BetterAuthorFormSet(data)
  272. >>> formset.is_valid()
  273. True
  274. >>> formset.save()
  275. []
  276. # Inline Formsets ############################################################
  277. We can also create a formset that is tied to a parent model. This is how the
  278. admin system's edit inline functionality works.
  279. >>> from django.forms.models import inlineformset_factory
  280. >>> AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3)
  281. >>> author = Author.objects.get(name='Charles Baudelaire')
  282. >>> formset = AuthorBooksFormSet(instance=author)
  283. >>> for form in formset.forms:
  284. ... print form.as_p()
  285. <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-id" id="id_book_set-0-id" /></p>
  286. <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-id" id="id_book_set-1-id" /></p>
  287. <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-id" id="id_book_set-2-id" /></p>
  288. >>> data = {
  289. ... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
  290. ... 'book_set-INITIAL_FORMS': '0', # the number of forms with initial data
  291. ... 'book_set-0-title': 'Les Fleurs du Mal',
  292. ... 'book_set-1-title': '',
  293. ... 'book_set-2-title': '',
  294. ... }
  295. >>> formset = AuthorBooksFormSet(data, instance=author)
  296. >>> formset.is_valid()
  297. True
  298. >>> formset.save()
  299. [<Book: Les Fleurs du Mal>]
  300. >>> for book in author.book_set.all():
  301. ... print book.title
  302. Les Fleurs du Mal
  303. Now that we've added a book to Charles Baudelaire, let's try adding another
  304. one. This time though, an edit form will be available for every existing
  305. book.
  306. >>> AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
  307. >>> author = Author.objects.get(name='Charles Baudelaire')
  308. >>> formset = AuthorBooksFormSet(instance=author)
  309. >>> for form in formset.forms:
  310. ... print form.as_p()
  311. <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-id" value="1" id="id_book_set-0-id" /></p>
  312. <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-id" id="id_book_set-1-id" /></p>
  313. <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-id" id="id_book_set-2-id" /></p>
  314. >>> data = {
  315. ... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
  316. ... 'book_set-INITIAL_FORMS': '1', # the number of forms with initial data
  317. ... 'book_set-0-id': '1',
  318. ... 'book_set-0-title': 'Les Fleurs du Mal',
  319. ... 'book_set-1-title': 'Le Spleen de Paris',
  320. ... 'book_set-2-title': '',
  321. ... }
  322. >>> formset = AuthorBooksFormSet(data, instance=author)
  323. >>> formset.is_valid()
  324. True
  325. >>> formset.save()
  326. [<Book: Le Spleen de Paris>]
  327. As you can see, 'Le Spleen de Paris' is now a book belonging to Charles Baudelaire.
  328. >>> for book in author.book_set.order_by('id'):
  329. ... print book.title
  330. Les Fleurs du Mal
  331. Le Spleen de Paris
  332. The save_as_new parameter lets you re-associate the data to a new instance.
  333. This is used in the admin for save_as functionality.
  334. >>> data = {
  335. ... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
  336. ... 'book_set-INITIAL_FORMS': '2', # the number of forms with initial data
  337. ... 'book_set-0-id': '1',
  338. ... 'book_set-0-title': 'Les Fleurs du Mal',
  339. ... 'book_set-1-id': '2',
  340. ... 'book_set-1-title': 'Le Spleen de Paris',
  341. ... 'book_set-2-title': '',
  342. ... }
  343. >>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
  344. >>> formset.is_valid()
  345. True
  346. >>> new_author = Author.objects.create(name='Charles Baudelaire')
  347. >>> formset.instance = new_author
  348. >>> [book for book in formset.save() if book.author.pk == new_author.pk]
  349. [<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
  350. Test using a custom prefix on an inline formset.
  351. >>> formset = AuthorBooksFormSet(prefix="test")
  352. >>> for form in formset.forms:
  353. ... print form.as_p()
  354. <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-id" id="id_test-0-id" /></p>
  355. <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-id" id="id_test-1-id" /></p>
  356. # Test a custom primary key ###################################################
  357. We need to ensure that it is displayed
  358. >>> CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey)
  359. >>> formset = CustomPrimaryKeyFormSet()
  360. >>> for form in formset.forms:
  361. ... print form.as_p()
  362. <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>
  363. <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>
  364. # Custom primary keys with ForeignKey, OneToOneField and AutoField ############
  365. >>> place = Place(name=u'Giordanos', city=u'Chicago')
  366. >>> place.save()
  367. >>> FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False)
  368. >>> formset = FormSet(instance=place)
  369. >>> for form in formset.forms:
  370. ... print form.as_p()
  371. <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-auto_id" id="id_owner_set-0-auto_id" /></p>
  372. <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-auto_id" id="id_owner_set-1-auto_id" /></p>
  373. >>> data = {
  374. ... 'owner_set-TOTAL_FORMS': '2',
  375. ... 'owner_set-INITIAL_FORMS': '0',
  376. ... 'owner_set-0-auto_id': '',
  377. ... 'owner_set-0-name': u'Joe Perry',
  378. ... 'owner_set-1-auto_id': '',
  379. ... 'owner_set-1-name': '',
  380. ... }
  381. >>> formset = FormSet(data, instance=place)
  382. >>> formset.is_valid()
  383. True
  384. >>> formset.save()
  385. [<Owner: Joe Perry at Giordanos>]
  386. >>> formset = FormSet(instance=place)
  387. >>> for form in formset.forms:
  388. ... print form.as_p()
  389. <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-auto_id" value="1" id="id_owner_set-0-auto_id" /></p>
  390. <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-auto_id" id="id_owner_set-1-auto_id" /></p>
  391. <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-auto_id" id="id_owner_set-2-auto_id" /></p>
  392. >>> data = {
  393. ... 'owner_set-TOTAL_FORMS': '3',
  394. ... 'owner_set-INITIAL_FORMS': '1',
  395. ... 'owner_set-0-auto_id': u'1',
  396. ... 'owner_set-0-name': u'Joe Perry',
  397. ... 'owner_set-1-auto_id': '',
  398. ... 'owner_set-1-name': u'Jack Berry',
  399. ... 'owner_set-2-auto_id': '',
  400. ... 'owner_set-2-name': '',
  401. ... }
  402. >>> formset = FormSet(data, instance=place)
  403. >>> formset.is_valid()
  404. True
  405. >>> formset.save()
  406. [<Owner: Jack Berry at Giordanos>]
  407. # Ensure a custom primary key that is a ForeignKey or OneToOneField get rendered for the user to choose.
  408. >>> FormSet = modelformset_factory(OwnerProfile)
  409. >>> formset = FormSet()
  410. >>> for form in formset.forms:
  411. ... print form.as_p()
  412. <p><label for="id_form-0-owner">Owner:</label> <select name="form-0-owner" id="id_form-0-owner">
  413. <option value="" selected="selected">---------</option>
  414. <option value="1">Joe Perry at Giordanos</option>
  415. <option value="2">Jack Berry at Giordanos</option>
  416. </select></p>
  417. <p><label for="id_form-0-age">Age:</label> <input type="text" name="form-0-age" id="id_form-0-age" /></p>
  418. >>> owner = Owner.objects.get(name=u'Joe Perry')
  419. >>> FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False)
  420. >>> formset = FormSet(instance=owner)
  421. >>> for form in formset.forms:
  422. ... print form.as_p()
  423. <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" id="id_ownerprofile-0-owner" /></p>
  424. >>> data = {
  425. ... 'ownerprofile-TOTAL_FORMS': '1',
  426. ... 'ownerprofile-INITIAL_FORMS': '0',
  427. ... 'ownerprofile-0-owner': '',
  428. ... 'ownerprofile-0-age': u'54',
  429. ... }
  430. >>> formset = FormSet(data, instance=owner)
  431. >>> formset.is_valid()
  432. True
  433. >>> formset.save()
  434. [<OwnerProfile: Joe Perry is 54>]
  435. >>> formset = FormSet(instance=owner)
  436. >>> for form in formset.forms:
  437. ... print form.as_p()
  438. <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="1" id="id_ownerprofile-0-owner" /></p>
  439. >>> data = {
  440. ... 'ownerprofile-TOTAL_FORMS': '1',
  441. ... 'ownerprofile-INITIAL_FORMS': '1',
  442. ... 'ownerprofile-0-owner': u'1',
  443. ... 'ownerprofile-0-age': u'55',
  444. ... }
  445. >>> formset = FormSet(data, instance=owner)
  446. >>> formset.is_valid()
  447. True
  448. >>> formset.save()
  449. [<OwnerProfile: Joe Perry is 55>]
  450. # ForeignKey with unique=True should enforce max_num=1
  451. >>> FormSet = inlineformset_factory(Place, Location, can_delete=False)
  452. >>> formset = FormSet(instance=place)
  453. >>> for form in formset.forms:
  454. ... print form.as_p()
  455. <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>
  456. <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-id" id="id_location_set-0-id" /></p>
  457. # Foreign keys in parents ########################################
  458. >>> from django.forms.models import _get_foreign_key
  459. >>> type(_get_foreign_key(Restaurant, Owner))
  460. <class 'django.db.models.fields.related.ForeignKey'>
  461. >>> type(_get_foreign_key(MexicanRestaurant, Owner))
  462. <class 'django.db.models.fields.related.ForeignKey'>
  463. # unique/unique_together validation ###########################################
  464. >>> FormSet = modelformset_factory(Product, extra=1)
  465. >>> data = {
  466. ... 'form-TOTAL_FORMS': '1',
  467. ... 'form-INITIAL_FORMS': '0',
  468. ... 'form-0-slug': 'car-red',
  469. ... }
  470. >>> formset = FormSet(data)
  471. >>> formset.is_valid()
  472. True
  473. >>> formset.save()
  474. [<Product: car-red>]
  475. >>> data = {
  476. ... 'form-TOTAL_FORMS': '1',
  477. ... 'form-INITIAL_FORMS': '0',
  478. ... 'form-0-slug': 'car-red',
  479. ... }
  480. >>> formset = FormSet(data)
  481. >>> formset.is_valid()
  482. False
  483. >>> formset.errors
  484. [{'slug': [u'Product with this Slug already exists.']}]
  485. # unique_together
  486. >>> FormSet = modelformset_factory(Price, extra=1)
  487. >>> data = {
  488. ... 'form-TOTAL_FORMS': '1',
  489. ... 'form-INITIAL_FORMS': '0',
  490. ... 'form-0-price': u'12.00',
  491. ... 'form-0-quantity': '1',
  492. ... }
  493. >>> formset = FormSet(data)
  494. >>> formset.is_valid()
  495. True
  496. >>> formset.save()
  497. [<Price: 1 for 12.00>]
  498. >>> data = {
  499. ... 'form-TOTAL_FORMS': '1',
  500. ... 'form-INITIAL_FORMS': '0',
  501. ... 'form-0-price': u'12.00',
  502. ... 'form-0-quantity': '1',
  503. ... }
  504. >>> formset = FormSet(data)
  505. >>> formset.is_valid()
  506. False
  507. >>> formset.errors
  508. [{'__all__': [u'Price with this Price and Quantity already exists.']}]
  509. # Use of callable defaults (see bug #7975).
  510. >>> person = Person.objects.create(name='Ringo')
  511. >>> FormSet = inlineformset_factory(Person, Membership, can_delete=False, extra=1)
  512. >>> formset = FormSet(instance=person)
  513. # Django will render a hidden field for model fields that have a callable
  514. # default. This is required to ensure the value is tested for change correctly
  515. # when determine what extra forms have changed to save.
  516. >>> form = formset.forms[0] # this formset only has one form
  517. >>> now = form.fields['date_joined'].initial
  518. >>> print form.as_p()
  519. <p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /></p>
  520. <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-id" id="id_membership_set-0-id" /></p>
  521. # test for validation with callable defaults. Validations rely on hidden fields
  522. >>> data = {
  523. ... 'membership_set-TOTAL_FORMS': '1',
  524. ... 'membership_set-INITIAL_FORMS': '0',
  525. ... 'membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
  526. ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
  527. ... 'membership_set-0-karma': '',
  528. ... }
  529. >>> formset = FormSet(data, instance=person)
  530. >>> formset.is_valid()
  531. True
  532. # now test for when the data changes
  533. >>> one_day_later = now + datetime.timedelta(days=1)
  534. >>> filled_data = {
  535. ... 'membership_set-TOTAL_FORMS': '1',
  536. ... 'membership_set-INITIAL_FORMS': '0',
  537. ... 'membership_set-0-date_joined': unicode(one_day_later.strftime('%Y-%m-%d %H:%M:%S')),
  538. ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
  539. ... 'membership_set-0-karma': '',
  540. ... }
  541. >>> formset = FormSet(filled_data, instance=person)
  542. >>> formset.is_valid()
  543. False
  544. # now test with split datetime fields
  545. >>> class MembershipForm(forms.ModelForm):
  546. ... date_joined = forms.SplitDateTimeField(initial=now)
  547. ... class Meta:
  548. ... model = Membership
  549. ... def __init__(self, **kwargs):
  550. ... super(MembershipForm, self).__init__(**kwargs)
  551. ... self.fields['date_joined'].widget = forms.SplitDateTimeWidget()
  552. >>> FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, can_delete=False, extra=1)
  553. >>> data = {
  554. ... 'membership_set-TOTAL_FORMS': '1',
  555. ... 'membership_set-INITIAL_FORMS': '0',
  556. ... 'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')),
  557. ... 'membership_set-0-date_joined_1': unicode(now.strftime('%H:%M:%S')),
  558. ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')),
  559. ... 'membership_set-0-karma': '',
  560. ... }
  561. >>> formset = FormSet(data, instance=person)
  562. >>> formset.is_valid()
  563. True
  564. """}