Spec-Zone.ru › Django 5.2

Наборы форм

class BaseFormSet [source]

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

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

Валидация набора форм

Валидация набора форм почти идентична обычной валидации. Существует метод formset.full_clean(), который предоставляет удобный способ валидации всех форм в наборе:

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

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

Точно так же, как и при использовании обычной формы, каждое поле в формах набора форм может включать HTML-атрибуты, такие как required для проверки браузером. Однако поля форм наборов форм не будут включать атрибут id, так как эта валидация может быть некорректной при добавлении и удалении форм.

BaseFormSet.total_error_count() [source]

Чтобы проверить, сколько ошибок есть в наборе форм, мы можем использовать метод 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

Понимание формы управления

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

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

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

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

Примечание

Помимо полей initial_form_count и total_form_count, показанных в примерах здесь, форма управления также включает поля order_0 и order_1. Они выводятся вместе с остальной частью формы управления, но только для удобства клиентского кода. Эти поля не являются обязательными и поэтому не показаны в данных примера 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.

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

Настраиваемая валидация набора форм

У набора форм есть метод clean, аналогичный методу класса 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 набора форм вызывается после вызова всех методов Form.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 строго, даже если max_num было превышено, потому что количество начальных данных, предоставленных, было чрезмерным.

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

Примечание

Независимо от 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' аргументу 'too_few_forms'.

Примечание

Независимо от 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 и является forms.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() [source]

Переопределите 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 и являющееся forms.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().

Если вы вызовете 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() вашего набора форм, так как нет общего понятия о том, что значит удалить форму.

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() [source]

Переопределите 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. Метод принимает один аргумент - индекс формы в наборе форм. Индекс равен 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">

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

END_OF_DOCUMENT_MARKER

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

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

BaseFormSet.renderer

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

BaseFormSet.template_name [source]

Имя шаблона, используемого для рендеринга набора форм, если набор форм преобразован в строку, например, через 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() [source]

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

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

  • formset: Экземпляр набора форм.
BaseFormSet.render(template_name=None, context=None, renderer=None)

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

  • template_name: template_name
  • 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.

Использование набора форм внутри представления не сильно отличается от использования обычного класса Form. Единственное, о чем следует помнить, это использование формы управления внутри шаблона. Посмотрим на пример представления:

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, или, более общим образом, переопределяя шаблон по умолчанию.

Вручную рендеренные 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 как в случае POST, так и в других случаях, чтобы он был правильно рендерирован и обработан.

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/topics/forms/formsets/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API