Наборы форм

class BaseFormSet

Набор форм — это абстрактный слой для работы с множеством форм на одной странице. Его можно сравнить с таблицей данных. Скажем у вас есть следующая форма:

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

Вы можете позволить пользователю создавать несколько статей за один раз. Для того, чтобы создать набор форм из ArticleForm вам потребуется:

>>> 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, 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.

К формами внутри набора можно обращаться по индексу. При определении собственного __iter__, вами потребуется также реализовать __getitem__ для сохранения этой возможности.

Использование начальных данных с наборами форм

Возможность назначения начальных данных является одним из ключевых аспектов удобства использования наборов форм. Как было показано выше, вы можете управлять количеством пустых форм, отображаемых набором. Это означает, что вы указываете набору сколько дополнительных пустых форм следует отобразить вместе с формами, которые заполнены начальными данными. Давайте обратимся к следующему примеру:

>>> 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>

Выше показаны три формы. Одна с начальными данными и две пустые. Следует отметить, что в качестве начальных данных мы передали список словарей.

Если вы используете initial при отображении набора форм, вам необходимо передать такой же initial при обработке набора форм, чтобы он мог определить какие формы изменил пользователь. Например, у вас может быть: ArticleFormSet(request.POST, initial=[...]).

Ограничение максимального количества форм

Параметр max_num фабрики formset_factory() предоставляет вам возможность ограничить максимальное количество пустых форм, отображаемых набором:

>>> 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>

Если значение параметра max_num больше количества существующих объектов, то к набору будет добавлено до extra пустых форм. Так будет происходить пока не будет достигнуто значение max_num. Например, если extra=2 и max_num=2, а набор форм был создан с одним initial элементом, то в данном наборе форм будет отображена одна переданная форма и одна пустая.

Если количество инициализированных форм превышает max_num, все они будут показаны, не взирая на значение max_num и никаких дополнительных форм показано не будет. Например, при``extra=3`` и max_num=1 и набором форм, проинициализированным двумя формами, именно эти две формы и будут отображены.

Присвоение свойству max_num значения None (по умолчанию) устанавливает достаточное ограничение на количество отображаемых форм (1000). На практике это эквивалентно отсутствию ограничения.

По умолчанию, max_num просто определяет количество форм без валидации. Если передать validate_max=True в formset_factory(), max_num будет учитываться при валидации. Смотрите validate_max.

Проверка набора форм

Проверка набора форм практически совпадает с аналогичной задачей для обычных форм. Набор форм обладает методом is_valid(), который предоставляет удобный способ выполнить проверку всех форм набора:

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

Мы не передали в набор форм никаких данных, но он успешно прошёл проверку. Набор обладает достаточной логикой, чтобы игнорировать дополнительные незаполненные формы. Но если мы предоставим неправильную статью:

>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
...     '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.']}]

Как можно увидеть, formset.errors является списком значений, которые связаны с формами набора. Проверка была выполнена для обеих форм и в результате было отображено сообщение об ошибке во второй форме.

Как и при использовании обычной Form, каждое поле формы из набора форм может содержать HTML атрибут, такой как maxlength, для валидации в браузере. Однако, поля форм из набора форм не будут содержать атрибут required т.к. валидации может работать неверно при добавлении или удалении форм.

BaseFormSet.total_error_count()

Чтобы определить общее количество ошибок, используйте метод total_error_count:

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

У нас есть возможность проверить изменились ли данные относительно начальных значений (т.е форма была отправлена пустой):

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

Понимание ManagementForm

Вы могли обратить внимание на дополнительные поля (form-TOTAL_FORMS, form-INITIAL_FORMS и form-MAX_NUM_FORMS), которые появлялись при выводе набора форм. Эти данные необходимы для работы формы ManagementForm. Эта форма используется набором для управления своей коллекцией форм. Если эти данные не будут предоставлены набору, то будет вызвано исключение:

>>> data = {
...     'form-0-title': 'Test',
...     'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
Traceback (most recent call last):
...
django.forms.utils.ValidationError: ['ManagementForm data is missing or has been tampered with']

Она используется для отслеживания количества экземпляров отображаемых форм. Если вы добавляете новую форму с помощью JavaScript, вам следует увеличить счётчики в соответствующих полях этой формы. С другой стороны, если вы позволяете удалять существующие объекты через JavaScript, то вы должны обеспечить правильную маркировку этих объектов с помощью form-#-DELETE в данных POST. Ожидается, что все формы присутствуют в данных POST, даже если они «удалены».

Доступ к форме управления возможен через атрибут набора форм. При отображении формы в шаблоне, вы можете включать в страницу все управляющие данные с помощью {{ my_formset.management_form }} (подставьте имя своего набора).

Методы total_form_count и initial_form_count

Класс BaseFormSet имеет ряд методов, которые предназначены для работы с ManagementForm: total_form_count и initial_form_count.

Метод total_form_count возвращает количество форм в наборе. Метод initial_form_count возвращает количество предварительно заполненных форм в наборе, а также используется для определения количества форм, обязательных для заполнения. Скорее всего у вас никогда не возникнет необходимости переопределения этих методов, просто запомните их назначение.

empty_form

Класс BaseFormSet имеет атрибут empty_form, который возвращает экземпляр формы с префиксом __prefix__, что может упростить динамическое создание форм с помощью JavaScript.

Собственная проверка набора форм

Набор форм имеет метод clean, аналогичный методу класса Form. Переопределите этот метод для реализации собственных проверок данных на уровне набора форм:

>>> 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 forms.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-MAX_NUM_FORMS': '',
...     '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.']

