summaryrefslogtreecommitdiff
path: root/docs/topics/forms/formsets.txt
blob: 30979edae4065b07577eb1cfac995fd410793810 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
========
Formsets
========

.. currentmodule:: django.forms.formsets

.. class:: BaseFormSet

A formset is a layer of abstraction to work with multiple forms on the same
page. It can be best compared to a data grid. Let's say you have the following
form::

    >>> from django import forms
    >>> class ArticleForm(forms.Form):
    ...     title = forms.CharField()
    ...     pub_date = forms.DateField()

You might want to allow the user to create several articles at once. To create
a formset out of an ``ArticleForm`` you would do::

    >>> from django.forms import formset_factory
    >>> ArticleFormSet = formset_factory(ArticleForm)

You now have created a formset class named ``ArticleFormSet``.
Instantiating the formset gives you the ability to iterate over the forms
in the formset and display them as you would with a regular form::

    >>> formset = ArticleFormSet()
    >>> for form in formset:
    ...     print(form.as_table())
    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>

As you can see it only displayed one empty form. The number of empty forms
that is displayed is controlled by the ``extra`` parameter. By default,
:func:`~django.forms.formsets.formset_factory` defines one extra form; the
following example will create a formset class to display two blank forms::

    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)

Iterating over a formset will render the forms in the order they were
created. You can change this order by providing an alternate implementation for
the ``__iter__()`` method.

Formsets can also be indexed into, which returns the corresponding form. If you
override ``__iter__``, you will need to also override ``__getitem__`` to have
matching behavior.

.. _formsets-initial-data:

Using initial data with a formset
=================================

Initial data is what drives the main usability of a formset. As shown above
you can define the number of extra forms. What this means is that you are
telling the formset how many additional forms to show in addition to the
number of forms it generates from the initial data. Let's take a look at an
example::

    >>> import datetime
    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
    >>> formset = ArticleFormSet(initial=[
    ...     {'title': 'Django is now open source',
    ...      'pub_date': datetime.date.today(),}
    ... ])

    >>> for form in formset:
    ...     print(form.as_table())
    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></td></tr>
    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date"></td></tr>
    <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title"></td></tr>
    <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></td></tr>
    <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
    <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>

There are now a total of three forms showing above. One for the initial data
that was passed in and two extra forms. Also note that we are passing in a
list of dictionaries as the initial data.

If you use an ``initial`` for displaying a formset, you should pass the same
``initial`` when processing that formset's submission so that the formset can
detect which forms were changed by the user. For example, you might have
something like: ``ArticleFormSet(request.POST, initial=[...])``.

.. seealso::

    :ref:`Creating formsets from models with model formsets <model-formsets>`.

.. _formsets-max-num:

Limiting the maximum number of forms
====================================

The ``max_num`` parameter to :func:`~django.forms.formsets.formset_factory`
gives you the ability to limit the number of forms the formset will display::

    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
    >>> formset = ArticleFormSet()
    >>> for form in formset:
    ...     print(form.as_table())
    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>

If the value of ``max_num`` is greater than the number of existing items in the
initial data, up to ``extra`` additional blank forms will be added to the
formset, so long as the total number of forms does not exceed ``max_num``. For
example, if ``extra=2`` and ``max_num=2`` and the formset is initialized with
one ``initial`` item, a form for the initial item and one blank form will be
displayed.

If the number of items in the initial data exceeds ``max_num``, all initial
data forms will be displayed regardless of the value of ``max_num`` and no
extra forms will be displayed. For example, if ``extra=3`` and ``max_num=1``
and the formset is initialized with two initial items, two forms with the
initial data will be displayed.

A ``max_num`` value of ``None`` (the default) puts a high limit on the number
of forms displayed (1000). In practice this is equivalent to no limit.

By default, ``max_num`` only affects how many forms are displayed and does not
affect validation.  If ``validate_max=True`` is passed to the
:func:`~django.forms.formsets.formset_factory`, then ``max_num`` will affect
validation.  See :ref:`validate_max`.

.. _formsets-absolute-max:

Limiting the maximum number of instantiated forms
=================================================

.. versionadded:: 3.2

