PageRenderTime 101ms CodeModel.GetById 14ms app.highlight 77ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/modeltests/model_formsets/tests.py

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