1. import datetime
    
  2. import re
    
  3. from datetime import date
    
  4. from decimal import Decimal
    
  5. 
    
  6. from django import forms
    
  7. from django.core.exceptions import ImproperlyConfigured
    
  8. from django.db import models
    
  9. from django.forms.formsets import formset_factory
    
  10. from django.forms.models import (
    
  11.     BaseModelFormSet,
    
  12.     _get_foreign_key,
    
  13.     inlineformset_factory,
    
  14.     modelformset_factory,
    
  15. )
    
  16. from django.http import QueryDict
    
  17. from django.test import TestCase, skipUnlessDBFeature
    
  18. 
    
  19. from .models import (
    
  20.     AlternateBook,
    
  21.     Author,
    
  22.     AuthorMeeting,
    
  23.     BetterAuthor,
    
  24.     Book,
    
  25.     BookWithCustomPK,
    
  26.     BookWithOptionalAltEditor,
    
  27.     ClassyMexicanRestaurant,
    
  28.     CustomPrimaryKey,
    
  29.     Location,
    
  30.     Membership,
    
  31.     MexicanRestaurant,
    
  32.     Owner,
    
  33.     OwnerProfile,
    
  34.     Person,
    
  35.     Place,
    
  36.     Player,
    
  37.     Poem,
    
  38.     Poet,
    
  39.     Post,
    
  40.     Price,
    
  41.     Product,
    
  42.     Repository,
    
  43.     Restaurant,
    
  44.     Revision,
    
  45.     Team,
    
  46. )
    
  47. 
    
  48. 
    
  49. class DeletionTests(TestCase):
    
  50.     def test_deletion(self):
    
  51.         PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
    
  52.         poet = Poet.objects.create(name="test")
    
  53.         data = {
    
  54.             "form-TOTAL_FORMS": "1",
    
  55.             "form-INITIAL_FORMS": "1",
    
  56.             "form-MAX_NUM_FORMS": "0",
    
  57.             "form-0-id": str(poet.pk),
    
  58.             "form-0-name": "test",
    
  59.             "form-0-DELETE": "on",
    
  60.         }
    
  61.         formset = PoetFormSet(data, queryset=Poet.objects.all())
    
  62.         formset.save(commit=False)
    
  63.         self.assertEqual(Poet.objects.count(), 1)
    
  64. 
    
  65.         formset.save()
    
  66.         self.assertTrue(formset.is_valid())
    
  67.         self.assertEqual(Poet.objects.count(), 0)
    
  68. 
    
  69.     def test_add_form_deletion_when_invalid(self):
    
  70.         """
    
  71.         Make sure that an add form that is filled out, but marked for deletion
    
  72.         doesn't cause validation errors.
    
  73.         """
    
  74.         PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
    
  75.         poet = Poet.objects.create(name="test")
    
  76.         # One existing untouched and two new unvalid forms
    
  77.         data = {
    
  78.             "form-TOTAL_FORMS": "3",
    
  79.             "form-INITIAL_FORMS": "1",
    
  80.             "form-MAX_NUM_FORMS": "0",
    
  81.             "form-0-id": str(poet.id),
    
  82.             "form-0-name": "test",
    
  83.             "form-1-id": "",
    
  84.             "form-1-name": "x" * 1000,  # Too long
    
  85.             "form-2-id": str(poet.id),  # Violate unique constraint
    
  86.             "form-2-name": "test2",
    
  87.         }
    
  88.         formset = PoetFormSet(data, queryset=Poet.objects.all())
    
  89.         # Make sure this form doesn't pass validation.
    
  90.         self.assertIs(formset.is_valid(), False)
    
  91.         self.assertEqual(Poet.objects.count(), 1)
    
  92. 
    
  93.         # Then make sure that it *does* pass validation and delete the object,
    
  94.         # even though the data in new forms aren't actually valid.
    
  95.         data["form-0-DELETE"] = "on"
    
  96.         data["form-1-DELETE"] = "on"
    
  97.         data["form-2-DELETE"] = "on"
    
  98.         formset = PoetFormSet(data, queryset=Poet.objects.all())
    
  99.         self.assertIs(formset.is_valid(), True)
    
  100.         formset.save()
    
  101.         self.assertEqual(Poet.objects.count(), 0)
    
  102. 
    
  103.     def test_change_form_deletion_when_invalid(self):
    
  104.         """
    
  105.         Make sure that a change form that is filled out, but marked for deletion
    
  106.         doesn't cause validation errors.
    
  107.         """
    
  108.         PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
    
  109.         poet = Poet.objects.create(name="test")
    
  110.         data = {
    
  111.             "form-TOTAL_FORMS": "1",
    
  112.             "form-INITIAL_FORMS": "1",
    
  113.             "form-MAX_NUM_FORMS": "0",
    
  114.             "form-0-id": str(poet.id),
    
  115.             "form-0-name": "x" * 1000,
    
  116.         }
    
  117.         formset = PoetFormSet(data, queryset=Poet.objects.all())
    
  118.         # Make sure this form doesn't pass validation.
    
  119.         self.assertIs(formset.is_valid(), False)
    
  120.         self.assertEqual(Poet.objects.count(), 1)
    
  121. 
    
  122.         # Then make sure that it *does* pass validation and delete the object,
    
  123.         # even though the data isn't actually valid.
    
  124.         data["form-0-DELETE"] = "on"
    
  125.         formset = PoetFormSet(data, queryset=Poet.objects.all())
    
  126.         self.assertIs(formset.is_valid(), True)
    
  127.         formset.save()
    
  128.         self.assertEqual(Poet.objects.count(), 0)
    
  129. 
    
  130.     def test_outdated_deletion(self):
    
  131.         poet = Poet.objects.create(name="test")
    
  132.         poem = Poem.objects.create(name="Brevity is the soul of wit", poet=poet)
    
  133. 
    
  134.         PoemFormSet = inlineformset_factory(
    
  135.             Poet, Poem, fields="__all__", can_delete=True
    
  136.         )
    
  137. 
    
  138.         # Simulate deletion of an object that doesn't exist in the database
    
  139.         data = {
    
  140.             "form-TOTAL_FORMS": "2",
    
  141.             "form-INITIAL_FORMS": "2",
    
  142.             "form-0-id": str(poem.pk),
    
  143.             "form-0-name": "foo",
    
  144.             "form-1-id": str(poem.pk + 1),  # doesn't exist
    
  145.             "form-1-name": "bar",
    
  146.             "form-1-DELETE": "on",
    
  147.         }
    
  148.         formset = PoemFormSet(data, instance=poet, prefix="form")
    
  149. 
    
  150.         # The formset is valid even though poem.pk + 1 doesn't exist,
    
  151.         # because it's marked for deletion anyway
    
  152.         self.assertTrue(formset.is_valid())
    
  153. 
    
  154.         formset.save()
    
  155. 
    
  156.         # Make sure the save went through correctly
    
  157.         self.assertEqual(Poem.objects.get(pk=poem.pk).name, "foo")
    
  158.         self.assertEqual(poet.poem_set.count(), 1)
    
  159.         self.assertFalse(Poem.objects.filter(pk=poem.pk + 1).exists())
    
  160. 
    
  161. 
    
  162. class ModelFormsetTest(TestCase):
    
  163.     def test_modelformset_factory_without_fields(self):
    
  164.         """Regression for #19733"""
    
  165.         message = (
    
  166.             "Calling modelformset_factory without defining 'fields' or 'exclude' "
    
  167.             "explicitly is prohibited."
    
  168.         )
    
  169.         with self.assertRaisesMessage(ImproperlyConfigured, message):
    
  170.             modelformset_factory(Author)
    
  171. 
    
  172.     def test_simple_save(self):
    
  173.         qs = Author.objects.all()
    
  174.         AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=3)
    
  175. 
    
  176.         formset = AuthorFormSet(queryset=qs)
    
  177.         self.assertEqual(len(formset.forms), 3)
    
  178.         self.assertHTMLEqual(
    
  179.             formset.forms[0].as_p(),
    
  180.             '<p><label for="id_form-0-name">Name:</label>'
    
  181.             '<input id="id_form-0-name" type="text" name="form-0-name" maxlength="100">'
    
  182.             '<input type="hidden" name="form-0-id" id="id_form-0-id"></p>',
    
  183.         )
    
  184.         self.assertHTMLEqual(
    
  185.             formset.forms[1].as_p(),
    
  186.             '<p><label for="id_form-1-name">Name:</label>'
    
  187.             '<input id="id_form-1-name" type="text" name="form-1-name" maxlength="100">'
    
  188.             '<input type="hidden" name="form-1-id" id="id_form-1-id"></p>',
    
  189.         )
    
  190.         self.assertHTMLEqual(
    
  191.             formset.forms[2].as_p(),
    
  192.             '<p><label for="id_form-2-name">Name:</label>'
    
  193.             '<input id="id_form-2-name" type="text" name="form-2-name" maxlength="100">'
    
  194.             '<input type="hidden" name="form-2-id" id="id_form-2-id"></p>',
    
  195.         )
    
  196. 
    
  197.         data = {
    
  198.             "form-TOTAL_FORMS": "3",  # the number of forms rendered
    
  199.             "form-INITIAL_FORMS": "0",  # the number of forms with initial data
    
  200.             "form-MAX_NUM_FORMS": "",  # the max number of forms
    
  201.             "form-0-name": "Charles Baudelaire",
    
  202.             "form-1-name": "Arthur Rimbaud",
    
  203.             "form-2-name": "",
    
  204.         }
    
  205. 
    
  206.         formset = AuthorFormSet(data=data, queryset=qs)
    
  207.         self.assertTrue(formset.is_valid())
    
  208. 
    
  209.         saved = formset.save()
    
  210.         self.assertEqual(len(saved), 2)
    
  211.         author1, author2 = saved
    
  212.         self.assertEqual(author1, Author.objects.get(name="Charles Baudelaire"))
    
  213.         self.assertEqual(author2, Author.objects.get(name="Arthur Rimbaud"))
    
  214. 
    
  215.         authors = list(Author.objects.order_by("name"))
    
  216.         self.assertEqual(authors, [author2, author1])
    
  217. 
    
  218.         # Gah! We forgot Paul Verlaine. Let's create a formset to edit the
    
  219.         # existing authors with an extra form to add him. We *could* pass in a
    
  220.         # queryset to restrict the Author objects we edit, but in this case
    
  221.         # we'll use it to display them in alphabetical order by name.
    
  222. 
    
  223.         qs = Author.objects.order_by("name")
    
  224.         AuthorFormSet = modelformset_factory(
    
  225.             Author, fields="__all__", extra=1, can_delete=False
    
  226.         )
    
  227. 
    
  228.         formset = AuthorFormSet(queryset=qs)
    
  229.         self.assertEqual(len(formset.forms), 3)
    
  230.         self.assertHTMLEqual(
    
  231.             formset.forms[0].as_p(),
    
  232.             '<p><label for="id_form-0-name">Name:</label>'
    
  233.             '<input id="id_form-0-name" type="text" name="form-0-name" '
    
  234.             'value="Arthur Rimbaud" maxlength="100">'
    
  235.             '<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id"></p>'
    
  236.             % author2.id,
    
  237.         )
    
  238.         self.assertHTMLEqual(
    
  239.             formset.forms[1].as_p(),
    
  240.             '<p><label for="id_form-1-name">Name:</label>'
    
  241.             '<input id="id_form-1-name" type="text" name="form-1-name" '
    
  242.             'value="Charles Baudelaire" maxlength="100">'
    
  243.             '<input type="hidden" name="form-1-id" value="%d" id="id_form-1-id"></p>'
    
  244.             % author1.id,
    
  245.         )
    
  246.         self.assertHTMLEqual(
    
  247.             formset.forms[2].as_p(),
    
  248.             '<p><label for="id_form-2-name">Name:</label>'
    
  249.             '<input id="id_form-2-name" type="text" name="form-2-name" maxlength="100">'
    
  250.             '<input type="hidden" name="form-2-id" id="id_form-2-id"></p>',
    
  251.         )
    
  252. 
    
  253.         data = {
    
  254.             "form-TOTAL_FORMS": "3",  # the number of forms rendered
    
  255.             "form-INITIAL_FORMS": "2",  # the number of forms with initial data
    
  256.             "form-MAX_NUM_FORMS": "",  # the max number of forms
    
  257.             "form-0-id": str(author2.id),
    
  258.             "form-0-name": "Arthur Rimbaud",
    
  259.             "form-1-id": str(author1.id),
    
  260.             "form-1-name": "Charles Baudelaire",
    
  261.             "form-2-name": "Paul Verlaine",
    
  262.         }
    
  263. 
    
  264.         formset = AuthorFormSet(data=data, queryset=qs)
    
  265.         self.assertTrue(formset.is_valid())
    
  266. 
    
  267.         # Only changed or new objects are returned from formset.save()
    
  268.         saved = formset.save()
    
  269.         self.assertEqual(len(saved), 1)
    
  270.         author3 = saved[0]
    
  271.         self.assertEqual(author3, Author.objects.get(name="Paul Verlaine"))
    
  272. 
    
  273.         authors = list(Author.objects.order_by("name"))
    
  274.         self.assertEqual(authors, [author2, author1, author3])
    
  275. 
    
  276.         # This probably shouldn't happen, but it will. If an add form was
    
  277.         # marked for deletion, make sure we don't save that form.
    
  278. 
    
  279.         qs = Author.objects.order_by("name")
    
  280.         AuthorFormSet = modelformset_factory(
    
  281.             Author, fields="__all__", extra=1, can_delete=True
    
  282.         )
    
  283. 
    
  284.         formset = AuthorFormSet(queryset=qs)
    
  285.         self.assertEqual(len(formset.forms), 4)
    
  286.         self.assertHTMLEqual(
    
  287.             formset.forms[0].as_p(),
    
  288.             '<p><label for="id_form-0-name">Name:</label>'
    
  289.             '<input id="id_form-0-name" type="text" name="form-0-name" '
    
  290.             'value="Arthur Rimbaud" maxlength="100"></p>'
    
  291.             '<p><label for="id_form-0-DELETE">Delete:</label>'
    
  292.             '<input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE">'
    
  293.             '<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id"></p>'
    
  294.             % author2.id,
    
  295.         )
    
  296.         self.assertHTMLEqual(
    
  297.             formset.forms[1].as_p(),
    
  298.             '<p><label for="id_form-1-name">Name:</label>'
    
  299.             '<input id="id_form-1-name" type="text" name="form-1-name" '
    
  300.             'value="Charles Baudelaire" maxlength="100"></p>'
    
  301.             '<p><label for="id_form-1-DELETE">Delete:</label>'
    
  302.             '<input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE">'
    
  303.             '<input type="hidden" name="form-1-id" value="%d" id="id_form-1-id"></p>'
    
  304.             % author1.id,
    
  305.         )
    
  306.         self.assertHTMLEqual(
    
  307.             formset.forms[2].as_p(),
    
  308.             '<p><label for="id_form-2-name">Name:</label>'
    
  309.             '<input id="id_form-2-name" type="text" name="form-2-name" '
    
  310.             'value="Paul Verlaine" maxlength="100"></p>'
    
  311.             '<p><label for="id_form-2-DELETE">Delete:</label>'
    
  312.             '<input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE">'
    
  313.             '<input type="hidden" name="form-2-id" value="%d" id="id_form-2-id"></p>'
    
  314.             % author3.id,
    
  315.         )
    
  316.         self.assertHTMLEqual(
    
  317.             formset.forms[3].as_p(),
    
  318.             '<p><label for="id_form-3-name">Name:</label>'
    
  319.             '<input id="id_form-3-name" type="text" name="form-3-name" maxlength="100">'
    
  320.             '</p><p><label for="id_form-3-DELETE">Delete:</label>'
    
  321.             '<input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE">'
    
  322.             '<input type="hidden" name="form-3-id" id="id_form-3-id"></p>',
    
  323.         )
    
  324. 
    
  325.         data = {
    
  326.             "form-TOTAL_FORMS": "4",  # the number of forms rendered
    
  327.             "form-INITIAL_FORMS": "3",  # the number of forms with initial data
    
  328.             "form-MAX_NUM_FORMS": "",  # the max number of forms
    
  329.             "form-0-id": str(author2.id),
    
  330.             "form-0-name": "Arthur Rimbaud",
    
  331.             "form-1-id": str(author1.id),
    
  332.             "form-1-name": "Charles Baudelaire",
    
  333.             "form-2-id": str(author3.id),
    
  334.             "form-2-name": "Paul Verlaine",
    
  335.             "form-3-name": "Walt Whitman",
    
  336.             "form-3-DELETE": "on",
    
  337.         }
    
  338. 
    
  339.         formset = AuthorFormSet(data=data, queryset=qs)
    
  340.         self.assertTrue(formset.is_valid())
    
  341. 
    
  342.         # No objects were changed or saved so nothing will come back.
    
  343. 
    
  344.         self.assertEqual(formset.save(), [])
    
  345. 
    
  346.         authors = list(Author.objects.order_by("name"))
    
  347.         self.assertEqual(authors, [author2, author1, author3])
    
  348. 
    
  349.         # Let's edit a record to ensure save only returns that one record.
    
  350. 
    
  351.         data = {
    
  352.             "form-TOTAL_FORMS": "4",  # the number of forms rendered
    
  353.             "form-INITIAL_FORMS": "3",  # the number of forms with initial data
    
  354.             "form-MAX_NUM_FORMS": "",  # the max number of forms
    
  355.             "form-0-id": str(author2.id),
    
  356.             "form-0-name": "Walt Whitman",
    
  357.             "form-1-id": str(author1.id),
    
  358.             "form-1-name": "Charles Baudelaire",
    
  359.             "form-2-id": str(author3.id),
    
  360.             "form-2-name": "Paul Verlaine",
    
  361.             "form-3-name": "",
    
  362.             "form-3-DELETE": "",
    
  363.         }
    
  364. 
    
  365.         formset = AuthorFormSet(data=data, queryset=qs)
    
  366.         self.assertTrue(formset.is_valid())
    
  367. 
    
  368.         # One record has changed.
    
  369. 
    
  370.         saved = formset.save()
    
  371.         self.assertEqual(len(saved), 1)
    
  372.         self.assertEqual(saved[0], Author.objects.get(name="Walt Whitman"))
    
  373. 
    
  374.     def test_commit_false(self):
    
  375.         # Test the behavior of commit=False and save_m2m
    
  376. 
    
  377.         author1 = Author.objects.create(name="Charles Baudelaire")
    
  378.         author2 = Author.objects.create(name="Paul Verlaine")
    
  379.         author3 = Author.objects.create(name="Walt Whitman")
    
  380. 
    
  381.         meeting = AuthorMeeting.objects.create(created=date.today())
    
  382.         meeting.authors.set(Author.objects.all())
    
  383. 
    
  384.         # create an Author instance to add to the meeting.
    
  385. 
    
  386.         author4 = Author.objects.create(name="John Steinbeck")
    
  387. 
    
  388.         AuthorMeetingFormSet = modelformset_factory(
    
  389.             AuthorMeeting, fields="__all__", extra=1, can_delete=True
    
  390.         )
    
  391.         data = {
    
  392.             "form-TOTAL_FORMS": "2",  # the number of forms rendered
    
  393.             "form-INITIAL_FORMS": "1",  # the number of forms with initial data
    
  394.             "form-MAX_NUM_FORMS": "",  # the max number of forms
    
  395.             "form-0-id": str(meeting.id),
    
  396.             "form-0-name": "2nd Tuesday of the Week Meeting",
    
  397.             "form-0-authors": [author2.id, author1.id, author3.id, author4.id],
    
  398.             "form-1-name": "",
    
  399.             "form-1-authors": "",
    
  400.             "form-1-DELETE": "",
    
  401.         }
    
  402.         formset = AuthorMeetingFormSet(data=data, queryset=AuthorMeeting.objects.all())
    
  403.         self.assertTrue(formset.is_valid())
    
  404. 
    
  405.         instances = formset.save(commit=False)
    
  406.         for instance in instances:
    
  407.             instance.created = date.today()
    
  408.             instance.save()
    
  409.         formset.save_m2m()
    
  410.         self.assertSequenceEqual(
    
  411.             instances[0].authors.all(),
    
  412.             [author1, author4, author2, author3],
    
  413.         )
    
  414. 
    
  415.     def test_max_num(self):
    
  416.         # Test the behavior of max_num with model formsets. It should allow
    
  417.         # all existing related objects/inlines for a given object to be
    
  418.         # displayed, but not allow the creation of new inlines beyond max_num.
    
  419. 
    
  420.         a1 = Author.objects.create(name="Charles Baudelaire")
    
  421.         a2 = Author.objects.create(name="Paul Verlaine")
    
  422.         a3 = Author.objects.create(name="Walt Whitman")
    
  423. 
    
  424.         qs = Author.objects.order_by("name")
    
  425. 
    
  426.         AuthorFormSet = modelformset_factory(
    
  427.             Author, fields="__all__", max_num=None, extra=3
    
  428.         )
    
  429.         formset = AuthorFormSet(queryset=qs)
    
  430.         self.assertEqual(len(formset.forms), 6)
    
  431.         self.assertEqual(len(formset.extra_forms), 3)
    
  432. 
    
  433.         AuthorFormSet = modelformset_factory(
    
  434.             Author, fields="__all__", max_num=4, extra=3
    
  435.         )
    
  436.         formset = AuthorFormSet(queryset=qs)
    
  437.         self.assertEqual(len(formset.forms), 4)
    
  438.         self.assertEqual(len(formset.extra_forms), 1)
    
  439. 
    
  440.         AuthorFormSet = modelformset_factory(
    
  441.             Author, fields="__all__", max_num=0, extra=3
    
  442.         )
    
  443.         formset = AuthorFormSet(queryset=qs)
    
  444.         self.assertEqual(len(formset.forms), 3)
    
  445.         self.assertEqual(len(formset.extra_forms), 0)
    
  446. 
    
  447.         AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=None)
    
  448.         formset = AuthorFormSet(queryset=qs)
    
  449.         self.assertSequenceEqual(formset.get_queryset(), [a1, a2, a3])
    
  450. 
    
  451.         AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=0)
    
  452.         formset = AuthorFormSet(queryset=qs)
    
  453.         self.assertSequenceEqual(formset.get_queryset(), [a1, a2, a3])
    
  454. 
    
  455.         AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=4)
    
  456.         formset = AuthorFormSet(queryset=qs)
    
  457.         self.assertSequenceEqual(formset.get_queryset(), [a1, a2, a3])
    
  458. 
    
  459.     def test_min_num(self):
    
  460.         # Test the behavior of min_num with model formsets. It should be
    
  461.         # added to extra.
    
  462.         qs = Author.objects.none()
    
  463. 
    
  464.         AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=0)
    
  465.         formset = AuthorFormSet(queryset=qs)
    
  466.         self.assertEqual(len(formset.forms), 0)
    
  467. 
    
  468.         AuthorFormSet = modelformset_factory(
    
  469.             Author, fields="__all__", min_num=1, extra=0
    
  470.         )
    
  471.         formset = AuthorFormSet(queryset=qs)
    
  472.         self.assertEqual(len(formset.forms), 1)
    
  473. 
    
  474.         AuthorFormSet = modelformset_factory(
    
  475.             Author, fields="__all__", min_num=1, extra=1
    
  476.         )
    
  477.         formset = AuthorFormSet(queryset=qs)
    
  478.         self.assertEqual(len(formset.forms), 2)
    
  479. 
    
  480.     def test_min_num_with_existing(self):
    
  481.         # Test the behavior of min_num with existing objects.
    
  482.         Author.objects.create(name="Charles Baudelaire")
    
  483.         qs = Author.objects.all()
    
  484. 
    
  485.         AuthorFormSet = modelformset_factory(
    
  486.             Author, fields="__all__", extra=0, min_num=1
    
  487.         )
    
  488.         formset = AuthorFormSet(queryset=qs)
    
  489.         self.assertEqual(len(formset.forms), 1)
    
  490. 
    
  491.     def test_custom_save_method(self):
    
  492.         class PoetForm(forms.ModelForm):
    
  493.             def save(self, commit=True):
    
  494.                 # change the name to "Vladimir Mayakovsky" just to be a jerk.
    
  495.                 author = super().save(commit=False)
    
  496.                 author.name = "Vladimir Mayakovsky"
    
  497.                 if commit:
    
  498.                     author.save()
    
  499.                 return author
    
  500. 
    
  501.         PoetFormSet = modelformset_factory(Poet, fields="__all__", form=PoetForm)
    
  502. 
    
  503.         data = {
    
  504.             "form-TOTAL_FORMS": "3",  # the number of forms rendered
    
  505.             "form-INITIAL_FORMS": "0",  # the number of forms with initial data
    
  506.             "form-MAX_NUM_FORMS": "",  # the max number of forms
    
  507.             "form-0-name": "Walt Whitman",
    
  508.             "form-1-name": "Charles Baudelaire",
    
  509.             "form-2-name": "",
    
  510.         }
    
  511. 
    
  512.         qs = Poet.objects.all()
    
  513.         formset = PoetFormSet(data=data, queryset=qs)
    
  514.         self.assertTrue(formset.is_valid())
    
  515. 
    
  516.         poets = formset.save()
    
  517.         self.assertEqual(len(poets), 2)
    
  518.         poet1, poet2 = poets
    
  519.         self.assertEqual(poet1.name, "Vladimir Mayakovsky")
    
  520.         self.assertEqual(poet2.name, "Vladimir Mayakovsky")
    
  521. 
    
  522.     def test_custom_form(self):
    
  523.         """
    
  524.         model_formset_factory() respects fields and exclude parameters of a
    
  525.         custom form.
    
  526.         """
    
  527. 
    
  528.         class PostForm1(forms.ModelForm):
    
  529.             class Meta:
    
  530.                 model = Post
    
  531.                 fields = ("title", "posted")
    
  532. 
    
  533.         class PostForm2(forms.ModelForm):
    
  534.             class Meta:
    
  535.                 model = Post
    
  536.                 exclude = ("subtitle",)
    
  537. 
    
  538.         PostFormSet = modelformset_factory(Post, form=PostForm1)
    
  539.         formset = PostFormSet()
    
  540.         self.assertNotIn("subtitle", formset.forms[0].fields)
    
  541. 
    
  542.         PostFormSet = modelformset_factory(Post, form=PostForm2)
    
  543.         formset = PostFormSet()
    
  544.         self.assertNotIn("subtitle", formset.forms[0].fields)
    
  545. 
    
  546.     def test_custom_queryset_init(self):
    
  547.         """
    
  548.         A queryset can be overridden in the formset's __init__() method.
    
  549.         """
    
  550.         Author.objects.create(name="Charles Baudelaire")
    
  551.         Author.objects.create(name="Paul Verlaine")
    
  552. 
    
  553.         class BaseAuthorFormSet(BaseModelFormSet):
    
  554.             def __init__(self, *args, **kwargs):
    
  555.                 super().__init__(*args, **kwargs)
    
  556.                 self.queryset = Author.objects.filter(name__startswith="Charles")
    
  557. 
    
  558.         AuthorFormSet = modelformset_factory(
    
  559.             Author, fields="__all__", formset=BaseAuthorFormSet
    
  560.         )
    
  561.         formset = AuthorFormSet()
    
  562.         self.assertEqual(len(formset.get_queryset()), 1)
    
  563. 
    
  564.     def test_model_inheritance(self):
    
  565.         BetterAuthorFormSet = modelformset_factory(BetterAuthor, fields="__all__")
    
  566.         formset = BetterAuthorFormSet()
    
  567.         self.assertEqual(len(formset.forms), 1)
    
  568.         self.assertHTMLEqual(
    
  569.             formset.forms[0].as_p(),
    
  570.             '<p><label for="id_form-0-name">Name:</label>'
    
  571.             '<input id="id_form-0-name" type="text" name="form-0-name" maxlength="100">'
    
  572.             '</p><p><label for="id_form-0-write_speed">Write speed:</label>'
    
  573.             '<input type="number" name="form-0-write_speed" id="id_form-0-write_speed">'
    
  574.             '<input type="hidden" name="form-0-author_ptr" id="id_form-0-author_ptr">'
    
  575.             "</p>",
    
  576.         )
    
  577. 
    
  578.         data = {
    
  579.             "form-TOTAL_FORMS": "1",  # the number of forms rendered
    
  580.             "form-INITIAL_FORMS": "0",  # the number of forms with initial data
    
  581.             "form-MAX_NUM_FORMS": "",  # the max number of forms
    
  582.             "form-0-author_ptr": "",
    
  583.             "form-0-name": "Ernest Hemingway",
    
  584.             "form-0-write_speed": "10",
    
  585.         }
    
  586. 
    
  587.         formset = BetterAuthorFormSet(data)
    
  588.         self.assertTrue(formset.is_valid())
    
  589.         saved = formset.save()
    
  590.         self.assertEqual(len(saved), 1)
    
  591.         (author1,) = saved
    
  592.         self.assertEqual(author1, BetterAuthor.objects.get(name="Ernest Hemingway"))
    
  593.         hemingway_id = BetterAuthor.objects.get(name="Ernest Hemingway").pk
    
  594. 
    
  595.         formset = BetterAuthorFormSet()
    
  596.         self.assertEqual(len(formset.forms), 2)
    
  597.         self.assertHTMLEqual(
    
  598.             formset.forms[0].as_p(),
    
  599.             '<p><label for="id_form-0-name">Name:</label>'
    
  600.             '<input id="id_form-0-name" type="text" name="form-0-name" '
    
  601.             'value="Ernest Hemingway" maxlength="100"></p>'
    
  602.             '<p><label for="id_form-0-write_speed">Write speed:</label>'
    
  603.             '<input type="number" name="form-0-write_speed" value="10" '
    
  604.             'id="id_form-0-write_speed">'
    
  605.             '<input type="hidden" name="form-0-author_ptr" value="%d" '
    
  606.             'id="id_form-0-author_ptr"></p>' % hemingway_id,
    
  607.         )
    
  608.         self.assertHTMLEqual(
    
  609.             formset.forms[1].as_p(),
    
  610.             '<p><label for="id_form-1-name">Name:</label>'
    
  611.             '<input id="id_form-1-name" type="text" name="form-1-name" maxlength="100">'
    
  612.             '</p><p><label for="id_form-1-write_speed">Write speed:</label>'
    
  613.             '<input type="number" name="form-1-write_speed" id="id_form-1-write_speed">'
    
  614.             '<input type="hidden" name="form-1-author_ptr" id="id_form-1-author_ptr">'
    
  615.             "</p>",
    
  616.         )
    
  617. 
    
  618.         data = {
    
  619.             "form-TOTAL_FORMS": "2",  # the number of forms rendered
    
  620.             "form-INITIAL_FORMS": "1",  # the number of forms with initial data
    
  621.             "form-MAX_NUM_FORMS": "",  # the max number of forms
    
  622.             "form-0-author_ptr": hemingway_id,
    
  623.             "form-0-name": "Ernest Hemingway",
    
  624.             "form-0-write_speed": "10",
    
  625.             "form-1-author_ptr": "",
    
  626.             "form-1-name": "",
    
  627.             "form-1-write_speed": "",
    
  628.         }
    
  629. 
    
  630.         formset = BetterAuthorFormSet(data)
    
  631.         self.assertTrue(formset.is_valid())
    
  632.         self.assertEqual(formset.save(), [])
    
  633. 
    
  634.     def test_inline_formsets(self):
    
  635.         # We can also create a formset that is tied to a parent model. This is
    
  636.         # how the admin system's edit inline functionality works.
    
  637. 
    
  638.         AuthorBooksFormSet = inlineformset_factory(
    
  639.             Author, Book, can_delete=False, extra=3, fields="__all__"
    
  640.         )
    
  641.         author = Author.objects.create(name="Charles Baudelaire")
    
  642. 
    
  643.         formset = AuthorBooksFormSet(instance=author)
    
  644.         self.assertEqual(len(formset.forms), 3)
    
  645.         self.assertHTMLEqual(
    
  646.             formset.forms[0].as_p(),
    
  647.             '<p><label for="id_book_set-0-title">Title:</label>'
    
  648.             '<input id="id_book_set-0-title" type="text" name="book_set-0-title" '
    
  649.             'maxlength="100">'
    
  650.             '<input type="hidden" name="book_set-0-author" value="%d" '
    
  651.             'id="id_book_set-0-author">'
    
  652.             '<input type="hidden" name="book_set-0-id" id="id_book_set-0-id">'
    
  653.             "</p>" % author.id,
    
  654.         )
    
  655.         self.assertHTMLEqual(
    
  656.             formset.forms[1].as_p(),
    
  657.             '<p><label for="id_book_set-1-title">Title:</label>'
    
  658.             '<input id="id_book_set-1-title" type="text" name="book_set-1-title" '
    
  659.             'maxlength="100">'
    
  660.             '<input type="hidden" name="book_set-1-author" value="%d" '
    
  661.             'id="id_book_set-1-author">'
    
  662.             '<input type="hidden" name="book_set-1-id" id="id_book_set-1-id"></p>'
    
  663.             % author.id,
    
  664.         )
    
  665.         self.assertHTMLEqual(
    
  666.             formset.forms[2].as_p(),
    
  667.             '<p><label for="id_book_set-2-title">Title:</label>'
    
  668.             '<input id="id_book_set-2-title" type="text" name="book_set-2-title" '
    
  669.             'maxlength="100">'
    
  670.             '<input type="hidden" name="book_set-2-author" value="%d" '
    
  671.             'id="id_book_set-2-author">'
    
  672.             '<input type="hidden" name="book_set-2-id" id="id_book_set-2-id"></p>'
    
  673.             % author.id,
    
  674.         )
    
  675. 
    
  676.         data = {
    
  677.             "book_set-TOTAL_FORMS": "3",  # the number of forms rendered
    
  678.             "book_set-INITIAL_FORMS": "0",  # the number of forms with initial data
    
  679.             "book_set-MAX_NUM_FORMS": "",  # the max number of forms
    
  680.             "book_set-0-title": "Les Fleurs du Mal",
    
  681.             "book_set-1-title": "",
    
  682.             "book_set-2-title": "",
    
  683.         }
    
  684. 
    
  685.         formset = AuthorBooksFormSet(data, instance=author)
    
  686.         self.assertTrue(formset.is_valid())
    
  687. 
    
  688.         saved = formset.save()
    
  689.         self.assertEqual(len(saved), 1)
    
  690.         (book1,) = saved
    
  691.         self.assertEqual(book1, Book.objects.get(title="Les Fleurs du Mal"))
    
  692.         self.assertSequenceEqual(author.book_set.all(), [book1])
    
  693. 
    
  694.         # Now that we've added a book to Charles Baudelaire, let's try adding
    
  695.         # another one. This time though, an edit form will be available for
    
  696.         # every existing book.
    
  697. 
    
  698.         AuthorBooksFormSet = inlineformset_factory(
    
  699.             Author, Book, can_delete=False, extra=2, fields="__all__"
    
  700.         )
    
  701.         author = Author.objects.get(name="Charles Baudelaire")
    
  702. 
    
  703.         formset = AuthorBooksFormSet(instance=author)
    
  704.         self.assertEqual(len(formset.forms), 3)
    
  705.         self.assertHTMLEqual(
    
  706.             formset.forms[0].as_p(),
    
  707.             '<p><label for="id_book_set-0-title">Title:</label>'
    
  708.             '<input id="id_book_set-0-title" type="text" name="book_set-0-title" '
    
  709.             'value="Les Fleurs du Mal" maxlength="100">'
    
  710.             '<input type="hidden" name="book_set-0-author" value="%d" '
    
  711.             'id="id_book_set-0-author">'
    
  712.             '<input type="hidden" name="book_set-0-id" value="%d" '
    
  713.             'id="id_book_set-0-id"></p>'
    
  714.             % (
    
  715.                 author.id,
    
  716.                 book1.id,
    
  717.             ),
    
  718.         )
    
  719.         self.assertHTMLEqual(
    
  720.             formset.forms[1].as_p(),
    
  721.             '<p><label for="id_book_set-1-title">Title:</label>'
    
  722.             '<input id="id_book_set-1-title" type="text" name="book_set-1-title" '
    
  723.             'maxlength="100">'
    
  724.             '<input type="hidden" name="book_set-1-author" value="%d" '
    
  725.             'id="id_book_set-1-author">'
    
  726.             '<input type="hidden" name="book_set-1-id" id="id_book_set-1-id"></p>'
    
  727.             % author.id,
    
  728.         )
    
  729.         self.assertHTMLEqual(
    
  730.             formset.forms[2].as_p(),
    
  731.             '<p><label for="id_book_set-2-title">Title:</label>'
    
  732.             '<input id="id_book_set-2-title" type="text" name="book_set-2-title" '
    
  733.             'maxlength="100">'
    
  734.             '<input type="hidden" name="book_set-2-author" value="%d" '
    
  735.             'id="id_book_set-2-author">'
    
  736.             '<input type="hidden" name="book_set-2-id" id="id_book_set-2-id"></p>'
    
  737.             % author.id,
    
  738.         )
    
  739. 
    
  740.         data = {
    
  741.             "book_set-TOTAL_FORMS": "3",  # the number of forms rendered
    
  742.             "book_set-INITIAL_FORMS": "1",  # the number of forms with initial data
    
  743.             "book_set-MAX_NUM_FORMS": "",  # the max number of forms
    
  744.             "book_set-0-id": str(book1.id),
    
  745.             "book_set-0-title": "Les Fleurs du Mal",
    
  746.             "book_set-1-title": "Les Paradis Artificiels",
    
  747.             "book_set-2-title": "",
    
  748.         }
    
  749. 
    
  750.         formset = AuthorBooksFormSet(data, instance=author)
    
  751.         self.assertTrue(formset.is_valid())
    
  752. 
    
  753.         saved = formset.save()
    
  754.         self.assertEqual(len(saved), 1)
    
  755.         (book2,) = saved
    
  756.         self.assertEqual(book2, Book.objects.get(title="Les Paradis Artificiels"))
    
  757. 
    
  758.         # As you can see, 'Les Paradis Artificiels' is now a book belonging to
    
  759.         # Charles Baudelaire.
    
  760.         self.assertSequenceEqual(author.book_set.order_by("title"), [book1, book2])
    
  761. 
    
  762.     def test_inline_formsets_save_as_new(self):
    
  763.         # The save_as_new parameter lets you re-associate the data to a new
    
  764.         # instance.  This is used in the admin for save_as functionality.
    
  765.         AuthorBooksFormSet = inlineformset_factory(
    
  766.             Author, Book, can_delete=False, extra=2, fields="__all__"
    
  767.         )
    
  768.         Author.objects.create(name="Charles Baudelaire")
    
  769. 
    
  770.         # An immutable QueryDict simulates request.POST.
    
  771.         data = QueryDict(mutable=True)
    
  772.         data.update(
    
  773.             {
    
  774.                 "book_set-TOTAL_FORMS": "3",  # the number of forms rendered
    
  775.                 "book_set-INITIAL_FORMS": "2",  # the number of forms with initial data
    
  776.                 "book_set-MAX_NUM_FORMS": "",  # the max number of forms
    
  777.                 "book_set-0-id": "1",
    
  778.                 "book_set-0-title": "Les Fleurs du Mal",
    
  779.                 "book_set-1-id": "2",
    
  780.                 "book_set-1-title": "Les Paradis Artificiels",
    
  781.                 "book_set-2-title": "",
    
  782.             }
    
  783.         )
    
  784.         data._mutable = False
    
  785. 
    
  786.         formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
    
  787.         self.assertTrue(formset.is_valid())
    
  788.         self.assertIs(data._mutable, False)
    
  789. 
    
  790.         new_author = Author.objects.create(name="Charles Baudelaire")
    
  791.         formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True)
    
  792.         saved = formset.save()
    
  793.         self.assertEqual(len(saved), 2)
    
  794.         book1, book2 = saved
    
  795.         self.assertEqual(book1.title, "Les Fleurs du Mal")
    
  796.         self.assertEqual(book2.title, "Les Paradis Artificiels")
    
  797. 
    
  798.         # Test using a custom prefix on an inline formset.
    
  799. 
    
  800.         formset = AuthorBooksFormSet(prefix="test")
    
  801.         self.assertEqual(len(formset.forms), 2)
    
  802.         self.assertHTMLEqual(
    
  803.             formset.forms[0].as_p(),
    
  804.             '<p><label for="id_test-0-title">Title:</label>'
    
  805.             '<input id="id_test-0-title" type="text" name="test-0-title" '
    
  806.             'maxlength="100">'
    
  807.             '<input type="hidden" name="test-0-author" id="id_test-0-author">'
    
  808.             '<input type="hidden" name="test-0-id" id="id_test-0-id"></p>',
    
  809.         )
    
  810. 
    
  811.         self.assertHTMLEqual(
    
  812.             formset.forms[1].as_p(),
    
  813.             '<p><label for="id_test-1-title">Title:</label>'
    
  814.             '<input id="id_test-1-title" type="text" name="test-1-title" '
    
  815.             'maxlength="100">'
    
  816.             '<input type="hidden" name="test-1-author" id="id_test-1-author">'
    
  817.             '<input type="hidden" name="test-1-id" id="id_test-1-id"></p>',
    
  818.         )
    
  819. 
    
  820.     def test_inline_formsets_with_custom_pk(self):
    
  821.         # Test inline formsets where the inline-edited object has a custom
    
  822.         # primary key that is not the fk to the parent object.
    
  823.         self.maxDiff = 1024
    
  824. 
    
  825.         AuthorBooksFormSet2 = inlineformset_factory(
    
  826.             Author, BookWithCustomPK, can_delete=False, extra=1, fields="__all__"
    
  827.         )
    
  828.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  829. 
    
  830.         formset = AuthorBooksFormSet2(instance=author)
    
  831.         self.assertEqual(len(formset.forms), 1)
    
  832.         self.assertHTMLEqual(
    
  833.             formset.forms[0].as_p(),
    
  834.             '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label>'
    
  835.             '<input id="id_bookwithcustompk_set-0-my_pk" type="number" '
    
  836.             'name="bookwithcustompk_set-0-my_pk" step="1"></p>'
    
  837.             '<p><label for="id_bookwithcustompk_set-0-title">Title:</label>'
    
  838.             '<input id="id_bookwithcustompk_set-0-title" type="text" '
    
  839.             'name="bookwithcustompk_set-0-title" maxlength="100">'
    
  840.             '<input type="hidden" name="bookwithcustompk_set-0-author" '
    
  841.             'value="1" id="id_bookwithcustompk_set-0-author"></p>',
    
  842.         )
    
  843. 
    
  844.         data = {
    
  845.             # The number of forms rendered.
    
  846.             "bookwithcustompk_set-TOTAL_FORMS": "1",
    
  847.             # The number of forms with initial data.
    
  848.             "bookwithcustompk_set-INITIAL_FORMS": "0",
    
  849.             # The max number of forms.
    
  850.             "bookwithcustompk_set-MAX_NUM_FORMS": "",
    
  851.             "bookwithcustompk_set-0-my_pk": "77777",
    
  852.             "bookwithcustompk_set-0-title": "Les Fleurs du Mal",
    
  853.         }
    
  854. 
    
  855.         formset = AuthorBooksFormSet2(data, instance=author)
    
  856.         self.assertTrue(formset.is_valid())
    
  857. 
    
  858.         saved = formset.save()
    
  859.         self.assertEqual(len(saved), 1)
    
  860.         (book1,) = saved
    
  861.         self.assertEqual(book1.pk, 77777)
    
  862. 
    
  863.         book1 = author.bookwithcustompk_set.get()
    
  864.         self.assertEqual(book1.title, "Les Fleurs du Mal")
    
  865. 
    
  866.     def test_inline_formsets_with_multi_table_inheritance(self):
    
  867.         # Test inline formsets where the inline-edited object uses multi-table
    
  868.         # inheritance, thus has a non AutoField yet auto-created primary key.
    
  869. 
    
  870.         AuthorBooksFormSet3 = inlineformset_factory(
    
  871.             Author, AlternateBook, can_delete=False, extra=1, fields="__all__"
    
  872.         )
    
  873.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  874. 
    
  875.         formset = AuthorBooksFormSet3(instance=author)
    
  876.         self.assertEqual(len(formset.forms), 1)
    
  877.         self.assertHTMLEqual(
    
  878.             formset.forms[0].as_p(),
    
  879.             '<p><label for="id_alternatebook_set-0-title">Title:</label>'
    
  880.             '<input id="id_alternatebook_set-0-title" type="text" '
    
  881.             'name="alternatebook_set-0-title" maxlength="100"></p>'
    
  882.             '<p><label for="id_alternatebook_set-0-notes">Notes:</label>'
    
  883.             '<input id="id_alternatebook_set-0-notes" type="text" '
    
  884.             'name="alternatebook_set-0-notes" maxlength="100">'
    
  885.             '<input type="hidden" name="alternatebook_set-0-author" value="1" '
    
  886.             'id="id_alternatebook_set-0-author">'
    
  887.             '<input type="hidden" name="alternatebook_set-0-book_ptr" '
    
  888.             'id="id_alternatebook_set-0-book_ptr"></p>',
    
  889.         )
    
  890. 
    
  891.         data = {
    
  892.             # The number of forms rendered.
    
  893.             "alternatebook_set-TOTAL_FORMS": "1",
    
  894.             # The number of forms with initial data.
    
  895.             "alternatebook_set-INITIAL_FORMS": "0",
    
  896.             # The max number of forms.
    
  897.             "alternatebook_set-MAX_NUM_FORMS": "",
    
  898.             "alternatebook_set-0-title": "Flowers of Evil",
    
  899.             "alternatebook_set-0-notes": "English translation of Les Fleurs du Mal",
    
  900.         }
    
  901. 
    
  902.         formset = AuthorBooksFormSet3(data, instance=author)
    
  903.         self.assertTrue(formset.is_valid())
    
  904. 
    
  905.         saved = formset.save()
    
  906.         self.assertEqual(len(saved), 1)
    
  907.         (book1,) = saved
    
  908.         self.assertEqual(book1.title, "Flowers of Evil")
    
  909.         self.assertEqual(book1.notes, "English translation of Les Fleurs du Mal")
    
  910. 
    
  911.     @skipUnlessDBFeature("supports_partially_nullable_unique_constraints")
    
  912.     def test_inline_formsets_with_nullable_unique_together(self):
    
  913.         # Test inline formsets where the inline-edited object has a
    
  914.         # unique_together constraint with a nullable member
    
  915. 
    
  916.         AuthorBooksFormSet4 = inlineformset_factory(
    
  917.             Author,
    
  918.             BookWithOptionalAltEditor,
    
  919.             can_delete=False,
    
  920.             extra=2,
    
  921.             fields="__all__",
    
  922.         )
    
  923.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  924. 
    
  925.         data = {
    
  926.             # The number of forms rendered.
    
  927.             "bookwithoptionalalteditor_set-TOTAL_FORMS": "2",
    
  928.             # The number of forms with initial data.
    
  929.             "bookwithoptionalalteditor_set-INITIAL_FORMS": "0",
    
  930.             # The max number of forms.
    
  931.             "bookwithoptionalalteditor_set-MAX_NUM_FORMS": "",
    
  932.             "bookwithoptionalalteditor_set-0-author": "1",
    
  933.             "bookwithoptionalalteditor_set-0-title": "Les Fleurs du Mal",
    
  934.             "bookwithoptionalalteditor_set-1-author": "1",
    
  935.             "bookwithoptionalalteditor_set-1-title": "Les Fleurs du Mal",
    
  936.         }
    
  937.         formset = AuthorBooksFormSet4(data, instance=author)
    
  938.         self.assertTrue(formset.is_valid())
    
  939. 
    
  940.         saved = formset.save()
    
  941.         self.assertEqual(len(saved), 2)
    
  942.         book1, book2 = saved
    
  943.         self.assertEqual(book1.author_id, 1)
    
  944.         self.assertEqual(book1.title, "Les Fleurs du Mal")
    
  945.         self.assertEqual(book2.author_id, 1)
    
  946.         self.assertEqual(book2.title, "Les Fleurs du Mal")
    
  947. 
    
  948.     def test_inline_formsets_with_custom_save_method(self):
    
  949.         AuthorBooksFormSet = inlineformset_factory(
    
  950.             Author, Book, can_delete=False, extra=2, fields="__all__"
    
  951.         )
    
  952.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  953.         book1 = Book.objects.create(
    
  954.             pk=1, author=author, title="Les Paradis Artificiels"
    
  955.         )
    
  956.         book2 = Book.objects.create(pk=2, author=author, title="Les Fleurs du Mal")
    
  957.         book3 = Book.objects.create(pk=3, author=author, title="Flowers of Evil")
    
  958. 
    
  959.         class PoemForm(forms.ModelForm):
    
  960.             def save(self, commit=True):
    
  961.                 # change the name to "Brooklyn Bridge" just to be a jerk.
    
  962.                 poem = super().save(commit=False)
    
  963.                 poem.name = "Brooklyn Bridge"
    
  964.                 if commit:
    
  965.                     poem.save()
    
  966.                 return poem
    
  967. 
    
  968.         PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm, fields="__all__")
    
  969. 
    
  970.         data = {
    
  971.             "poem_set-TOTAL_FORMS": "3",  # the number of forms rendered
    
  972.             "poem_set-INITIAL_FORMS": "0",  # the number of forms with initial data
    
  973.             "poem_set-MAX_NUM_FORMS": "",  # the max number of forms
    
  974.             "poem_set-0-name": "The Cloud in Trousers",
    
  975.             "poem_set-1-name": "I",
    
  976.             "poem_set-2-name": "",
    
  977.         }
    
  978. 
    
  979.         poet = Poet.objects.create(name="Vladimir Mayakovsky")
    
  980.         formset = PoemFormSet(data=data, instance=poet)
    
  981.         self.assertTrue(formset.is_valid())
    
  982. 
    
  983.         saved = formset.save()
    
  984.         self.assertEqual(len(saved), 2)
    
  985.         poem1, poem2 = saved
    
  986.         self.assertEqual(poem1.name, "Brooklyn Bridge")
    
  987.         self.assertEqual(poem2.name, "Brooklyn Bridge")
    
  988. 
    
  989.         # We can provide a custom queryset to our InlineFormSet:
    
  990. 
    
  991.         custom_qs = Book.objects.order_by("-title")
    
  992.         formset = AuthorBooksFormSet(instance=author, queryset=custom_qs)
    
  993.         self.assertEqual(len(formset.forms), 5)
    
  994.         self.assertHTMLEqual(
    
  995.             formset.forms[0].as_p(),
    
  996.             '<p><label for="id_book_set-0-title">Title:</label>'
    
  997.             '<input id="id_book_set-0-title" type="text" name="book_set-0-title" '
    
  998.             'value="Les Paradis Artificiels" maxlength="100">'
    
  999.             '<input type="hidden" name="book_set-0-author" value="1" '
    
  1000.             'id="id_book_set-0-author">'
    
  1001.             '<input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id">'
    
  1002.             "</p>",
    
  1003.         )
    
  1004.         self.assertHTMLEqual(
    
  1005.             formset.forms[1].as_p(),
    
  1006.             '<p><label for="id_book_set-1-title">Title:</label>'
    
  1007.             '<input id="id_book_set-1-title" type="text" name="book_set-1-title" '
    
  1008.             'value="Les Fleurs du Mal" maxlength="100">'
    
  1009.             '<input type="hidden" name="book_set-1-author" value="1" '
    
  1010.             'id="id_book_set-1-author">'
    
  1011.             '<input type="hidden" name="book_set-1-id" value="2" id="id_book_set-1-id">'
    
  1012.             "</p>",
    
  1013.         )
    
  1014.         self.assertHTMLEqual(
    
  1015.             formset.forms[2].as_p(),
    
  1016.             '<p><label for="id_book_set-2-title">Title:</label>'
    
  1017.             '<input id="id_book_set-2-title" type="text" name="book_set-2-title" '
    
  1018.             'value="Flowers of Evil" maxlength="100">'
    
  1019.             '<input type="hidden" name="book_set-2-author" value="1" '
    
  1020.             'id="id_book_set-2-author">'
    
  1021.             '<input type="hidden" name="book_set-2-id" value="3" '
    
  1022.             'id="id_book_set-2-id"></p>',
    
  1023.         )
    
  1024.         self.assertHTMLEqual(
    
  1025.             formset.forms[3].as_p(),
    
  1026.             '<p><label for="id_book_set-3-title">Title:</label>'
    
  1027.             '<input id="id_book_set-3-title" type="text" name="book_set-3-title" '
    
  1028.             'maxlength="100">'
    
  1029.             '<input type="hidden" name="book_set-3-author" value="1" '
    
  1030.             'id="id_book_set-3-author">'
    
  1031.             '<input type="hidden" name="book_set-3-id" id="id_book_set-3-id"></p>',
    
  1032.         )
    
  1033.         self.assertHTMLEqual(
    
  1034.             formset.forms[4].as_p(),
    
  1035.             '<p><label for="id_book_set-4-title">Title:</label>'
    
  1036.             '<input id="id_book_set-4-title" type="text" name="book_set-4-title" '
    
  1037.             'maxlength="100">'
    
  1038.             '<input type="hidden" name="book_set-4-author" value="1" '
    
  1039.             'id="id_book_set-4-author">'
    
  1040.             '<input type="hidden" name="book_set-4-id" id="id_book_set-4-id"></p>',
    
  1041.         )
    
  1042. 
    
  1043.         data = {
    
  1044.             "book_set-TOTAL_FORMS": "5",  # the number of forms rendered
    
  1045.             "book_set-INITIAL_FORMS": "3",  # the number of forms with initial data
    
  1046.             "book_set-MAX_NUM_FORMS": "",  # the max number of forms
    
  1047.             "book_set-0-id": str(book1.id),
    
  1048.             "book_set-0-title": "Les Paradis Artificiels",
    
  1049.             "book_set-1-id": str(book2.id),
    
  1050.             "book_set-1-title": "Les Fleurs du Mal",
    
  1051.             "book_set-2-id": str(book3.id),
    
  1052.             "book_set-2-title": "Flowers of Evil",
    
  1053.             "book_set-3-title": "Revue des deux mondes",
    
  1054.             "book_set-4-title": "",
    
  1055.         }
    
  1056.         formset = AuthorBooksFormSet(data, instance=author, queryset=custom_qs)
    
  1057.         self.assertTrue(formset.is_valid())
    
  1058. 
    
  1059.         custom_qs = Book.objects.filter(title__startswith="F")
    
  1060.         formset = AuthorBooksFormSet(instance=author, queryset=custom_qs)
    
  1061.         self.assertHTMLEqual(
    
  1062.             formset.forms[0].as_p(),
    
  1063.             '<p><label for="id_book_set-0-title">Title:</label>'
    
  1064.             '<input id="id_book_set-0-title" type="text" name="book_set-0-title" '
    
  1065.             'value="Flowers of Evil" maxlength="100">'
    
  1066.             '<input type="hidden" name="book_set-0-author" value="1" '
    
  1067.             'id="id_book_set-0-author">'
    
  1068.             '<input type="hidden" name="book_set-0-id" value="3" '
    
  1069.             'id="id_book_set-0-id"></p>',
    
  1070.         )
    
  1071.         self.assertHTMLEqual(
    
  1072.             formset.forms[1].as_p(),
    
  1073.             '<p><label for="id_book_set-1-title">Title:</label>'
    
  1074.             '<input id="id_book_set-1-title" type="text" name="book_set-1-title" '
    
  1075.             'maxlength="100">'
    
  1076.             '<input type="hidden" name="book_set-1-author" value="1" '
    
  1077.             'id="id_book_set-1-author">'
    
  1078.             '<input type="hidden" name="book_set-1-id" id="id_book_set-1-id"></p>',
    
  1079.         )
    
  1080.         self.assertHTMLEqual(
    
  1081.             formset.forms[2].as_p(),
    
  1082.             '<p><label for="id_book_set-2-title">Title:</label>'
    
  1083.             '<input id="id_book_set-2-title" type="text" name="book_set-2-title" '
    
  1084.             'maxlength="100">'
    
  1085.             '<input type="hidden" name="book_set-2-author" value="1" '
    
  1086.             'id="id_book_set-2-author">'
    
  1087.             '<input type="hidden" name="book_set-2-id" id="id_book_set-2-id"></p>',
    
  1088.         )
    
  1089. 
    
  1090.         data = {
    
  1091.             "book_set-TOTAL_FORMS": "3",  # the number of forms rendered
    
  1092.             "book_set-INITIAL_FORMS": "1",  # the number of forms with initial data
    
  1093.             "book_set-MAX_NUM_FORMS": "",  # the max number of forms
    
  1094.             "book_set-0-id": str(book3.id),
    
  1095.             "book_set-0-title": "Flowers of Evil",
    
  1096.             "book_set-1-title": "Revue des deux mondes",
    
  1097.             "book_set-2-title": "",
    
  1098.         }
    
  1099.         formset = AuthorBooksFormSet(data, instance=author, queryset=custom_qs)
    
  1100.         self.assertTrue(formset.is_valid())
    
  1101. 
    
  1102.     def test_inline_formsets_with_custom_save_method_related_instance(self):
    
  1103.         """
    
  1104.         The ModelForm.save() method should be able to access the related object
    
  1105.         if it exists in the database (#24395).
    
  1106.         """
    
  1107. 
    
  1108.         class PoemForm2(forms.ModelForm):
    
  1109.             def save(self, commit=True):
    
  1110.                 poem = super().save(commit=False)
    
  1111.                 poem.name = "%s by %s" % (poem.name, poem.poet.name)
    
  1112.                 if commit:
    
  1113.                     poem.save()
    
  1114.                 return poem
    
  1115. 
    
  1116.         PoemFormSet = inlineformset_factory(
    
  1117.             Poet, Poem, form=PoemForm2, fields="__all__"
    
  1118.         )
    
  1119.         data = {
    
  1120.             "poem_set-TOTAL_FORMS": "1",
    
  1121.             "poem_set-INITIAL_FORMS": "0",
    
  1122.             "poem_set-MAX_NUM_FORMS": "",
    
  1123.             "poem_set-0-name": "Le Lac",
    
  1124.         }
    
  1125.         poet = Poet()
    
  1126.         formset = PoemFormSet(data=data, instance=poet)
    
  1127.         self.assertTrue(formset.is_valid())
    
  1128. 
    
  1129.         # The Poet instance is saved after the formset instantiation. This
    
  1130.         # happens in admin's changeform_view() when adding a new object and
    
  1131.         # some inlines in the same request.
    
  1132.         poet.name = "Lamartine"
    
  1133.         poet.save()
    
  1134.         poem = formset.save()[0]
    
  1135.         self.assertEqual(poem.name, "Le Lac by Lamartine")
    
  1136. 
    
  1137.     def test_inline_formsets_with_wrong_fk_name(self):
    
  1138.         """Regression for #23451"""
    
  1139.         message = "fk_name 'title' is not a ForeignKey to 'model_formsets.Author'."
    
  1140.         with self.assertRaisesMessage(ValueError, message):
    
  1141.             inlineformset_factory(Author, Book, fields="__all__", fk_name="title")
    
  1142. 
    
  1143.     def test_custom_pk(self):
    
  1144.         # We need to ensure that it is displayed
    
  1145. 
    
  1146.         CustomPrimaryKeyFormSet = modelformset_factory(
    
  1147.             CustomPrimaryKey, fields="__all__"
    
  1148.         )
    
  1149.         formset = CustomPrimaryKeyFormSet()
    
  1150.         self.assertEqual(len(formset.forms), 1)
    
  1151.         self.assertHTMLEqual(
    
  1152.             formset.forms[0].as_p(),
    
  1153.             '<p><label for="id_form-0-my_pk">My pk:</label>'
    
  1154.             '<input id="id_form-0-my_pk" type="text" name="form-0-my_pk" '
    
  1155.             'maxlength="10"></p>'
    
  1156.             '<p><label for="id_form-0-some_field">Some field:</label>'
    
  1157.             '<input id="id_form-0-some_field" type="text" name="form-0-some_field" '
    
  1158.             'maxlength="100"></p>',
    
  1159.         )
    
  1160. 
    
  1161.         # Custom primary keys with ForeignKey, OneToOneField and AutoField ############
    
  1162. 
    
  1163.         place = Place.objects.create(pk=1, name="Giordanos", city="Chicago")
    
  1164. 
    
  1165.         FormSet = inlineformset_factory(
    
  1166.             Place, Owner, extra=2, can_delete=False, fields="__all__"
    
  1167.         )
    
  1168.         formset = FormSet(instance=place)
    
  1169.         self.assertEqual(len(formset.forms), 2)
    
  1170.         self.assertHTMLEqual(
    
  1171.             formset.forms[0].as_p(),
    
  1172.             '<p><label for="id_owner_set-0-name">Name:</label>'
    
  1173.             '<input id="id_owner_set-0-name" type="text" name="owner_set-0-name" '
    
  1174.             'maxlength="100">'
    
  1175.             '<input type="hidden" name="owner_set-0-place" value="1" '
    
  1176.             'id="id_owner_set-0-place">'
    
  1177.             '<input type="hidden" name="owner_set-0-auto_id" '
    
  1178.             'id="id_owner_set-0-auto_id"></p>',
    
  1179.         )
    
  1180.         self.assertHTMLEqual(
    
  1181.             formset.forms[1].as_p(),
    
  1182.             '<p><label for="id_owner_set-1-name">Name:</label>'
    
  1183.             '<input id="id_owner_set-1-name" type="text" name="owner_set-1-name" '
    
  1184.             'maxlength="100">'
    
  1185.             '<input type="hidden" name="owner_set-1-place" value="1" '
    
  1186.             'id="id_owner_set-1-place">'
    
  1187.             '<input type="hidden" name="owner_set-1-auto_id" '
    
  1188.             'id="id_owner_set-1-auto_id"></p>',
    
  1189.         )
    
  1190. 
    
  1191.         data = {
    
  1192.             "owner_set-TOTAL_FORMS": "2",
    
  1193.             "owner_set-INITIAL_FORMS": "0",
    
  1194.             "owner_set-MAX_NUM_FORMS": "",
    
  1195.             "owner_set-0-auto_id": "",
    
  1196.             "owner_set-0-name": "Joe Perry",
    
  1197.             "owner_set-1-auto_id": "",
    
  1198.             "owner_set-1-name": "",
    
  1199.         }
    
  1200.         formset = FormSet(data, instance=place)
    
  1201.         self.assertTrue(formset.is_valid())
    
  1202.         saved = formset.save()
    
  1203.         self.assertEqual(len(saved), 1)
    
  1204.         (owner1,) = saved
    
  1205.         self.assertEqual(owner1.name, "Joe Perry")
    
  1206.         self.assertEqual(owner1.place.name, "Giordanos")
    
  1207. 
    
  1208.         formset = FormSet(instance=place)
    
  1209.         self.assertEqual(len(formset.forms), 3)
    
  1210.         self.assertHTMLEqual(
    
  1211.             formset.forms[0].as_p(),
    
  1212.             '<p><label for="id_owner_set-0-name">Name:</label>'
    
  1213.             '<input id="id_owner_set-0-name" type="text" name="owner_set-0-name" '
    
  1214.             'value="Joe Perry" maxlength="100">'
    
  1215.             '<input type="hidden" name="owner_set-0-place" value="1" '
    
  1216.             'id="id_owner_set-0-place">'
    
  1217.             '<input type="hidden" name="owner_set-0-auto_id" value="%d" '
    
  1218.             'id="id_owner_set-0-auto_id"></p>' % owner1.auto_id,
    
  1219.         )
    
  1220.         self.assertHTMLEqual(
    
  1221.             formset.forms[1].as_p(),
    
  1222.             '<p><label for="id_owner_set-1-name">Name:</label>'
    
  1223.             '<input id="id_owner_set-1-name" type="text" name="owner_set-1-name" '
    
  1224.             'maxlength="100">'
    
  1225.             '<input type="hidden" name="owner_set-1-place" value="1" '
    
  1226.             'id="id_owner_set-1-place">'
    
  1227.             '<input type="hidden" name="owner_set-1-auto_id" '
    
  1228.             'id="id_owner_set-1-auto_id"></p>',
    
  1229.         )
    
  1230.         self.assertHTMLEqual(
    
  1231.             formset.forms[2].as_p(),
    
  1232.             '<p><label for="id_owner_set-2-name">Name:</label>'
    
  1233.             '<input id="id_owner_set-2-name" type="text" name="owner_set-2-name" '
    
  1234.             'maxlength="100">'
    
  1235.             '<input type="hidden" name="owner_set-2-place" value="1" '
    
  1236.             'id="id_owner_set-2-place">'
    
  1237.             '<input type="hidden" name="owner_set-2-auto_id" '
    
  1238.             'id="id_owner_set-2-auto_id"></p>',
    
  1239.         )
    
  1240. 
    
  1241.         data = {
    
  1242.             "owner_set-TOTAL_FORMS": "3",
    
  1243.             "owner_set-INITIAL_FORMS": "1",
    
  1244.             "owner_set-MAX_NUM_FORMS": "",
    
  1245.             "owner_set-0-auto_id": str(owner1.auto_id),
    
  1246.             "owner_set-0-name": "Joe Perry",
    
  1247.             "owner_set-1-auto_id": "",
    
  1248.             "owner_set-1-name": "Jack Berry",
    
  1249.             "owner_set-2-auto_id": "",
    
  1250.             "owner_set-2-name": "",
    
  1251.         }
    
  1252.         formset = FormSet(data, instance=place)
    
  1253.         self.assertTrue(formset.is_valid())
    
  1254.         saved = formset.save()
    
  1255.         self.assertEqual(len(saved), 1)
    
  1256.         (owner2,) = saved
    
  1257.         self.assertEqual(owner2.name, "Jack Berry")
    
  1258.         self.assertEqual(owner2.place.name, "Giordanos")
    
  1259. 
    
  1260.         # A custom primary key that is a ForeignKey or OneToOneField get
    
  1261.         # rendered for the user to choose.
    
  1262.         FormSet = modelformset_factory(OwnerProfile, fields="__all__")
    
  1263.         formset = FormSet()
    
  1264.         self.assertHTMLEqual(
    
  1265.             formset.forms[0].as_p(),
    
  1266.             '<p><label for="id_form-0-owner">Owner:</label>'
    
  1267.             '<select name="form-0-owner" id="id_form-0-owner">'
    
  1268.             '<option value="" selected>---------</option>'
    
  1269.             '<option value="%d">Joe Perry at Giordanos</option>'
    
  1270.             '<option value="%d">Jack Berry at Giordanos</option>'
    
  1271.             "</select></p>"
    
  1272.             '<p><label for="id_form-0-age">Age:</label>'
    
  1273.             '<input type="number" name="form-0-age" id="id_form-0-age" min="0"></p>'
    
  1274.             % (owner1.auto_id, owner2.auto_id),
    
  1275.         )
    
  1276. 
    
  1277.         owner1 = Owner.objects.get(name="Joe Perry")
    
  1278.         FormSet = inlineformset_factory(
    
  1279.             Owner, OwnerProfile, max_num=1, can_delete=False, fields="__all__"
    
  1280.         )
    
  1281.         self.assertEqual(FormSet.max_num, 1)
    
  1282. 
    
  1283.         formset = FormSet(instance=owner1)
    
  1284.         self.assertEqual(len(formset.forms), 1)
    
  1285.         self.assertHTMLEqual(
    
  1286.             formset.forms[0].as_p(),
    
  1287.             '<p><label for="id_ownerprofile-0-age">Age:</label>'
    
  1288.             '<input type="number" name="ownerprofile-0-age" '
    
  1289.             'id="id_ownerprofile-0-age" min="0">'
    
  1290.             '<input type="hidden" name="ownerprofile-0-owner" value="%d" '
    
  1291.             'id="id_ownerprofile-0-owner"></p>' % owner1.auto_id,
    
  1292.         )
    
  1293. 
    
  1294.         data = {
    
  1295.             "ownerprofile-TOTAL_FORMS": "1",
    
  1296.             "ownerprofile-INITIAL_FORMS": "0",
    
  1297.             "ownerprofile-MAX_NUM_FORMS": "1",
    
  1298.             "ownerprofile-0-owner": "",
    
  1299.             "ownerprofile-0-age": "54",
    
  1300.         }
    
  1301.         formset = FormSet(data, instance=owner1)
    
  1302.         self.assertTrue(formset.is_valid())
    
  1303.         saved = formset.save()
    
  1304.         self.assertEqual(len(saved), 1)
    
  1305.         (profile1,) = saved
    
  1306.         self.assertEqual(profile1.owner, owner1)
    
  1307.         self.assertEqual(profile1.age, 54)
    
  1308. 
    
  1309.         formset = FormSet(instance=owner1)
    
  1310.         self.assertEqual(len(formset.forms), 1)
    
  1311.         self.assertHTMLEqual(
    
  1312.             formset.forms[0].as_p(),
    
  1313.             '<p><label for="id_ownerprofile-0-age">Age:</label>'
    
  1314.             '<input type="number" name="ownerprofile-0-age" value="54" '
    
  1315.             'id="id_ownerprofile-0-age" min="0">'
    
  1316.             '<input type="hidden" name="ownerprofile-0-owner" value="%d" '
    
  1317.             'id="id_ownerprofile-0-owner"></p>' % owner1.auto_id,
    
  1318.         )
    
  1319. 
    
  1320.         data = {
    
  1321.             "ownerprofile-TOTAL_FORMS": "1",
    
  1322.             "ownerprofile-INITIAL_FORMS": "1",
    
  1323.             "ownerprofile-MAX_NUM_FORMS": "1",
    
  1324.             "ownerprofile-0-owner": str(owner1.auto_id),
    
  1325.             "ownerprofile-0-age": "55",
    
  1326.         }
    
  1327.         formset = FormSet(data, instance=owner1)
    
  1328.         self.assertTrue(formset.is_valid())
    
  1329.         saved = formset.save()
    
  1330.         self.assertEqual(len(saved), 1)
    
  1331.         (profile1,) = saved
    
  1332.         self.assertEqual(profile1.owner, owner1)
    
  1333.         self.assertEqual(profile1.age, 55)
    
  1334. 
    
  1335.     def test_unique_true_enforces_max_num_one(self):
    
  1336.         # ForeignKey with unique=True should enforce max_num=1
    
  1337. 
    
  1338.         place = Place.objects.create(pk=1, name="Giordanos", city="Chicago")
    
  1339. 
    
  1340.         FormSet = inlineformset_factory(
    
  1341.             Place, Location, can_delete=False, fields="__all__"
    
  1342.         )
    
  1343.         self.assertEqual(FormSet.max_num, 1)
    
  1344. 
    
  1345.         formset = FormSet(instance=place)
    
  1346.         self.assertEqual(len(formset.forms), 1)
    
  1347.         self.assertHTMLEqual(
    
  1348.             formset.forms[0].as_p(),
    
  1349.             '<p><label for="id_location_set-0-lat">Lat:</label>'
    
  1350.             '<input id="id_location_set-0-lat" type="text" name="location_set-0-lat" '
    
  1351.             'maxlength="100"></p>'
    
  1352.             '<p><label for="id_location_set-0-lon">Lon:</label>'
    
  1353.             '<input id="id_location_set-0-lon" type="text" name="location_set-0-lon" '
    
  1354.             'maxlength="100">'
    
  1355.             '<input type="hidden" name="location_set-0-place" value="1" '
    
  1356.             'id="id_location_set-0-place">'
    
  1357.             '<input type="hidden" name="location_set-0-id" '
    
  1358.             'id="id_location_set-0-id"></p>',
    
  1359.         )
    
  1360. 
    
  1361.     def test_foreign_keys_in_parents(self):
    
  1362.         self.assertEqual(type(_get_foreign_key(Restaurant, Owner)), models.ForeignKey)
    
  1363.         self.assertEqual(
    
  1364.             type(_get_foreign_key(MexicanRestaurant, Owner)), models.ForeignKey
    
  1365.         )
    
  1366. 
    
  1367.     def test_unique_validation(self):
    
  1368.         FormSet = modelformset_factory(Product, fields="__all__", extra=1)
    
  1369.         data = {
    
  1370.             "form-TOTAL_FORMS": "1",
    
  1371.             "form-INITIAL_FORMS": "0",
    
  1372.             "form-MAX_NUM_FORMS": "",
    
  1373.             "form-0-slug": "car-red",
    
  1374.         }
    
  1375.         formset = FormSet(data)
    
  1376.         self.assertTrue(formset.is_valid())
    
  1377.         saved = formset.save()
    
  1378.         self.assertEqual(len(saved), 1)
    
  1379.         (product1,) = saved
    
  1380.         self.assertEqual(product1.slug, "car-red")
    
  1381. 
    
  1382.         data = {
    
  1383.             "form-TOTAL_FORMS": "1",
    
  1384.             "form-INITIAL_FORMS": "0",
    
  1385.             "form-MAX_NUM_FORMS": "",
    
  1386.             "form-0-slug": "car-red",
    
  1387.         }
    
  1388.         formset = FormSet(data)
    
  1389.         self.assertFalse(formset.is_valid())
    
  1390.         self.assertEqual(
    
  1391.             formset.errors, [{"slug": ["Product with this Slug already exists."]}]
    
  1392.         )
    
  1393. 
    
  1394.     def test_modelformset_validate_max_flag(self):
    
  1395.         # If validate_max is set and max_num is less than TOTAL_FORMS in the
    
  1396.         # data, then throw an exception. MAX_NUM_FORMS in the data is
    
  1397.         # irrelevant here (it's output as a hint for the client but its
    
  1398.         # value in the returned data is not checked)
    
  1399. 
    
  1400.         data = {
    
  1401.             "form-TOTAL_FORMS": "2",
    
  1402.             "form-INITIAL_FORMS": "0",
    
  1403.             "form-MAX_NUM_FORMS": "2",  # should be ignored
    
  1404.             "form-0-price": "12.00",
    
  1405.             "form-0-quantity": "1",
    
  1406.             "form-1-price": "24.00",
    
  1407.             "form-1-quantity": "2",
    
  1408.         }
    
  1409. 
    
  1410.         FormSet = modelformset_factory(
    
  1411.             Price, fields="__all__", extra=1, max_num=1, validate_max=True
    
  1412.         )
    
  1413.         formset = FormSet(data)
    
  1414.         self.assertFalse(formset.is_valid())
    
  1415.         self.assertEqual(formset.non_form_errors(), ["Please submit at most 1 form."])
    
  1416. 
    
  1417.         # Now test the same thing without the validate_max flag to ensure
    
  1418.         # default behavior is unchanged
    
  1419.         FormSet = modelformset_factory(Price, fields="__all__", extra=1, max_num=1)
    
  1420.         formset = FormSet(data)
    
  1421.         self.assertTrue(formset.is_valid())
    
  1422. 
    
  1423.     def test_modelformset_min_num_equals_max_num_less_than(self):
    
  1424.         data = {
    
  1425.             "form-TOTAL_FORMS": "3",
    
  1426.             "form-INITIAL_FORMS": "0",
    
  1427.             "form-MAX_NUM_FORMS": "2",
    
  1428.             "form-0-slug": "car-red",
    
  1429.             "form-1-slug": "car-blue",
    
  1430.             "form-2-slug": "car-black",
    
  1431.         }
    
  1432.         FormSet = modelformset_factory(
    
  1433.             Product,
    
  1434.             fields="__all__",
    
  1435.             extra=1,
    
  1436.             max_num=2,
    
  1437.             validate_max=True,
    
  1438.             min_num=2,
    
  1439.             validate_min=True,
    
  1440.         )
    
  1441.         formset = FormSet(data)
    
  1442.         self.assertFalse(formset.is_valid())
    
  1443.         self.assertEqual(formset.non_form_errors(), ["Please submit at most 2 forms."])
    
  1444. 
    
  1445.     def test_modelformset_min_num_equals_max_num_more_than(self):
    
  1446.         data = {
    
  1447.             "form-TOTAL_FORMS": "1",
    
  1448.             "form-INITIAL_FORMS": "0",
    
  1449.             "form-MAX_NUM_FORMS": "2",
    
  1450.             "form-0-slug": "car-red",
    
  1451.         }
    
  1452.         FormSet = modelformset_factory(
    
  1453.             Product,
    
  1454.             fields="__all__",
    
  1455.             extra=1,
    
  1456.             max_num=2,
    
  1457.             validate_max=True,
    
  1458.             min_num=2,
    
  1459.             validate_min=True,
    
  1460.         )
    
  1461.         formset = FormSet(data)
    
  1462.         self.assertFalse(formset.is_valid())
    
  1463.         self.assertEqual(formset.non_form_errors(), ["Please submit at least 2 forms."])
    
  1464. 
    
  1465.     def test_unique_together_validation(self):
    
  1466.         FormSet = modelformset_factory(Price, fields="__all__", extra=1)
    
  1467.         data = {
    
  1468.             "form-TOTAL_FORMS": "1",
    
  1469.             "form-INITIAL_FORMS": "0",
    
  1470.             "form-MAX_NUM_FORMS": "",
    
  1471.             "form-0-price": "12.00",
    
  1472.             "form-0-quantity": "1",
    
  1473.         }
    
  1474.         formset = FormSet(data)
    
  1475.         self.assertTrue(formset.is_valid())
    
  1476.         saved = formset.save()
    
  1477.         self.assertEqual(len(saved), 1)
    
  1478.         (price1,) = saved
    
  1479.         self.assertEqual(price1.price, Decimal("12.00"))
    
  1480.         self.assertEqual(price1.quantity, 1)
    
  1481. 
    
  1482.         data = {
    
  1483.             "form-TOTAL_FORMS": "1",
    
  1484.             "form-INITIAL_FORMS": "0",
    
  1485.             "form-MAX_NUM_FORMS": "",
    
  1486.             "form-0-price": "12.00",
    
  1487.             "form-0-quantity": "1",
    
  1488.         }
    
  1489.         formset = FormSet(data)
    
  1490.         self.assertFalse(formset.is_valid())
    
  1491.         self.assertEqual(
    
  1492.             formset.errors,
    
  1493.             [{"__all__": ["Price with this Price and Quantity already exists."]}],
    
  1494.         )
    
  1495. 
    
  1496.     def test_unique_together_with_inlineformset_factory(self):
    
  1497.         # Also see bug #8882.
    
  1498. 
    
  1499.         repository = Repository.objects.create(name="Test Repo")
    
  1500.         FormSet = inlineformset_factory(Repository, Revision, extra=1, fields="__all__")
    
  1501.         data = {
    
  1502.             "revision_set-TOTAL_FORMS": "1",
    
  1503.             "revision_set-INITIAL_FORMS": "0",
    
  1504.             "revision_set-MAX_NUM_FORMS": "",
    
  1505.             "revision_set-0-repository": repository.pk,
    
  1506.             "revision_set-0-revision": "146239817507f148d448db38840db7c3cbf47c76",
    
  1507.             "revision_set-0-DELETE": "",
    
  1508.         }
    
  1509.         formset = FormSet(data, instance=repository)
    
  1510.         self.assertTrue(formset.is_valid())
    
  1511.         saved = formset.save()
    
  1512.         self.assertEqual(len(saved), 1)
    
  1513.         (revision1,) = saved
    
  1514.         self.assertEqual(revision1.repository, repository)
    
  1515.         self.assertEqual(revision1.revision, "146239817507f148d448db38840db7c3cbf47c76")
    
  1516. 
    
  1517.         # attempt to save the same revision against the same repo.
    
  1518.         data = {
    
  1519.             "revision_set-TOTAL_FORMS": "1",
    
  1520.             "revision_set-INITIAL_FORMS": "0",
    
  1521.             "revision_set-MAX_NUM_FORMS": "",
    
  1522.             "revision_set-0-repository": repository.pk,
    
  1523.             "revision_set-0-revision": "146239817507f148d448db38840db7c3cbf47c76",
    
  1524.             "revision_set-0-DELETE": "",
    
  1525.         }
    
  1526.         formset = FormSet(data, instance=repository)
    
  1527.         self.assertFalse(formset.is_valid())
    
  1528.         self.assertEqual(
    
  1529.             formset.errors,
    
  1530.             [
    
  1531.                 {
    
  1532.                     "__all__": [
    
  1533.                         "Revision with this Repository and Revision already exists."
    
  1534.                     ]
    
  1535.                 }
    
  1536.             ],
    
  1537.         )
    
  1538. 
    
  1539.         # unique_together with inlineformset_factory with overridden form fields
    
  1540.         # Also see #9494
    
  1541. 
    
  1542.         FormSet = inlineformset_factory(
    
  1543.             Repository, Revision, fields=("revision",), extra=1
    
  1544.         )
    
  1545.         data = {
    
  1546.             "revision_set-TOTAL_FORMS": "1",
    
  1547.             "revision_set-INITIAL_FORMS": "0",
    
  1548.             "revision_set-MAX_NUM_FORMS": "",
    
  1549.             "revision_set-0-repository": repository.pk,
    
  1550.             "revision_set-0-revision": "146239817507f148d448db38840db7c3cbf47c76",
    
  1551.             "revision_set-0-DELETE": "",
    
  1552.         }
    
  1553.         formset = FormSet(data, instance=repository)
    
  1554.         self.assertFalse(formset.is_valid())
    
  1555. 
    
  1556.     def test_callable_defaults(self):
    
  1557.         # Use of callable defaults (see bug #7975).
    
  1558. 
    
  1559.         person = Person.objects.create(name="Ringo")
    
  1560.         FormSet = inlineformset_factory(
    
  1561.             Person, Membership, can_delete=False, extra=1, fields="__all__"
    
  1562.         )
    
  1563.         formset = FormSet(instance=person)
    
  1564. 
    
  1565.         # Django will render a hidden field for model fields that have a callable
    
  1566.         # default. This is required to ensure the value is tested for change correctly
    
  1567.         # when determine what extra forms have changed to save.
    
  1568. 
    
  1569.         self.assertEqual(len(formset.forms), 1)  # this formset only has one form
    
  1570.         form = formset.forms[0]
    
  1571.         now = form.fields["date_joined"].initial()
    
  1572.         result = form.as_p()
    
  1573.         result = re.sub(
    
  1574.             r"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?",
    
  1575.             "__DATETIME__",
    
  1576.             result,
    
  1577.         )
    
  1578.         self.assertHTMLEqual(
    
  1579.             result,
    
  1580.             '<p><label for="id_membership_set-0-date_joined">Date joined:</label>'
    
  1581.             '<input type="text" name="membership_set-0-date_joined" '
    
  1582.             'value="__DATETIME__" id="id_membership_set-0-date_joined">'
    
  1583.             '<input type="hidden" name="initial-membership_set-0-date_joined" '
    
  1584.             'value="__DATETIME__" '
    
  1585.             'id="initial-membership_set-0-id_membership_set-0-date_joined"></p>'
    
  1586.             '<p><label for="id_membership_set-0-karma">Karma:</label>'
    
  1587.             '<input type="number" name="membership_set-0-karma" '
    
  1588.             'id="id_membership_set-0-karma">'
    
  1589.             '<input type="hidden" name="membership_set-0-person" value="%d" '
    
  1590.             'id="id_membership_set-0-person">'
    
  1591.             '<input type="hidden" name="membership_set-0-id" '
    
  1592.             'id="id_membership_set-0-id"></p>' % person.id,
    
  1593.         )
    
  1594. 
    
  1595.         # test for validation with callable defaults. Validations rely on hidden fields
    
  1596. 
    
  1597.         data = {
    
  1598.             "membership_set-TOTAL_FORMS": "1",
    
  1599.             "membership_set-INITIAL_FORMS": "0",
    
  1600.             "membership_set-MAX_NUM_FORMS": "",
    
  1601.             "membership_set-0-date_joined": now.strftime("%Y-%m-%d %H:%M:%S"),
    
  1602.             "initial-membership_set-0-date_joined": now.strftime("%Y-%m-%d %H:%M:%S"),
    
  1603.             "membership_set-0-karma": "",
    
  1604.         }
    
  1605.         formset = FormSet(data, instance=person)
    
  1606.         self.assertTrue(formset.is_valid())
    
  1607. 
    
  1608.         # now test for when the data changes
    
  1609. 
    
  1610.         one_day_later = now + datetime.timedelta(days=1)
    
  1611.         filled_data = {
    
  1612.             "membership_set-TOTAL_FORMS": "1",
    
  1613.             "membership_set-INITIAL_FORMS": "0",
    
  1614.             "membership_set-MAX_NUM_FORMS": "",
    
  1615.             "membership_set-0-date_joined": one_day_later.strftime("%Y-%m-%d %H:%M:%S"),
    
  1616.             "initial-membership_set-0-date_joined": now.strftime("%Y-%m-%d %H:%M:%S"),
    
  1617.             "membership_set-0-karma": "",
    
  1618.         }
    
  1619.         formset = FormSet(filled_data, instance=person)
    
  1620.         self.assertFalse(formset.is_valid())
    
  1621. 
    
  1622.         # now test with split datetime fields
    
  1623. 
    
  1624.         class MembershipForm(forms.ModelForm):
    
  1625.             date_joined = forms.SplitDateTimeField(initial=now)
    
  1626. 
    
  1627.             class Meta:
    
  1628.                 model = Membership
    
  1629.                 fields = "__all__"
    
  1630. 
    
  1631.             def __init__(self, **kwargs):
    
  1632.                 super().__init__(**kwargs)
    
  1633.                 self.fields["date_joined"].widget = forms.SplitDateTimeWidget()
    
  1634. 
    
  1635.         FormSet = inlineformset_factory(
    
  1636.             Person,
    
  1637.             Membership,
    
  1638.             form=MembershipForm,
    
  1639.             can_delete=False,
    
  1640.             extra=1,
    
  1641.             fields="__all__",
    
  1642.         )
    
  1643.         data = {
    
  1644.             "membership_set-TOTAL_FORMS": "1",
    
  1645.             "membership_set-INITIAL_FORMS": "0",
    
  1646.             "membership_set-MAX_NUM_FORMS": "",
    
  1647.             "membership_set-0-date_joined_0": now.strftime("%Y-%m-%d"),
    
  1648.             "membership_set-0-date_joined_1": now.strftime("%H:%M:%S"),
    
  1649.             "initial-membership_set-0-date_joined": now.strftime("%Y-%m-%d %H:%M:%S"),
    
  1650.             "membership_set-0-karma": "",
    
  1651.         }
    
  1652.         formset = FormSet(data, instance=person)
    
  1653.         self.assertTrue(formset.is_valid())
    
  1654. 
    
  1655.     def test_inlineformset_factory_with_null_fk(self):
    
  1656.         # inlineformset_factory tests with fk having null=True. see #9462.
    
  1657.         # create some data that will exhibit the issue
    
  1658.         team = Team.objects.create(name="Red Vipers")
    
  1659.         Player(name="Timmy").save()
    
  1660.         Player(name="Bobby", team=team).save()
    
  1661. 
    
  1662.         PlayerInlineFormSet = inlineformset_factory(Team, Player, fields="__all__")
    
  1663.         formset = PlayerInlineFormSet()
    
  1664.         self.assertQuerysetEqual(formset.get_queryset(), [])
    
  1665. 
    
  1666.         formset = PlayerInlineFormSet(instance=team)
    
  1667.         players = formset.get_queryset()
    
  1668.         self.assertEqual(len(players), 1)
    
  1669.         (player1,) = players
    
  1670.         self.assertEqual(player1.team, team)
    
  1671.         self.assertEqual(player1.name, "Bobby")
    
  1672. 
    
  1673.     def test_inlineformset_with_arrayfield(self):
    
  1674.         class SimpleArrayField(forms.CharField):
    
  1675.             """A proxy for django.contrib.postgres.forms.SimpleArrayField."""
    
  1676. 
    
  1677.             def to_python(self, value):
    
  1678.                 value = super().to_python(value)
    
  1679.                 return value.split(",") if value else []
    
  1680. 
    
  1681.         class BookForm(forms.ModelForm):
    
  1682.             title = SimpleArrayField()
    
  1683. 
    
  1684.             class Meta:
    
  1685.                 model = Book
    
  1686.                 fields = ("title",)
    
  1687. 
    
  1688.         BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
    
  1689.         data = {
    
  1690.             "book_set-TOTAL_FORMS": "3",
    
  1691.             "book_set-INITIAL_FORMS": "0",
    
  1692.             "book_set-MAX_NUM_FORMS": "",
    
  1693.             "book_set-0-title": "test1,test2",
    
  1694.             "book_set-1-title": "test1,test2",
    
  1695.             "book_set-2-title": "test3,test4",
    
  1696.         }
    
  1697.         author = Author.objects.create(name="test")
    
  1698.         formset = BookFormSet(data, instance=author)
    
  1699.         self.assertEqual(
    
  1700.             formset.errors,
    
  1701.             [{}, {"__all__": ["Please correct the duplicate values below."]}, {}],
    
  1702.         )
    
  1703. 
    
  1704.     def test_model_formset_with_custom_pk(self):
    
  1705.         # a formset for a Model that has a custom primary key that still needs to be
    
  1706.         # added to the formset automatically
    
  1707.         FormSet = modelformset_factory(
    
  1708.             ClassyMexicanRestaurant, fields=["tacos_are_yummy"]
    
  1709.         )
    
  1710.         self.assertEqual(
    
  1711.             sorted(FormSet().forms[0].fields), ["tacos_are_yummy", "the_restaurant"]
    
  1712.         )
    
  1713. 
    
  1714.     def test_model_formset_with_initial_model_instance(self):
    
  1715.         # has_changed should compare model instance and primary key
    
  1716.         # see #18898
    
  1717.         FormSet = modelformset_factory(Poem, fields="__all__")
    
  1718.         john_milton = Poet(name="John Milton")
    
  1719.         john_milton.save()
    
  1720.         data = {
    
  1721.             "form-TOTAL_FORMS": 1,
    
  1722.             "form-INITIAL_FORMS": 0,
    
  1723.             "form-MAX_NUM_FORMS": "",
    
  1724.             "form-0-name": "",
    
  1725.             "form-0-poet": str(john_milton.id),
    
  1726.         }
    
  1727.         formset = FormSet(initial=[{"poet": john_milton}], data=data)
    
  1728.         self.assertFalse(formset.extra_forms[0].has_changed())
    
  1729. 
    
  1730.     def test_model_formset_with_initial_queryset(self):
    
  1731.         # has_changed should work with queryset and list of pk's
    
  1732.         # see #18898
    
  1733.         FormSet = modelformset_factory(AuthorMeeting, fields="__all__")
    
  1734.         Author.objects.create(pk=1, name="Charles Baudelaire")
    
  1735.         data = {
    
  1736.             "form-TOTAL_FORMS": 1,
    
  1737.             "form-INITIAL_FORMS": 0,
    
  1738.             "form-MAX_NUM_FORMS": "",
    
  1739.             "form-0-name": "",
    
  1740.             "form-0-created": "",
    
  1741.             "form-0-authors": list(Author.objects.values_list("id", flat=True)),
    
  1742.         }
    
  1743.         formset = FormSet(initial=[{"authors": Author.objects.all()}], data=data)
    
  1744.         self.assertFalse(formset.extra_forms[0].has_changed())
    
  1745. 
    
  1746.     def test_prevent_duplicates_from_with_the_same_formset(self):
    
  1747.         FormSet = modelformset_factory(Product, fields="__all__", extra=2)
    
  1748.         data = {
    
  1749.             "form-TOTAL_FORMS": 2,
    
  1750.             "form-INITIAL_FORMS": 0,
    
  1751.             "form-MAX_NUM_FORMS": "",
    
  1752.             "form-0-slug": "red_car",
    
  1753.             "form-1-slug": "red_car",
    
  1754.         }
    
  1755.         formset = FormSet(data)
    
  1756.         self.assertFalse(formset.is_valid())
    
  1757.         self.assertEqual(
    
  1758.             formset._non_form_errors, ["Please correct the duplicate data for slug."]
    
  1759.         )
    
  1760. 
    
  1761.         FormSet = modelformset_factory(Price, fields="__all__", extra=2)
    
  1762.         data = {
    
  1763.             "form-TOTAL_FORMS": 2,
    
  1764.             "form-INITIAL_FORMS": 0,
    
  1765.             "form-MAX_NUM_FORMS": "",
    
  1766.             "form-0-price": "25",
    
  1767.             "form-0-quantity": "7",
    
  1768.             "form-1-price": "25",
    
  1769.             "form-1-quantity": "7",
    
  1770.         }
    
  1771.         formset = FormSet(data)
    
  1772.         self.assertFalse(formset.is_valid())
    
  1773.         self.assertEqual(
    
  1774.             formset._non_form_errors,
    
  1775.             [
    
  1776.                 "Please correct the duplicate data for price and quantity, which must "
    
  1777.                 "be unique."
    
  1778.             ],
    
  1779.         )
    
  1780. 
    
  1781.         # Only the price field is specified, this should skip any unique
    
  1782.         # checks since the unique_together is not fulfilled. This will fail
    
  1783.         # with a KeyError if broken.
    
  1784.         FormSet = modelformset_factory(Price, fields=("price",), extra=2)
    
  1785.         data = {
    
  1786.             "form-TOTAL_FORMS": "2",
    
  1787.             "form-INITIAL_FORMS": "0",
    
  1788.             "form-MAX_NUM_FORMS": "",
    
  1789.             "form-0-price": "24",
    
  1790.             "form-1-price": "24",
    
  1791.         }
    
  1792.         formset = FormSet(data)
    
  1793.         self.assertTrue(formset.is_valid())
    
  1794. 
    
  1795.         FormSet = inlineformset_factory(Author, Book, extra=0, fields="__all__")
    
  1796.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  1797.         Book.objects.create(pk=1, author=author, title="Les Paradis Artificiels")
    
  1798.         Book.objects.create(pk=2, author=author, title="Les Fleurs du Mal")
    
  1799.         Book.objects.create(pk=3, author=author, title="Flowers of Evil")
    
  1800. 
    
  1801.         book_ids = author.book_set.order_by("id").values_list("id", flat=True)
    
  1802.         data = {
    
  1803.             "book_set-TOTAL_FORMS": "2",
    
  1804.             "book_set-INITIAL_FORMS": "2",
    
  1805.             "book_set-MAX_NUM_FORMS": "",
    
  1806.             "book_set-0-title": "The 2008 Election",
    
  1807.             "book_set-0-author": str(author.id),
    
  1808.             "book_set-0-id": str(book_ids[0]),
    
  1809.             "book_set-1-title": "The 2008 Election",
    
  1810.             "book_set-1-author": str(author.id),
    
  1811.             "book_set-1-id": str(book_ids[1]),
    
  1812.         }
    
  1813.         formset = FormSet(data=data, instance=author)
    
  1814.         self.assertFalse(formset.is_valid())
    
  1815.         self.assertEqual(
    
  1816.             formset._non_form_errors, ["Please correct the duplicate data for title."]
    
  1817.         )
    
  1818.         self.assertEqual(
    
  1819.             formset.errors,
    
  1820.             [{}, {"__all__": ["Please correct the duplicate values below."]}],
    
  1821.         )
    
  1822. 
    
  1823.         FormSet = modelformset_factory(Post, fields="__all__", extra=2)
    
  1824.         data = {
    
  1825.             "form-TOTAL_FORMS": "2",
    
  1826.             "form-INITIAL_FORMS": "0",
    
  1827.             "form-MAX_NUM_FORMS": "",
    
  1828.             "form-0-title": "blah",
    
  1829.             "form-0-slug": "Morning",
    
  1830.             "form-0-subtitle": "foo",
    
  1831.             "form-0-posted": "2009-01-01",
    
  1832.             "form-1-title": "blah",
    
  1833.             "form-1-slug": "Morning in Prague",
    
  1834.             "form-1-subtitle": "rawr",
    
  1835.             "form-1-posted": "2009-01-01",
    
  1836.         }
    
  1837.         formset = FormSet(data)
    
  1838.         self.assertFalse(formset.is_valid())
    
  1839.         self.assertEqual(
    
  1840.             formset._non_form_errors,
    
  1841.             [
    
  1842.                 "Please correct the duplicate data for title which must be unique for "
    
  1843.                 "the date in posted."
    
  1844.             ],
    
  1845.         )
    
  1846.         self.assertEqual(
    
  1847.             formset.errors,
    
  1848.             [{}, {"__all__": ["Please correct the duplicate values below."]}],
    
  1849.         )
    
  1850. 
    
  1851.         data = {
    
  1852.             "form-TOTAL_FORMS": "2",
    
  1853.             "form-INITIAL_FORMS": "0",
    
  1854.             "form-MAX_NUM_FORMS": "",
    
  1855.             "form-0-title": "foo",
    
  1856.             "form-0-slug": "Morning in Prague",
    
  1857.             "form-0-subtitle": "foo",
    
  1858.             "form-0-posted": "2009-01-01",
    
  1859.             "form-1-title": "blah",
    
  1860.             "form-1-slug": "Morning in Prague",
    
  1861.             "form-1-subtitle": "rawr",
    
  1862.             "form-1-posted": "2009-08-02",
    
  1863.         }
    
  1864.         formset = FormSet(data)
    
  1865.         self.assertFalse(formset.is_valid())
    
  1866.         self.assertEqual(
    
  1867.             formset._non_form_errors,
    
  1868.             [
    
  1869.                 "Please correct the duplicate data for slug which must be unique for "
    
  1870.                 "the year in posted."
    
  1871.             ],
    
  1872.         )
    
  1873. 
    
  1874.         data = {
    
  1875.             "form-TOTAL_FORMS": "2",
    
  1876.             "form-INITIAL_FORMS": "0",
    
  1877.             "form-MAX_NUM_FORMS": "",
    
  1878.             "form-0-title": "foo",
    
  1879.             "form-0-slug": "Morning in Prague",
    
  1880.             "form-0-subtitle": "rawr",
    
  1881.             "form-0-posted": "2008-08-01",
    
  1882.             "form-1-title": "blah",
    
  1883.             "form-1-slug": "Prague",
    
  1884.             "form-1-subtitle": "rawr",
    
  1885.             "form-1-posted": "2009-08-02",
    
  1886.         }
    
  1887.         formset = FormSet(data)
    
  1888.         self.assertFalse(formset.is_valid())
    
  1889.         self.assertEqual(
    
  1890.             formset._non_form_errors,
    
  1891.             [
    
  1892.                 "Please correct the duplicate data for subtitle which must be unique "
    
  1893.                 "for the month in posted."
    
  1894.             ],
    
  1895.         )
    
  1896. 
    
  1897.     def test_prevent_change_outer_model_and_create_invalid_data(self):
    
  1898.         author = Author.objects.create(name="Charles")
    
  1899.         other_author = Author.objects.create(name="Walt")
    
  1900.         AuthorFormSet = modelformset_factory(Author, fields="__all__")
    
  1901.         data = {
    
  1902.             "form-TOTAL_FORMS": "2",
    
  1903.             "form-INITIAL_FORMS": "2",
    
  1904.             "form-MAX_NUM_FORMS": "",
    
  1905.             "form-0-id": str(author.id),
    
  1906.             "form-0-name": "Charles",
    
  1907.             "form-1-id": str(other_author.id),  # A model not in the formset's queryset.
    
  1908.             "form-1-name": "Changed name",
    
  1909.         }
    
  1910.         # This formset is only for Walt Whitman and shouldn't accept data for
    
  1911.         # other_author.
    
  1912.         formset = AuthorFormSet(
    
  1913.             data=data, queryset=Author.objects.filter(id__in=(author.id,))
    
  1914.         )
    
  1915.         self.assertTrue(formset.is_valid())
    
  1916.         formset.save()
    
  1917.         # The name of other_author shouldn't be changed and new models aren't
    
  1918.         # created.
    
  1919.         self.assertSequenceEqual(Author.objects.all(), [author, other_author])
    
  1920. 
    
  1921.     def test_validation_without_id(self):
    
  1922.         AuthorFormSet = modelformset_factory(Author, fields="__all__")
    
  1923.         data = {
    
  1924.             "form-TOTAL_FORMS": "1",
    
  1925.             "form-INITIAL_FORMS": "1",
    
  1926.             "form-MAX_NUM_FORMS": "",
    
  1927.             "form-0-name": "Charles",
    
  1928.         }
    
  1929.         formset = AuthorFormSet(data)
    
  1930.         self.assertEqual(
    
  1931.             formset.errors,
    
  1932.             [{"id": ["This field is required."]}],
    
  1933.         )
    
  1934. 
    
  1935.     def test_validation_with_child_model_without_id(self):
    
  1936.         BetterAuthorFormSet = modelformset_factory(BetterAuthor, fields="__all__")
    
  1937.         data = {
    
  1938.             "form-TOTAL_FORMS": "1",
    
  1939.             "form-INITIAL_FORMS": "1",
    
  1940.             "form-MAX_NUM_FORMS": "",
    
  1941.             "form-0-name": "Charles",
    
  1942.             "form-0-write_speed": "10",
    
  1943.         }
    
  1944.         formset = BetterAuthorFormSet(data)
    
  1945.         self.assertEqual(
    
  1946.             formset.errors,
    
  1947.             [{"author_ptr": ["This field is required."]}],
    
  1948.         )
    
  1949. 
    
  1950.     def test_validation_with_invalid_id(self):
    
  1951.         AuthorFormSet = modelformset_factory(Author, fields="__all__")
    
  1952.         data = {
    
  1953.             "form-TOTAL_FORMS": "1",
    
  1954.             "form-INITIAL_FORMS": "1",
    
  1955.             "form-MAX_NUM_FORMS": "",
    
  1956.             "form-0-id": "abc",
    
  1957.             "form-0-name": "Charles",
    
  1958.         }
    
  1959.         formset = AuthorFormSet(data)
    
  1960.         self.assertEqual(
    
  1961.             formset.errors,
    
  1962.             [
    
  1963.                 {
    
  1964.                     "id": [
    
  1965.                         "Select a valid choice. That choice is not one of the "
    
  1966.                         "available choices."
    
  1967.                     ]
    
  1968.                 }
    
  1969.             ],
    
  1970.         )
    
  1971. 
    
  1972.     def test_validation_with_nonexistent_id(self):
    
  1973.         AuthorFormSet = modelformset_factory(Author, fields="__all__")
    
  1974.         data = {
    
  1975.             "form-TOTAL_FORMS": "1",
    
  1976.             "form-INITIAL_FORMS": "1",
    
  1977.             "form-MAX_NUM_FORMS": "",
    
  1978.             "form-0-id": "12345",
    
  1979.             "form-0-name": "Charles",
    
  1980.         }
    
  1981.         formset = AuthorFormSet(data)
    
  1982.         self.assertEqual(
    
  1983.             formset.errors,
    
  1984.             [
    
  1985.                 {
    
  1986.                     "id": [
    
  1987.                         "Select a valid choice. That choice is not one of the "
    
  1988.                         "available choices."
    
  1989.                     ]
    
  1990.                 }
    
  1991.             ],
    
  1992.         )
    
  1993. 
    
  1994.     def test_initial_form_count_empty_data(self):
    
  1995.         AuthorFormSet = modelformset_factory(Author, fields="__all__")
    
  1996.         formset = AuthorFormSet({})
    
  1997.         self.assertEqual(formset.initial_form_count(), 0)
    
  1998. 
    
  1999.     def test_edit_only(self):
    
  2000.         charles = Author.objects.create(name="Charles Baudelaire")
    
  2001.         AuthorFormSet = modelformset_factory(Author, fields="__all__", edit_only=True)
    
  2002.         data = {
    
  2003.             "form-TOTAL_FORMS": "2",
    
  2004.             "form-INITIAL_FORMS": "0",
    
  2005.             "form-MAX_NUM_FORMS": "0",
    
  2006.             "form-0-name": "Arthur Rimbaud",
    
  2007.             "form-1-name": "Walt Whitman",
    
  2008.         }
    
  2009.         formset = AuthorFormSet(data)
    
  2010.         self.assertIs(formset.is_valid(), True)
    
  2011.         formset.save()
    
  2012.         self.assertSequenceEqual(Author.objects.all(), [charles])
    
  2013.         data = {
    
  2014.             "form-TOTAL_FORMS": "2",
    
  2015.             "form-INITIAL_FORMS": "1",
    
  2016.             "form-MAX_NUM_FORMS": "0",
    
  2017.             "form-0-id": charles.pk,
    
  2018.             "form-0-name": "Arthur Rimbaud",
    
  2019.             "form-1-name": "Walt Whitman",
    
  2020.         }
    
  2021.         formset = AuthorFormSet(data)
    
  2022.         self.assertIs(formset.is_valid(), True)
    
  2023.         formset.save()
    
  2024.         charles.refresh_from_db()
    
  2025.         self.assertEqual(charles.name, "Arthur Rimbaud")
    
  2026.         self.assertSequenceEqual(Author.objects.all(), [charles])
    
  2027. 
    
  2028.     def test_edit_only_inlineformset_factory(self):
    
  2029.         charles = Author.objects.create(name="Charles Baudelaire")
    
  2030.         book = Book.objects.create(author=charles, title="Les Paradis Artificiels")
    
  2031.         AuthorFormSet = inlineformset_factory(
    
  2032.             Author,
    
  2033.             Book,
    
  2034.             can_delete=False,
    
  2035.             fields="__all__",
    
  2036.             edit_only=True,
    
  2037.         )
    
  2038.         data = {
    
  2039.             "book_set-TOTAL_FORMS": "4",
    
  2040.             "book_set-INITIAL_FORMS": "1",
    
  2041.             "book_set-MAX_NUM_FORMS": "0",
    
  2042.             "book_set-0-id": book.pk,
    
  2043.             "book_set-0-title": "Les Fleurs du Mal",
    
  2044.             "book_set-0-author": charles.pk,
    
  2045.             "book_set-1-title": "Flowers of Evil",
    
  2046.             "book_set-1-author": charles.pk,
    
  2047.         }
    
  2048.         formset = AuthorFormSet(data, instance=charles)
    
  2049.         self.assertIs(formset.is_valid(), True)
    
  2050.         formset.save()
    
  2051.         book.refresh_from_db()
    
  2052.         self.assertEqual(book.title, "Les Fleurs du Mal")
    
  2053.         self.assertSequenceEqual(Book.objects.all(), [book])
    
  2054. 
    
  2055.     def test_edit_only_object_outside_of_queryset(self):
    
  2056.         charles = Author.objects.create(name="Charles Baudelaire")
    
  2057.         walt = Author.objects.create(name="Walt Whitman")
    
  2058.         data = {
    
  2059.             "form-TOTAL_FORMS": "1",
    
  2060.             "form-INITIAL_FORMS": "1",
    
  2061.             "form-0-id": walt.pk,
    
  2062.             "form-0-name": "Parth Patil",
    
  2063.         }
    
  2064.         AuthorFormSet = modelformset_factory(Author, fields="__all__", edit_only=True)
    
  2065.         formset = AuthorFormSet(data, queryset=Author.objects.filter(pk=charles.pk))
    
  2066.         self.assertIs(formset.is_valid(), True)
    
  2067.         formset.save()
    
  2068.         self.assertCountEqual(Author.objects.all(), [charles, walt])
    
  2069. 
    
  2070.     def test_edit_only_formset_factory_with_basemodelformset(self):
    
  2071.         charles = Author.objects.create(name="Charles Baudelaire")
    
  2072. 
    
  2073.         class AuthorForm(forms.ModelForm):
    
  2074.             class Meta:
    
  2075.                 model = Author
    
  2076.                 fields = "__all__"
    
  2077. 
    
  2078.         class BaseAuthorFormSet(BaseModelFormSet):
    
  2079.             def __init__(self, *args, **kwargs):
    
  2080.                 self.model = Author
    
  2081.                 super().__init__(*args, **kwargs)
    
  2082. 
    
  2083.         AuthorFormSet = formset_factory(AuthorForm, formset=BaseAuthorFormSet)
    
  2084.         data = {
    
  2085.             "form-TOTAL_FORMS": "2",
    
  2086.             "form-INITIAL_FORMS": "1",
    
  2087.             "form-MAX_NUM_FORMS": "0",
    
  2088.             "form-0-id": charles.pk,
    
  2089.             "form-0-name": "Shawn Dong",
    
  2090.             "form-1-name": "Walt Whitman",
    
  2091.         }
    
  2092.         formset = AuthorFormSet(data)
    
  2093.         self.assertIs(formset.is_valid(), True)
    
  2094.         formset.save()
    
  2095.         self.assertEqual(Author.objects.count(), 2)
    
  2096.         charles.refresh_from_db()
    
  2097.         self.assertEqual(charles.name, "Shawn Dong")
    
  2098.         self.assertEqual(Author.objects.count(), 2)
    
  2099. 
    
  2100. 
    
  2101. class TestModelFormsetOverridesTroughFormMeta(TestCase):
    
  2102.     def test_modelformset_factory_widgets(self):
    
  2103.         widgets = {"name": forms.TextInput(attrs={"class": "poet"})}
    
  2104.         PoetFormSet = modelformset_factory(Poet, fields="__all__", widgets=widgets)
    
  2105.         form = PoetFormSet.form()
    
  2106.         self.assertHTMLEqual(
    
  2107.             str(form["name"]),
    
  2108.             '<input id="id_name" maxlength="100" type="text" class="poet" name="name" '
    
  2109.             "required>",
    
  2110.         )
    
  2111. 
    
  2112.     def test_inlineformset_factory_widgets(self):
    
  2113.         widgets = {"title": forms.TextInput(attrs={"class": "book"})}
    
  2114.         BookFormSet = inlineformset_factory(
    
  2115.             Author, Book, widgets=widgets, fields="__all__"
    
  2116.         )
    
  2117.         form = BookFormSet.form()
    
  2118.         self.assertHTMLEqual(
    
  2119.             str(form["title"]),
    
  2120.             '<input class="book" id="id_title" maxlength="100" name="title" '
    
  2121.             'type="text" required>',
    
  2122.         )
    
  2123. 
    
  2124.     def test_modelformset_factory_labels_overrides(self):
    
  2125.         BookFormSet = modelformset_factory(
    
  2126.             Book, fields="__all__", labels={"title": "Name"}
    
  2127.         )
    
  2128.         form = BookFormSet.form()
    
  2129.         self.assertHTMLEqual(
    
  2130.             form["title"].label_tag(), '<label for="id_title">Name:</label>'
    
  2131.         )
    
  2132.         self.assertHTMLEqual(
    
  2133.             form["title"].legend_tag(),
    
  2134.             '<legend for="id_title">Name:</legend>',
    
  2135.         )
    
  2136. 
    
  2137.     def test_inlineformset_factory_labels_overrides(self):
    
  2138.         BookFormSet = inlineformset_factory(
    
  2139.             Author, Book, fields="__all__", labels={"title": "Name"}
    
  2140.         )
    
  2141.         form = BookFormSet.form()
    
  2142.         self.assertHTMLEqual(
    
  2143.             form["title"].label_tag(), '<label for="id_title">Name:</label>'
    
  2144.         )
    
  2145.         self.assertHTMLEqual(
    
  2146.             form["title"].legend_tag(),
    
  2147.             '<legend for="id_title">Name:</legend>',
    
  2148.         )
    
  2149. 
    
  2150.     def test_modelformset_factory_help_text_overrides(self):
    
  2151.         BookFormSet = modelformset_factory(
    
  2152.             Book, fields="__all__", help_texts={"title": "Choose carefully."}
    
  2153.         )
    
  2154.         form = BookFormSet.form()
    
  2155.         self.assertEqual(form["title"].help_text, "Choose carefully.")
    
  2156. 
    
  2157.     def test_inlineformset_factory_help_text_overrides(self):
    
  2158.         BookFormSet = inlineformset_factory(
    
  2159.             Author, Book, fields="__all__", help_texts={"title": "Choose carefully."}
    
  2160.         )
    
  2161.         form = BookFormSet.form()
    
  2162.         self.assertEqual(form["title"].help_text, "Choose carefully.")
    
  2163. 
    
  2164.     def test_modelformset_factory_error_messages_overrides(self):
    
  2165.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  2166.         BookFormSet = modelformset_factory(
    
  2167.             Book,
    
  2168.             fields="__all__",
    
  2169.             error_messages={"title": {"max_length": "Title too long!!"}},
    
  2170.         )
    
  2171.         form = BookFormSet.form(data={"title": "Foo " * 30, "author": author.id})
    
  2172.         form.full_clean()
    
  2173.         self.assertEqual(form.errors, {"title": ["Title too long!!"]})
    
  2174. 
    
  2175.     def test_inlineformset_factory_error_messages_overrides(self):
    
  2176.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  2177.         BookFormSet = inlineformset_factory(
    
  2178.             Author,
    
  2179.             Book,
    
  2180.             fields="__all__",
    
  2181.             error_messages={"title": {"max_length": "Title too long!!"}},
    
  2182.         )
    
  2183.         form = BookFormSet.form(data={"title": "Foo " * 30, "author": author.id})
    
  2184.         form.full_clean()
    
  2185.         self.assertEqual(form.errors, {"title": ["Title too long!!"]})
    
  2186. 
    
  2187.     def test_modelformset_factory_field_class_overrides(self):
    
  2188.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  2189.         BookFormSet = modelformset_factory(
    
  2190.             Book,
    
  2191.             fields="__all__",
    
  2192.             field_classes={
    
  2193.                 "title": forms.SlugField,
    
  2194.             },
    
  2195.         )
    
  2196.         form = BookFormSet.form(data={"title": "Foo " * 30, "author": author.id})
    
  2197.         self.assertIs(Book._meta.get_field("title").__class__, models.CharField)
    
  2198.         self.assertIsInstance(form.fields["title"], forms.SlugField)
    
  2199. 
    
  2200.     def test_inlineformset_factory_field_class_overrides(self):
    
  2201.         author = Author.objects.create(pk=1, name="Charles Baudelaire")
    
  2202.         BookFormSet = inlineformset_factory(
    
  2203.             Author,
    
  2204.             Book,
    
  2205.             fields="__all__",
    
  2206.             field_classes={
    
  2207.                 "title": forms.SlugField,
    
  2208.             },
    
  2209.         )
    
  2210.         form = BookFormSet.form(data={"title": "Foo " * 30, "author": author.id})
    
  2211.         self.assertIs(Book._meta.get_field("title").__class__, models.CharField)
    
  2212.         self.assertIsInstance(form.fields["title"], forms.SlugField)
    
  2213. 
    
  2214.     def test_modelformset_factory_absolute_max(self):
    
  2215.         AuthorFormSet = modelformset_factory(
    
  2216.             Author, fields="__all__", absolute_max=1500
    
  2217.         )
    
  2218.         data = {
    
  2219.             "form-TOTAL_FORMS": "1501",
    
  2220.             "form-INITIAL_FORMS": "0",
    
  2221.             "form-MAX_NUM_FORMS": "0",
    
  2222.         }
    
  2223.         formset = AuthorFormSet(data=data)
    
  2224.         self.assertIs(formset.is_valid(), False)
    
  2225.         self.assertEqual(len(formset.forms), 1500)
    
  2226.         self.assertEqual(
    
  2227.             formset.non_form_errors(),
    
  2228.             ["Please submit at most 1000 forms."],
    
  2229.         )
    
  2230. 
    
  2231.     def test_modelformset_factory_absolute_max_with_max_num(self):
    
  2232.         AuthorFormSet = modelformset_factory(
    
  2233.             Author,
    
  2234.             fields="__all__",
    
  2235.             max_num=20,
    
  2236.             absolute_max=100,
    
  2237.         )
    
  2238.         data = {
    
  2239.             "form-TOTAL_FORMS": "101",
    
  2240.             "form-INITIAL_FORMS": "0",
    
  2241.             "form-MAX_NUM_FORMS": "0",
    
  2242.         }
    
  2243.         formset = AuthorFormSet(data=data)
    
  2244.         self.assertIs(formset.is_valid(), False)
    
  2245.         self.assertEqual(len(formset.forms), 100)
    
  2246.         self.assertEqual(
    
  2247.             formset.non_form_errors(),
    
  2248.             ["Please submit at most 20 forms."],
    
  2249.         )
    
  2250. 
    
  2251.     def test_inlineformset_factory_absolute_max(self):
    
  2252.         author = Author.objects.create(name="Charles Baudelaire")
    
  2253.         BookFormSet = inlineformset_factory(
    
  2254.             Author,
    
  2255.             Book,
    
  2256.             fields="__all__",
    
  2257.             absolute_max=1500,
    
  2258.         )
    
  2259.         data = {
    
  2260.             "book_set-TOTAL_FORMS": "1501",
    
  2261.             "book_set-INITIAL_FORMS": "0",
    
  2262.             "book_set-MAX_NUM_FORMS": "0",
    
  2263.         }
    
  2264.         formset = BookFormSet(data, instance=author)
    
  2265.         self.assertIs(formset.is_valid(), False)
    
  2266.         self.assertEqual(len(formset.forms), 1500)
    
  2267.         self.assertEqual(
    
  2268.             formset.non_form_errors(),
    
  2269.             ["Please submit at most 1000 forms."],
    
  2270.         )
    
  2271. 
    
  2272.     def test_inlineformset_factory_absolute_max_with_max_num(self):
    
  2273.         author = Author.objects.create(name="Charles Baudelaire")
    
  2274.         BookFormSet = inlineformset_factory(
    
  2275.             Author,
    
  2276.             Book,
    
  2277.             fields="__all__",
    
  2278.             max_num=20,
    
  2279.             absolute_max=100,
    
  2280.         )
    
  2281.         data = {
    
  2282.             "book_set-TOTAL_FORMS": "101",
    
  2283.             "book_set-INITIAL_FORMS": "0",
    
  2284.             "book_set-MAX_NUM_FORMS": "0",
    
  2285.         }
    
  2286.         formset = BookFormSet(data, instance=author)
    
  2287.         self.assertIs(formset.is_valid(), False)
    
  2288.         self.assertEqual(len(formset.forms), 100)
    
  2289.         self.assertEqual(
    
  2290.             formset.non_form_errors(),
    
  2291.             ["Please submit at most 20 forms."],
    
  2292.         )
    
  2293. 
    
  2294.     def test_modelformset_factory_can_delete_extra(self):
    
  2295.         AuthorFormSet = modelformset_factory(
    
  2296.             Author,
    
  2297.             fields="__all__",
    
  2298.             can_delete=True,
    
  2299.             can_delete_extra=True,
    
  2300.             extra=2,
    
  2301.         )
    
  2302.         formset = AuthorFormSet()
    
  2303.         self.assertEqual(len(formset), 2)
    
  2304.         self.assertIn("DELETE", formset.forms[0].fields)
    
  2305.         self.assertIn("DELETE", formset.forms[1].fields)
    
  2306. 
    
  2307.     def test_modelformset_factory_disable_delete_extra(self):
    
  2308.         AuthorFormSet = modelformset_factory(
    
  2309.             Author,
    
  2310.             fields="__all__",
    
  2311.             can_delete=True,
    
  2312.             can_delete_extra=False,
    
  2313.             extra=2,
    
  2314.         )
    
  2315.         formset = AuthorFormSet()
    
  2316.         self.assertEqual(len(formset), 2)
    
  2317.         self.assertNotIn("DELETE", formset.forms[0].fields)
    
  2318.         self.assertNotIn("DELETE", formset.forms[1].fields)
    
  2319. 
    
  2320.     def test_inlineformset_factory_can_delete_extra(self):
    
  2321.         BookFormSet = inlineformset_factory(
    
  2322.             Author,
    
  2323.             Book,
    
  2324.             fields="__all__",
    
  2325.             can_delete=True,
    
  2326.             can_delete_extra=True,
    
  2327.             extra=2,
    
  2328.         )
    
  2329.         formset = BookFormSet()
    
  2330.         self.assertEqual(len(formset), 2)
    
  2331.         self.assertIn("DELETE", formset.forms[0].fields)
    
  2332.         self.assertIn("DELETE", formset.forms[1].fields)
    
  2333. 
    
  2334.     def test_inlineformset_factory_can_not_delete_extra(self):
    
  2335.         BookFormSet = inlineformset_factory(
    
  2336.             Author,
    
  2337.             Book,
    
  2338.             fields="__all__",
    
  2339.             can_delete=True,
    
  2340.             can_delete_extra=False,
    
  2341.             extra=2,
    
  2342.         )
    
  2343.         formset = BookFormSet()
    
  2344.         self.assertEqual(len(formset), 2)
    
  2345.         self.assertNotIn("DELETE", formset.forms[0].fields)
    
  2346.         self.assertNotIn("DELETE", formset.forms[1].fields)
    
  2347. 
    
  2348.     def test_inlineformset_factory_passes_renderer(self):
    
  2349.         from django.forms.renderers import Jinja2
    
  2350. 
    
  2351.         renderer = Jinja2()
    
  2352.         BookFormSet = inlineformset_factory(
    
  2353.             Author,
    
  2354.             Book,
    
  2355.             fields="__all__",
    
  2356.             renderer=renderer,
    
  2357.         )
    
  2358.         formset = BookFormSet()
    
  2359.         self.assertEqual(formset.renderer, renderer)
    
  2360. 
    
  2361.     def test_modelformset_factory_passes_renderer(self):
    
  2362.         from django.forms.renderers import Jinja2
    
  2363. 
    
  2364.         renderer = Jinja2()
    
  2365.         BookFormSet = modelformset_factory(Author, fields="__all__", renderer=renderer)
    
  2366.         formset = BookFormSet()
    
  2367.         self.assertEqual(formset.renderer, renderer)