• 3.1
  • 3.2
  • 5.0
  • Версия документации: 6.1

Наборы форм

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)

Теперь вы создали класс набора форм с именем «ArticleFormSet». Создание экземпляра набора форм дает вам возможность перебирать формы в наборе форм и отображать их так же, как и в обычной форме:

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

Как видите, отображается только одна пустая форма. Количество отображаемых пустых форм контролируется параметром extra. По умолчанию formset_factory() определяет одну дополнительную форму; в следующем примере будет создан класс набора форм для отображения двух пустых форм:

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

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

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

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

>>> 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)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2023-02-11" id="id_form-0-pub_date"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>

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

Если вы используете 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)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>

Если значение параметра 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.

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

Параметр absolute_max в formset_factory() позволяет ограничить количество форм, экземпляры которых могут быть созданы при передаче данных POST. Это защищает от атак на исчерпание памяти с использованием поддельных запросов POST:

>>> 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.']

Если для absolute_max установлено значение None, по умолчанию оно равно max_num + 1000. (Если max_num имеет значение None, по умолчанию оно равно 2000).

Если absolute_max меньше max_num, будет выдано ValueError.

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

Проверка с помощью набора форм практически идентична обычной «Форме». В наборе форм есть метод 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",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
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",
...     "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-0-title": "",
...     "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
False

Понимание ManagementForm

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

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

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

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

Примечание

Помимо полей form-TOTAL_FORMS и form-INITIAL_FORMS, показанных в примерах здесь, форма управления также включает поля form-MIN_NUM_FORMS и form-MAX_NUM_FORMS. Они выводятся вместе с остальной формой управления, но только для удобства клиентского кода. Эти поля не являются обязательными и поэтому не отображаются в примере данных POST.

Методы 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.

error_messages

Аргумент error_messages позволяет вам переопределить сообщения по умолчанию, которые будет выдавать набор форм. Передайте словарь с ключами, соответствующими сообщениям об ошибках, которые вы хотите переопределить. Ключи сообщений об ошибках включают 'too_few_forms', 'too_many_forms' и 'missing_management_form'. Сообщения об ошибках Too_few_forms и Too_many_forms могут содержать %(num)d, которые будут заменены на min_num и max_num соответственно.

Например, вот сообщение об ошибке по умолчанию, когда форма управления отсутствует:

>>> 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.']

А вот специальное сообщение об ошибке:

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

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

Набор форм имеет чистый метод, аналогичный методу класса Form. Здесь вы определяете свою собственную проверку, которая работает на уровне набора форм:

>>> 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 = set()
...         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.add(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.']

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

Ошибки, не относящиеся к форме, будут отображаться с помощью дополнительного класса «nonform», чтобы помочь отличить их от ошибок, связанных с формой. Например, {{ formset.non_form_errors }} будет выглядеть так:

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

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

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-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 будет ошибка даже если количество инициализированных форм превышает max_num

Сообщение об ошибке можно настроить, передав сообщение Too_many_forms в аргумент error_messages.

Примечание

Независимо от validate_max, если количество форм в наборе данных превышает absolute_max, форма не сможет быть проверена, как если бы был установлен validate_max, и, кроме того, будут проверены только первые формы absolute_max. Остальное будет полностью урезано. Это сделано для защиты от атак на исчерпание памяти с использованием поддельных запросов 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-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.']

Сообщение об ошибке можно настроить, передав сообщение Too_few_forms в аргумент error_messages.

Примечание

Независимо от validate_min, если набор форм не содержит данных, то будут отображаться пустые формы extra + min_num.

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

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)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-ORDER">Order:</label><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
<div><label for="id_form-1-ORDER">Order:</label><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
<div><label for="id_form-2-ORDER">Order:</label><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></div>

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

