Формсеты¶
-
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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
Как вы можете видеть, он отобразил только одну пустую форму. Количество отображаемых пустых форм контролируется параметром extra
. По умолчанию formset_factory()
определяет одну дополнительную форму; в следующем примере будет создан класс formset для отображения двух пустых форм:
>>> 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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
Теперь на экране отображается в общей сложности три формы. Одна для исходных данных, которые были переданы, и две дополнительные формы. Также обратите внимание, что в качестве исходных данных мы передаем список словарей.
Если вы используете 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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
Если значение 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, вам следует увеличить поля count и в этой форме. С другой стороны, если вы используете JavaScript для удаления существующих объектов, то вам необходимо убедиться, что удаляемые объекты должным образом помечены для удаления путем включения form-#-DELETE
в данные POST
. Ожидается, что все формы будут присутствовать в данных POST
независимо от этого.
Форма управления доступна как атрибут самого набора форм. При отображении набора форм в шаблоне вы можете включить все данные управления, отобразив {{ my_formset.management_form }}
(заменив имя вашего набора форм соответствующим образом).
Примечание
Помимо полей form-TOTAL_FORMS
и form-INITIAL_FORMS
, показанных в приведенных здесь примерах, форма управления также включает поля form-MIN_NUM_FORMS
и form-MAX_NUM_FORMS
. Они выводятся вместе с остальной частью формы управления, но только для удобства кода на стороне клиента. Эти поля не являются обязательными и поэтому не показаны в примере POST
данных.
formset.is_valid()
теперь возвращает False
, а не вызывает исключение, когда форма управления отсутствует или была испорчена.
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
позволяет вам переопределить сообщения по умолчанию, которые будет выдавать набор форм. Передайте словарь с ключами, соответствующими сообщениям об ошибках, которые вы хотите переопределить. Например, вот сообщение об ошибке по умолчанию, когда отсутствует форма управления:
>>> 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 = []
... 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.append(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>
Был добавлен дополнительный класс nonform
.
Проверка количества форм в наборе форм¶
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
был превышен из-за чрезмерного количества предоставленных начальных данных.
Примечание
Независимо от 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.']
Примечание
Независимо от 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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></td></tr>
Это добавляет дополнительное поле в каждую форму. Это новое поле называется 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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></td></tr>
Аналогично 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)
Добавление дополнительных полей в набор форм¶
Если вам нужно добавить дополнительные поля в набор форм, это можно легко сделать. Базовый класс formset предоставляет метод 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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field"></td></tr>
Передача пользовательских параметров формам набора форм¶
Иногда класс вашей формы принимает пользовательские параметры, например 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
может также зависеть от конкретного экземпляра формы. Базовый класс formset предоставляет метод get_form_kwargs
. Метод принимает единственный аргумент - индекс формы в наборе форм. Индекс является None
для empty_form:
>>> 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
Настройка префикса набора форм¶
В отображаемом 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">
Это полезно, если вы хотите use more than one formset in a view.
Использование набора форм в представлениях и шаблонах¶
Наборы форм имеют пять атрибутов и пять методов, связанных с рендерингом.
-
BaseFormSet.
renderer
¶ - New in Django 4.0.
Определяет renderer, который будет использоваться для набора форм. По умолчанию используется рендерер, заданный параметром
FORM_RENDERER
.
-
BaseFormSet.
template_name
¶ - New in Django 4.0.
Имя шаблона, используемого при вызове
__str__
илиrender()
. Этот шаблон отображает управляющую форму набора форм, а затем каждую форму в наборе форм в соответствии с шаблоном, определенным вtemplate_name
. По умолчанию это проксиas_table
.
-
BaseFormSet.
template_name_p
¶ - New in Django 4.0.
Имя шаблона, используемого при вызове
as_p()
. По умолчанию это'django/forms/formsets/p.html'
. Этот шаблон отображает управляющую форму набора форм, а затем каждую форму в наборе форм в соответствии с методом формыas_p()
.
-
BaseFormSet.
template_name_table
¶ - New in Django 4.0.
Имя шаблона, используемого при вызове
as_table()
. По умолчанию это'django/forms/formsets/table.html'
. Этот шаблон отображает управляющую форму набора форм, а затем каждую форму в наборе форм в соответствии с методом формыas_table()
.
-
BaseFormSet.
template_name_ul
¶ - New in Django 4.0.
Имя шаблона, используемого при вызове
as_ul()
. По умолчанию это'django/forms/formsets/ul.html'
. Этот шаблон отображает управляющую форму набора форм, а затем каждую форму в наборе форм в соответствии с методом формыas_ul()
.
-
BaseFormSet.
get_context
()[исходный код]¶ - New in Django 4.0.
Возвращает контекст для отображения набора форм в шаблоне.
Доступный контекст:
formset
: Экземпляр набора форм.
-
BaseFormSet.
render
(template_name=None, context=None, renderer=None)¶ - New in Django 4.0.
Метод render вызывается методом
__str__
, а также методамиas_p()
,as_ul()
иas_table()
. Все аргументы являются необязательными и используются по умолчанию:template_name
:template_name
context
: Значение, возвращаемоеget_context()
renderer
: Значение, возвращаемоеrenderer
-
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()
на классе formset. Это рендерит набор форм, используя шаблон, указанный атрибутом template_name
. Как и в случае с формами, по умолчанию набор форм будет отображаться as_table
, при этом доступны другие вспомогательные методы as_p
и as_ul
. Рендеринг набора форм можно настроить, указав атрибут template_name
, или, в более общем случае, overriding the default template.
Рендеринг наборов форм был перенесен в движок шаблонов.
Ручная визуализация 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, так и в случае не-POST, чтобы все было правильно отображено и обработано.
Каждый набор форм prefix заменяет префикс по умолчанию form
, который добавляется к HTML-атрибутам каждого поля name
и id
.