Наборы форм
-
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() определяет одну дополнительную форму; следующий пример создаст класс набора форм для отображения двух пустых форм:
>>> 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, вам также необходимо увеличивать счётчики в этой форме. С другой стороны, если вы используете 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 предоставляет несколько способов проверки минимального или максимального количества отправленных форм. Приложения, которым требуется более настраиваемая проверка количества форм, должны использовать пользовательскую проверку formset.
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, если formset не содержит данных, то будут отображаться extra + min_num пустых форм.
Обработка упорядочивания и удаления форм
Метод formset_factory() предоставляет два необязательных параметра can_order и can_delete для помощи в упорядочивании форм в formset и удалении форм из formset.
can_order
-
BaseFormSet.can_order
Значение по умолчанию: False
Позволяет создавать formset с возможностью упорядочивания:
>>> 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
Позволяет создавать formset с возможностью выбора форм для удаления:
>>> 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() вашего formset, так как нет общего понятия о том, что означает удаление формы.
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 уберет возможность удаления дополнительных форм.
Добавление дополнительных полей в formset
Если вам нужно добавить дополнительные поля в formset, это можно легко сделать. Базовый класс 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>
Передача пользовательских параметров в формы formset
Иногда ваш класс формы принимает пользовательские параметры, такие как MyArticleForm. Вы можете передать этот параметр при создании formset:
>>> 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. Метод принимает один аргумент — индекс формы в formset. Индекс равен 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
Настройка префикса formset
В сгенерированном HTML formset включает префикс в имени каждого поля. По умолчанию префикс равен 'form', но его можно настроить, используя аргумент prefix formset.
Например, в стандартном случае вы можете увидеть:
<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">
Это полезно, если вы хотите использовать более одного formset в представлении.
Использование formset в представлениях и шаблонах
Formset имеет пять атрибутов и пять методов, связанных с рендерингом.
-
BaseFormSet.renderer -
Новое в Django 4.0.
Указывает рендерер, который нужно использовать для formset. По умолчанию используется рендерер, указанный в настройке
FORM_RENDERER.
-
BaseFormSet.template_name -
Новое в Django 4.0.
Имя шаблона, используемого при вызове
__str__илиrender(). Этот шаблон отображает форму управления набором форм, а затем каждую форму в наборе форм в соответствии с шаблоном, определённым атрибутом формыtemplate_name. По умолчанию это прокси дляas_table.
-
BaseFormSet.template_name_p -
Новое в Django 4.0.
Имя шаблона, используемого при вызове
as_p(). По умолчанию это'django/forms/formsets/p.html'. Этот шаблон отображает форму управления набором форм, а затем каждую форму в наборе форм в соответствии с методом формыas_p().
-
BaseFormSet.template_name_table -
Новое в Django 4.0.
Имя шаблона, используемого при вызове
as_table(). По умолчанию это'django/forms/formsets/table.html'. Этот шаблон отображает форму управления набором форм, а затем каждую форму в наборе форм в соответствии с методом формыas_table().
-
BaseFormSet.template_name_ul -
Новое в Django 4.0.
Имя шаблона, используемого при вызове
as_ul(). По умолчанию это'django/forms/formsets/ul.html'. Этот шаблон отображает форму управления набором форм, а затем каждую форму в наборе форм в соответствии с методом формыas_ul().
-
BaseFormSet.get_context() -
Новое в Django 4.0.
Возвращает контекст для отображения набора форм в шаблоне.
Доступные контексты:
-
formset: Экземпляр набора форм.
-
-
BaseFormSet.render(template_name=None, context=None, renderer=None) -
Новое в 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() класса набора форм. Это отображает набор форм с использованием шаблона, указанного в атрибуте 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 HTML-полей.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.0/topics/forms/formsets/