>>> 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)},
...     ],
... )
>>> for form in formset.ordered_forms:
...     print(form.cleaned_data)
...
{'title': 'Article #3', 'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0}
{'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1}
{'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2}

BaseFormSet также предоставляет атрибут ordering_widget и метод get_ordering_widget(), которые управляют виджетом, используемым с can_order.

ordering_widget

BaseFormSet.ordering_widget

По умолчанию: NumberInput

Установите ordering_widget, чтобы указать класс виджета, который будет использоваться с 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

BaseFormSet.get_ordering_widget()

Переопределите get_ordering_widget(), если вам нужно предоставить экземпляр виджета для использования с 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)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-DELETE">Delete:</label><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
<div><label for="id_form-1-DELETE">Delete:</label><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
<div><label for="id_form-2-DELETE">Delete:</label><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></div>

Подобно can_order, это добавляет новое поле в каждую форму с именем DELETE и представляет собой form.BooleanField. Когда данные поступают через маркировку любого из полей удаления, вы можете получить к ним доступ с помощью 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]
[{'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10), 'DELETE': True}]

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

If you call formset.save(commit=False), objects will not be deleted automatically. Вам нужно будет вызвать delete() для каждого из formset.deleted_objects, чтобы фактически удалить их:

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

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

BaseFormSet также предоставляет атрибут deletion_widget и метод get_deletion_widget(), которые управляют виджетом, используемым с can_delete.

deletion_widget

BaseFormSet.deletion_widget

По умолчанию: CheckboxInput

Установите deletion_widget, чтобы указать класс виджета, который будет использоваться с 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

BaseFormSet.get_deletion_widget()

Переопределите get_deletion_widget(), если вам нужно предоставить экземпляр виджета для использования с 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

BaseFormSet.can_delete_extra

По умолчанию: True

При настройке can_delete=True, указание can_delete_extra=False приведет к удалению возможности удаления дополнительных форм.

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

Если вам нужно добавить дополнительные поля в набор форм, это можно легко сделать. Базовый класс набора форм предоставляет метод add_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)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-my_field">My field:</label><input type="text" name="form-0-my_field" id="id_form-0-my_field"></div>

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

Иногда ваш класс формы принимает пользовательские параметры, например 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. Метод принимает единственный аргумент — индекс формы в наборе форм. Для empty_form` индекс равен None:

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

>>> ArticleFormSet = formset_factory(MyArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()

Настройка префикса набора форм

В отображаемом HTML наборы форм включают префикс в имени каждого поля. По умолчанию префиксом является «form», но его можно настроить с помощью аргумента «prefix» набора форм.

Например, в случае по умолчанию вы можете увидеть:

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

Но с ArticleFormset(prefix='article') это становится:

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

Это полезно, если вы хотите использовать более одного набора форм в представлении.

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

Наборы форм имеют следующие атрибуты и методы, связанные с рендерингом:

BaseFormSet.renderer

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

BaseFormSet.template_name

Имя отображаемого шаблона, если набор форм преобразован в строку, например через print(formset) или в шаблоне через {{ formset }}.

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

Этот шаблон будет использоваться для отображения формы управления набором форм, а затем каждой формы в наборе форм в соответствии с шаблоном, определенным template_name формы.

BaseFormSet.template_name_div

Имя шаблона, используемого при вызове as_div(). По умолчанию это "django/forms/formsets/div.html". Этот шаблон отображает форму управления набором форм, а затем каждую форму в наборе форм в соответствии с методом as_div() формы.

BaseFormSet.template_name_p

Имя шаблона, используемого при вызове as_p(). По умолчанию это "django/forms/formsets/p.html". Этот шаблон отображает форму управления набором форм, а затем каждую форму в наборе форм в соответствии с методом as_p() формы.

BaseFormSet.template_name_table

Имя шаблона, используемого при вызове as_table(). По умолчанию это "django/forms/formsets/table.html". Этот шаблон отображает форму управления набором форм, а затем каждую форму в наборе форм в соответствии с методом as_table() формы.

BaseFormSet.template_name_ul

Имя шаблона, используемого при вызове as_ul(). По умолчанию это "django/forms/formsets/ul.html". Этот шаблон отображает форму управления набором форм, а затем каждую форму в наборе форм в соответствии с методом as_ul() формы.

BaseFormSet.get_context()

Возвращает контекст для отрисовки набора форм в шаблоне.

Доступный контекст:

  • formset: Экземпляр набора форм.

BaseFormSet.render(template_name=None, context=None, renderer=None)

Метод рендеринга вызывается __str__, а также методами as_div(), as_p(), as_ul() и as_table(). Все аргументы являются необязательными и по умолчанию имеют значения:

  • имя_шаблона: имя_шаблона

  • context: значение, возвращаемое get_context()

  • renderer: значение, возвращаемое renderer

BaseFormSet.as_div()

Отображает набор форм с помощью шаблона template_name_div.

BaseFormSet.as_p()

Отрисовывает набор форм с помощью шаблона template_name_p.

BaseFormSet.as_table()

Отрисовывает набор форм с помощью шаблона template_name_table.

BaseFormSet.as_ul()

Отрисовывает набор форм с помощью шаблона template_name_ul.

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

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>

Вышеупомянутое заканчивается вызовом метода BaseFormSet.render() в классе набора форм. При этом набор форм визуализируется с использованием шаблона, указанного атрибутом template_name. Подобно формам, по умолчанию набор форм будет отображаться как as_div, при этом доступны другие вспомогательные методы as_p, as_ul и as_table. Отображение набора форм можно настроить, указав атрибут template_name или, в более общем смысле, переопределив шаблон по умолчанию <overriding-built-in-formset-templates>`.

Вручную созданные поля 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 }}.

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

Можно использовать несколько наборов форм в представлении. Наборы форм очень похожи своим поведением на обычные формы. То есть, можно использовать prefix для их различения при использовании нескольких наборов форм. Давайте рассмотрим пример такой реализации:

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

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

prefix каждого набора форм заменяет префикс form по умолчанию, который добавляется к HTML-атрибутам name и id каждого поля.

Back to Top