Метод clean набора форм вызывается после вызова аналогичного метода всех форм. Ошибки будут найдены с помощью метода non_form_errors() набора форм.

Проверка количества форм

Django предоставляет несколько способов валидации минимального и максимально количества отправленных форм. Если вам нужна более сложная валидация, используйте собственную валидацию для набора форм.

validate_max

Если передать validate_max=True в formset_factory(), при валидации будет проверенно, что общее количество форм, без учета помеченных для удаления, не больше чем 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-MIN_NUM_FORMS': '',
...     'form-MAX_NUM_FORMS': '',
...     '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 1 or fewer forms.']

При validate_max=True будет ошибка даже если количество инициализированных форм превышает max_num

Примечание

Независимо от validate_max, если количество форм превышает max_num на более чем 1000, будет ошибка валидация как при включенном validate_max, и только первые 1000 форм после max_num буду провалидированы. Остальные будут проигнорированы. Это сделано для защит от атаки на память(memory exhaustion attack) через модифицированный POST запрос.

validate_min

Если передать validate_min=True в formset_factory(), при валидации будет проверенно, что общее количество форм, без учета помеченных для удаления, больше или равно 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-MIN_NUM_FORMS': '',
...     'form-MAX_NUM_FORMS': '',
...     '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 3 or more forms.']

Сортировка и удаление форм

formset_factory() принимает два необязательных аргумента: can_order и can_delete, которые добавляют возможность сортировки и удаления форм.

can_order

BaseFormSet.can_order

Значение по умолчанию: False

Позволяет создавать наборы форм с возможностью сортировки:

>>> 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>

Здесь добавляется дополнительное поле к каждой форме. Поле называется ORDER и представлено в виде forms.IntegerField. Для форм, которые были созданы с помощью начальных данных, это поле будет автоматически заполнено их порядковым номером. Давайте рассмотрим, что произойдёт, если пользователь изменит эти значения:

>>> data = {
...     'form-TOTAL_FORMS': '3',
...     'form-INITIAL_FORMS': '2',
...     'form-MAX_NUM_FORMS': '',
...     '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'}

BaseFormSet also provides an ordering_widget attribute and get_ordering_widget() method that control the widget used with can_order.

ordering_widget

New in Django 3.0.
BaseFormSet.ordering_widget

Default: 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

New in Django 3.0.
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

BaseFormSet.can_delete

Значение по умолчанию: False

Позволяет создавать наборы форм с возможностью удаления их элементов:

>>> 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>

Подобно can_order, использование этого аргумента добавляет новое поле DELETE в виде forms.BooleanField. При обработке данных набора форм, вы можете получить доступ ко всем помеченным полям через свойство deleted_forms:

>>> data = {
...     'form-TOTAL_FORMS': '3',
...     'form-INITIAL_FORMS': '2',
...     'form-MAX_NUM_FORMS': '',
...     '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'}]

Если вы используете ModelFormSet, объекты удаленных форм будут удалены при выполнении formset.save().

Если выполнить formset.save(commit=False), объекты не будут удалены автоматически. Вам необходимо вызвать delete() для каждого объекта из formset.deleted_objects:

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

Если же вы используете просто FormSet, можете делать что хотите с formset.deleted_forms, возможно в переопределенном методе save(). Django просто не знает что должно произойти при удалении таких форм.

Добавление дополнительных полей к набору форм

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>

Как передать свои параметры в формы набора форм

Ваша форма может принимать дополнительные параметры, например MyArticleForm. Вы можете передать из при создании набора форм:

>>> 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})

form_kwargs может зависеть от конкретного экземпляра формы. Набор форм предоставляет метод get_form_kwargs. Этот метод принимает единственный аргумент - норме формы в наборе форм. Номер равен None для 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

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:

<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:

<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 use more than one formset in a view.

Использование наборов форм в представлениях и шаблонах

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})

Шаблон manage_articles.html может выглядеть так:

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

Тем не менее, вышеприведённый код можно сократить и позволить набору самостоятельно обеспечивать вывод управляющей формы:

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

Пример завершается вызовом метода as_table набора форм.

Вручную созданные поля can_delete и can_order

Если вы вручную создаёте эти поля в шаблоне, то вы можете отобразить параметр can_delete с помощью {{ form.DELETE }}:

<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>

Аналогично, если набор форм может выполнять сортировку (can_order=True), то соответствующее поле можно вывести с помощью {{ form.ORDER }}.

Использование нескольких наборов форм в представлении

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,
    })

После этого вы можете отображать наборы форм как обычно. Важно помнить, что надо передать префикс набора форм в каждый экземпляр набора, это гарантирует их правильную работу.

Each formset’s prefix replaces the default form prefix that’s added to each field’s name and id HTML attributes.