The ``absolute_max`` parameter to :func:`.formset_factory` allows limiting the
number of forms that can be instantiated when supplying ``POST`` data. This
protects against memory exhaustion attacks using forged ``POST`` requests::

    >>> from django.forms.formsets import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> ArticleFormSet = formset_factory(ArticleForm, absolute_max=1500)
    >>> data = {
    ...     'form-TOTAL_FORMS': '1501',
    ...     'form-INITIAL_FORMS': '0',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> len(formset.forms)
    1500
    >>> formset.is_valid()
    False
    >>> formset.non_form_errors()
    ['Please submit at most 1000 forms.']

When ``absolute_max`` is ``None``, it defaults to ``max_num + 1000``. (If
``max_num`` is ``None``, it defaults to ``2000``).

If ``absolute_max`` is less than ``max_num``, a ``ValueError`` will be raised.

Formset validation
==================

Validation with a formset is almost identical to a regular ``Form``. There is
an ``is_valid`` method on the formset to provide a convenient way to validate
all forms in the formset::

    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> ArticleFormSet = formset_factory(ArticleForm)
    >>> data = {
    ...     'form-TOTAL_FORMS': '1',
    ...     'form-INITIAL_FORMS': '0',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    True

We passed in no data to the formset which is resulting in a valid form. The
formset is smart enough to ignore extra forms that were not changed. If we
provide an invalid article::

    >>> data = {
    ...     'form-TOTAL_FORMS': '2',
    ...     'form-INITIAL_FORMS': '0',
    ...     'form-0-title': 'Test',
    ...     'form-0-pub_date': '1904-06-16',
    ...     'form-1-title': 'Test',
    ...     'form-1-pub_date': '', # <-- this date is missing but required
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    False
    >>> formset.errors
    [{}, {'pub_date': ['This field is required.']}]

As we can see, ``formset.errors`` is a list whose entries correspond to the
forms in the formset. Validation was performed for each of the two forms, and
the expected error message appears for the second item.

Just like when using a normal ``Form``, each field in a formset's forms may
include HTML attributes such as ``maxlength`` for browser validation. However,
form fields of formsets won't include the ``required`` attribute as that
validation may be incorrect when adding and deleting forms.

.. method:: BaseFormSet.total_error_count()

To check how many errors there are in the formset, we can use the
``total_error_count`` method::

    >>> # Using the previous example
    >>> formset.errors
    [{}, {'pub_date': ['This field is required.']}]
    >>> len(formset.errors)
    2
    >>> formset.total_error_count()
    1

We can also check if form data differs from the initial data (i.e. the form was
sent without any data)::

    >>> data = {
    ...     'form-TOTAL_FORMS': '1',
    ...     'form-INITIAL_FORMS': '0',
    ...     'form-0-title': '',
    ...     'form-0-pub_date': '',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.has_changed()
    False

.. _understanding-the-managementform:

Understanding the ``ManagementForm``
------------------------------------

You may have noticed the additional data (``form-TOTAL_FORMS``,
``form-INITIAL_FORMS``) that was required in the formset's data above. This
data is required for the ``ManagementForm``. This form is used by the formset
to manage the collection of forms contained in the formset. If you don't
provide this management data, the formset will be invalid::

    >>> data = {
    ...     'form-0-title': 'Test',
    ...     'form-0-pub_date': '',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    False

It is used to keep track of how many form instances are being displayed. If
you are adding new forms via JavaScript, you should increment the count fields
in this form as well. On the other hand, if you are using JavaScript to allow
deletion of existing objects, then you need to ensure the ones being removed
are properly marked for deletion by including ``form-#-DELETE`` in the ``POST``
data. It is expected that all forms are present in the ``POST`` data regardless.

The management form is available as an attribute of the formset
itself. When rendering a formset in a template, you can include all
the management data by rendering ``{{ my_formset.management_form }}``
(substituting the name of your formset as appropriate).

.. note::

    As well as the ``form-TOTAL_FORMS`` and ``form-INITIAL_FORMS`` fields shown
    in the examples here, the management form also includes
    ``form-MIN_NUM_FORMS`` and ``form-MAX_NUM_FORMS`` fields. They are output
    with the rest of the management form, but only for the convenience of
    client-side code. These fields are not required and so are not shown in
    the example ``POST`` data.

.. versionchanged:: 3.2

    ``formset.is_valid()`` now returns ``False`` rather than raising an
    exception when the management form is missing or has been tampered with.

``total_form_count`` and ``initial_form_count``
-----------------------------------------------

``BaseFormSet`` has a couple of methods that are closely related to the
``ManagementForm``, ``total_form_count`` and ``initial_form_count``.

``total_form_count`` returns the total number of forms in this formset.
``initial_form_count`` returns the number of forms in the formset that were
pre-filled, and is also used to determine how many forms are required. You
will probably never need to override either of these methods, so please be
sure you understand what they do before doing so.

.. _empty_form:

``empty_form``
--------------

``BaseFormSet`` provides an additional attribute ``empty_form`` which returns
a form instance with a prefix of ``__prefix__`` for easier use in dynamic
forms with JavaScript.

``error_messages``
------------------

.. versionadded:: 3.2

The ``error_messages`` argument lets you override the default messages that the
formset will raise. Pass in a dictionary with keys matching the error messages
you want to override. For example, here is the default error message when the
management form is missing::

    >>> formset = ArticleFormSet({})
    >>> formset.is_valid()
    False
    >>> formset.non_form_errors()
    ['ManagementForm data is missing or has been tampered with. Missing fields: form-TOTAL_FORMS, form-INITIAL_FORMS. You may need to file a bug report if the issue persists.']

And here is a custom error message::

    >>> formset = ArticleFormSet({}, error_messages={'missing_management_form': 'Sorry, something went wrong.'})
    >>> formset.is_valid()
    False
    >>> formset.non_form_errors()
    ['Sorry, something went wrong.']

Custom formset validation
-------------------------

A formset has a ``clean`` method similar to the one on a ``Form`` class. This
is where you define your own validation that works at the formset level::

    >>> from django.core.exceptions import ValidationError
    >>> from django.forms import BaseFormSet
    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm

    >>> class BaseArticleFormSet(BaseFormSet):
    ...     def clean(self):
    ...         """Checks that no two articles have the same title."""
    ...         if any(self.errors):
    ...             # Don't bother validating the formset unless each form is valid on its own
    ...             return
    ...         titles = []
    ...         for form in self.forms:
    ...             if self.can_delete and self._should_delete_form(form):
    ...                 continue
    ...             title = form.cleaned_data.get('title')
    ...             if title in titles:
    ...                 raise ValidationError("Articles in a set must have distinct titles.")
    ...             titles.append(title)

    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
    >>> data = {
    ...     'form-TOTAL_FORMS': '2',
    ...     'form-INITIAL_FORMS': '0',
    ...     'form-0-title': 'Test',
    ...     'form-0-pub_date': '1904-06-16',
    ...     'form-1-title': 'Test',
    ...     'form-1-pub_date': '1912-06-23',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    False
    >>> formset.errors
    [{}, {}]
    >>> formset.non_form_errors()
    ['Articles in a set must have distinct titles.']

The formset ``clean`` method is called after all the ``Form.clean`` methods
have been called. The errors will be found using the ``non_form_errors()``
method on the formset.

Non-form errors will be rendered with an additional class of ``nonform`` to
help distinguish them from form-specific errors. For example,
``{{ formset.non_form_errors }}`` would look like:

.. code-block:: html+django

    <ul class="errorlist nonform">
        <li>Articles in a set must have distinct titles.</li>
    </ul>

.. versionchanged:: 4.0

    The additional ``nonform`` class was added.

Validating the number of forms in a formset
===========================================

Django provides a couple ways to validate the minimum or maximum number of
submitted forms. Applications which need more customizable validation of the
number of forms should use custom formset validation.

.. _validate_max:

``validate_max``
----------------

If ``validate_max=True`` is passed to
:func:`~django.forms.formsets.formset_factory`, validation will also check
that the number of forms in the data set, minus those marked for
deletion, is less than or equal to ``max_num``.

    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
    >>> data = {
    ...     'form-TOTAL_FORMS': '2',
    ...     'form-INITIAL_FORMS': '0',
    ...     'form-0-title': 'Test',
    ...     'form-0-pub_date': '1904-06-16',
    ...     'form-1-title': 'Test 2',
    ...     'form-1-pub_date': '1912-06-23',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    False
    >>> formset.errors
    [{}, {}]
    >>> formset.non_form_errors()
    ['Please submit at most 1 form.']

``validate_max=True`` validates against ``max_num`` strictly even if
``max_num`` was exceeded because the amount of initial data supplied was
excessive.

.. note::

    Regardless of ``validate_max``, if the number of forms in a data set
    exceeds ``absolute_max``, then the form will fail to validate as if
    ``validate_max`` were set, and additionally only the first ``absolute_max``
    forms will be validated. The remainder will be truncated entirely. This is
    to protect against memory exhaustion attacks using forged POST requests.
    See :ref:`formsets-absolute-max`.

``validate_min``
----------------

If ``validate_min=True`` is passed to
:func:`~django.forms.formsets.formset_factory`, validation will also check
that the number of forms in the data set, minus those marked for
deletion, is greater than or equal to ``min_num``.

    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
    >>> data = {
    ...     'form-TOTAL_FORMS': '2',
    ...     'form-INITIAL_FORMS': '0',
    ...     'form-0-title': 'Test',
    ...     'form-0-pub_date': '1904-06-16',
    ...     'form-1-title': 'Test 2',
    ...     'form-1-pub_date': '1912-06-23',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    False
    >>> formset.errors
    [{}, {}]
    >>> formset.non_form_errors()
    ['Please submit at least 3 forms.']

.. note::

    Regardless of ``validate_min``, if a formset contains no data, then
    ``extra + min_num`` empty forms will be displayed.

Dealing with ordering and deletion of forms
===========================================

The :func:`~django.forms.formsets.formset_factory` provides two optional
parameters ``can_order`` and ``can_delete`` to help with ordering of forms in
formsets and deletion of forms from a formset.

``can_order``
-------------

.. attribute:: BaseFormSet.can_order

Default: ``False``

Lets you create a formset with the ability to order::

    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
    >>> formset = ArticleFormSet(initial=[
    ...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
    ...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
    ... ])
    >>> for form in formset:
    ...     print(form.as_table())
    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
    <tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></td></tr>
    <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
    <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
    <tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></td></tr>
    <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
    <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
    <tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></td></tr>

This adds an additional field to each form. This new field is named ``ORDER``
and is an ``forms.IntegerField``. For the forms that came from the initial
data it automatically assigned them a numeric value. Let's look at what will
happen when the user changes these values::

    >>> data = {
    ...     'form-TOTAL_FORMS': '3',
    ...     'form-INITIAL_FORMS': '2',
    ...     'form-0-title': 'Article #1',
    ...     'form-0-pub_date': '2008-05-10',
    ...     'form-0-ORDER': '2',
    ...     'form-1-title': 'Article #2',
    ...     'form-1-pub_date': '2008-05-11',
    ...     'form-1-ORDER': '1',
    ...     'form-2-title': 'Article #3',
    ...     'form-2-pub_date': '2008-05-01',
    ...     'form-2-ORDER': '0',
    ... }

    >>> formset = ArticleFormSet(data, initial=[
    ...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
    ...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
    ... ])
    >>> formset.is_valid()
    True
    >>> for form in formset.ordered_forms:
    ...     print(form.cleaned_data)
    {'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': 'Article #3'}
    {'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
    {'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}

:class:`~django.forms.formsets.BaseFormSet` also provides an
:attr:`~django.forms.formsets.BaseFormSet.ordering_widget` attribute and
:meth:`~django.forms.formsets.BaseFormSet.get_ordering_widget` method that
control the widget used with
:attr:`~django.forms.formsets.BaseFormSet.can_order`.

``ordering_widget``
^^^^^^^^^^^^^^^^^^^

.. attribute:: BaseFormSet.ordering_widget

Default: :class:`~django.forms.NumberInput`

Set ``ordering_widget`` to specify the widget class to be used with
``can_order``::

    >>> from django.forms import BaseFormSet, formset_factory
    >>> from myapp.forms import ArticleForm
    >>> class BaseArticleFormSet(BaseFormSet):
    ...     ordering_widget = HiddenInput

    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)

``get_ordering_widget``
^^^^^^^^^^^^^^^^^^^^^^^

.. method:: BaseFormSet.get_ordering_widget()

Override ``get_ordering_widget()`` if you need to provide a widget instance for
use with ``can_order``::

    >>> from django.forms import BaseFormSet, formset_factory
    >>> from myapp.forms import ArticleForm
    >>> class BaseArticleFormSet(BaseFormSet):
    ...     def get_ordering_widget(self):
    ...         return HiddenInput(attrs={'class': 'ordering'})

    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)

``can_delete``
--------------

.. attribute:: BaseFormSet.can_delete

Default: ``False``

Lets you create a formset with the ability to select forms for deletion::

    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
    >>> formset = ArticleFormSet(initial=[
    ...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
    ...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
    ... ])
    >>> for form in formset:
    ...     print(form.as_table())
    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
    <tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></td></tr>
    <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
    <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
    <tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></td></tr>
    <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
    <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
    <tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></td></tr>

Similar to ``can_order`` this adds a new field to each form named ``DELETE``
and is a ``forms.BooleanField``. When data comes through marking any of the
delete fields you can access them with ``deleted_forms``::

    >>> data = {
    ...     'form-TOTAL_FORMS': '3',
    ...     'form-INITIAL_FORMS': '2',
    ...     'form-0-title': 'Article #1',
    ...     'form-0-pub_date': '2008-05-10',
    ...     'form-0-DELETE': 'on',
    ...     'form-1-title': 'Article #2',
    ...     'form-1-pub_date': '2008-05-11',
    ...     'form-1-DELETE': '',
    ...     'form-2-title': '',
    ...     'form-2-pub_date': '',
    ...     'form-2-DELETE': '',
    ... }

    >>> formset = ArticleFormSet(data, initial=[
    ...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
    ...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
    ... ])
    >>> [form.cleaned_data for form in formset.deleted_forms]
    [{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': 'Article #1'}]

If you are using a :class:`ModelFormSet<django.forms.models.BaseModelFormSet>`,
model instances for deleted forms will be deleted when you call
``formset.save()``.

If you call ``formset.save(commit=False)``, objects will not be deleted
automatically.  You'll need to call ``delete()`` on each of the
:attr:`formset.deleted_objects
<django.forms.models.BaseModelFormSet.deleted_objects>` to actually delete
them::

    >>> instances = formset.save(commit=False)
    >>> for obj in formset.deleted_objects:
    ...     obj.delete()

On the other hand, if you are using a plain ``FormSet``, it's up to you to
handle ``formset.deleted_forms``, perhaps in your formset's ``save()`` method,
as there's no general notion of what it means to delete a form.

:class:`~django.forms.formsets.BaseFormSet` also provides a
:attr:`~django.forms.formsets.BaseFormSet.deletion_widget` attribute and
:meth:`~django.forms.formsets.BaseFormSet.get_deletion_widget` method that
control the widget used with
:attr:`~django.forms.formsets.BaseFormSet.can_delete`.

``deletion_widget``
^^^^^^^^^^^^^^^^^^^

.. versionadded:: 4.0

.. attribute:: BaseFormSet.deletion_widget

Default: :class:`~django.forms.CheckboxInput`

Set ``deletion_widget`` to specify the widget class to be used with
``can_delete``::

    >>> from django.forms import BaseFormSet, formset_factory
    >>> from myapp.forms import ArticleForm
    >>> class BaseArticleFormSet(BaseFormSet):
    ...     deletion_widget = HiddenInput

    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_delete=True)

``get_deletion_widget``
^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 4.0

.. method:: BaseFormSet.get_deletion_widget()

Override ``get_deletion_widget()`` if you need to provide a widget instance for
use with ``can_delete``::

    >>> from django.forms import BaseFormSet, formset_factory
    >>> from myapp.forms import ArticleForm
    >>> class BaseArticleFormSet(BaseFormSet):
    ...     def get_deletion_widget(self):
    ...         return HiddenInput(attrs={'class': 'deletion'})

    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_delete=True)

``can_delete_extra``
--------------------

.. versionadded:: 3.2

.. attribute:: BaseFormSet.can_delete_extra

Default: ``True``

While setting ``can_delete=True``, specifying ``can_delete_extra=False`` will
remove the option to delete extra forms.

Adding additional fields to a formset
=====================================

If you need to add additional fields to the formset this can be easily
accomplished. The formset base class provides an ``add_fields`` method. You
can override this method to add your own fields or even redefine the default
fields/attributes of the order and deletion fields::

    >>> from django.forms import BaseFormSet
    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm
    >>> class BaseArticleFormSet(BaseFormSet):
    ...     def add_fields(self, form, index):
    ...         super().add_fields(form, index)
    ...         form.fields["my_field"] = forms.CharField()

    >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
    >>> formset = ArticleFormSet()
    >>> for form in formset:
    ...     print(form.as_table())
    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
    <tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field"></td></tr>

.. _custom-formset-form-kwargs:

Passing custom parameters to formset forms
==========================================

Sometimes your form class takes custom parameters, like ``MyArticleForm``.
You can pass this parameter when instantiating the formset::

    >>> from django.forms import BaseFormSet
    >>> from django.forms import formset_factory
    >>> from myapp.forms import ArticleForm

    >>> class MyArticleForm(ArticleForm):
    ...     def __init__(self, *args, user, **kwargs):
    ...         self.user = user
    ...         super().__init__(*args, **kwargs)

    >>> ArticleFormSet = formset_factory(MyArticleForm)
    >>> formset = ArticleFormSet(form_kwargs={'user': request.user})

The ``form_kwargs`` may also depend on the specific form instance. The formset
base class provides a ``get_form_kwargs`` method. The method takes a single
argument - the index of the form in the formset. The index is ``None`` for the
:ref:`empty_form`::

    >>> from django.forms import BaseFormSet
    >>> from django.forms import formset_factory

    >>> class BaseArticleFormSet(BaseFormSet):
    ...     def get_form_kwargs(self, index):
    ...         kwargs = super().get_form_kwargs(index)
    ...         kwargs['custom_kwarg'] = index
    ...         return kwargs

.. _formset-prefix:

Customizing a formset's prefix
==============================

In the rendered HTML, formsets include a prefix on each field's name. By
default, the prefix is ``'form'``, but it can be customized using the formset's
``prefix`` argument.

For example, in the default case, you might see:

.. code-block:: html

    <label for="id_form-0-title">Title:</label>
    <input type="text" name="form-0-title" id="id_form-0-title">

But with ``ArticleFormset(prefix='article')`` that becomes:

.. code-block:: html

    <label for="id_article-0-title">Title:</label>
    <input type="text" name="article-0-title" id="id_article-0-title">

This is useful if you want to :ref:`use more than one formset in a view
<multiple-formsets-in-view>`.

Using a formset in views and templates
======================================

Using a formset inside a view is not very different from using a regular
``Form`` class. The only thing you will want to be aware of is making sure to
use the management form inside the template. Let's look at a sample view::

    from django.forms import formset_factory
    from django.shortcuts import render
    from myapp.forms import ArticleForm

    def manage_articles(request):
        ArticleFormSet = formset_factory(ArticleForm)
        if request.method == 'POST':
            formset = ArticleFormSet(request.POST, request.FILES)
            if formset.is_valid():
                # do something with the formset.cleaned_data
                pass
        else:
            formset = ArticleFormSet()
        return render(request, 'manage_articles.html', {'formset': formset})

The ``manage_articles.html`` template might look like this:

.. code-block:: html+django

    <form method="post">
        {{ formset.management_form }}
        <table>
            {% for form in formset %}
            {{ form }}
            {% endfor %}
        </table>
    </form>

However there's a slight shortcut for the above by letting the formset itself
deal with the management form:

.. code-block:: html+django

    <form method="post">
        <table>
            {{ formset }}
        </table>
    </form>

The above ends up calling the ``as_table`` method on the formset class.

.. _manually-rendered-can-delete-and-can-order:

Manually rendered ``can_delete`` and ``can_order``
--------------------------------------------------

If you manually render fields in the template, you can render
``can_delete`` parameter with ``{{ form.DELETE }}``:

.. code-block:: html+django

    <form method="post">
        {{ formset.management_form }}
        {% for form in formset %}
            <ul>
                <li>{{ form.title }}</li>
                <li>{{ form.pub_date }}</li>
                {% if formset.can_delete %}
                    <li>{{ form.DELETE }}</li>
                {% endif %}
            </ul>
        {% endfor %}
    </form>


Similarly, if the formset has the ability to order (``can_order=True``), it is
possible to render it with ``{{ form.ORDER }}``.

.. _multiple-formsets-in-view:

Using more than one formset in a view
-------------------------------------

You are able to use more than one formset in a view if you like. Formsets
borrow much of its behavior from forms. With that said you are able to use
``prefix`` to prefix formset form field names with a given value to allow
more than one formset to be sent to a view without name clashing. Let's take
a look at how this might be accomplished::

    from django.forms import formset_factory
    from django.shortcuts import render
    from myapp.forms import ArticleForm, BookForm

    def manage_articles(request):
        ArticleFormSet = formset_factory(ArticleForm)
        BookFormSet = formset_factory(BookForm)
        if request.method == 'POST':
            article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
            book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
            if article_formset.is_valid() and book_formset.is_valid():
                # do something with the cleaned_data on the formsets.
                pass
        else:
            article_formset = ArticleFormSet(prefix='articles')
            book_formset = BookFormSet(prefix='books')
        return render(request, 'manage_articles.html', {
            'article_formset': article_formset,
            'book_formset': book_formset,
        })

You would then render the formsets as normal. It is important to point out
that you need to pass ``prefix`` on both the POST and non-POST cases so that
it is rendered and processed correctly.

Each formset's :ref:`prefix <formset-prefix>` replaces the default ``form``
prefix that's added to each field's ``name`` and ``id`` HTML attributes.