Наборы форм¶
- 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 каждого поля.