Наборы форм
-
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)
Итерация по набору форм отобразит формы в порядке их создания. Вы можете изменить этот порядок, предоставив альтернативную реализацию метода __iter__().
К наборам форм также можно обращаться по индексу, что возвращает соответствующую форму. Если вы переопределите __iter__, вам также необходимо переопределить __getitem__ для соответствия поведения.
Использование начальных данных с набором форм
Начальные данные — это то, что определяет основную полезность набора форм. Как показано выше, вы можете определить количество дополнительных форм. Это означает, что вы сообщаете набору форм, сколько дополнительных форм отображать дополнительно к количеству форм, сгенерированных из начальных данных. Давайте рассмотрим пример:
>>> import datetime
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(
... initial=[
... {
... "title": "Django is now open source",
... "pub_date": datetime.date.today(),
... }
... ]
... )
>>> for form in formset:
... print(form)
...
<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.
Валидация набора форм
Валидация с набором форм почти идентична обычной 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
Если 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' в аргумент 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 и является 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)},
... ],
... )
>>> formset.is_valid()
True
>>> for form in formset.ordered_forms:
... print(form.cleaned_data)
...
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': 'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}
BaseFormSet также предоставляет атрибут 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]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': 'Article #1'}]
Если вы используете ModelFormSet, экземпляры моделей для удалённых форм будут удалены при вызове formset.save().
Если вы вызываете formset.save(commit=False), объекты не будут удалены автоматически. Вам нужно будет вызвать delete() для каждого из formset.deleted_objects, чтобы действительно их удалить:
>>> instances = formset.save(commit=False) >>> for obj in formset.deleted_objects: ... obj.delete() ...
С другой стороны, если вы используете обычный FormSet, вам нужно будет самим обработать formset.deleted_forms, возможно, в методе save() вашего набора форм, так как нет общего понятия о том, что означает удаление формы.
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 -
Имя шаблона, используемого при преобразовании набора форм в строку, например, с помощью
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) -
Метод 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_table, при этом доступны другие вспомогательные методы as_p и as_ul. Отображение набора форм можно настроить, указав атрибут 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 атрибутам каждого поля.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.0/topics/forms/formsets/