Spec-Zone.ru › Django 6.0

Наборы форм

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=[...]).

См. также

Создание наборов форм на основе моделей с помощью наборов ModelForm.

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

Параметр 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 влияет только на количество отображаемых форм и не влияет на проверку. Если в formset_factory() передан validate_max=True, то 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.

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

Проверка набора форм почти не отличается от проверки обычной Form. У набора форм есть метод 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.']

Пользовательская проверка набора форм

У набора форм есть метод 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

Если функции formset_factory() передан validate_max=True, проверка также убедится, что количество форм в наборе данных за вычетом форм, помеченных для удаления, не превышает 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' в аргумент error_messages.

Примечание

Независимо от validate_max, если количество форм в наборе данных превышает absolute_max, проверка формы завершится неудачей так, как если бы было задано validate_max; кроме того, будут проверены только первые absolute_max форм. Остальные формы будут полностью отброшены. Это защищает от атак, приводящих к исчерпанию памяти, с использованием поддельных POST-запросов. См. Ограничение максимального количества созданных экземпляров форм.

validate_min

Если функции formset_factory() передан validate_min=True, проверка также убедится, что количество форм в наборе данных за вычетом форм, помеченных для удаления, не меньше 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 и является полем типа 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() [исходный код]

Переопределите 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() [исходный код]

Переопределите 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">

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

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

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

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

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

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

Spec-Zone.ru

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