Spec-Zone.ru › Django 4.0

Наборы форм

class BaseFormSet

Набор форм — это уровень абстракции для работы с несколькими формами на одной странице. Его можно сравнить с таблицей данных. Допустим, у вас есть следующая форма:

>>> from django import forms
>>> class ArticleForm(forms.Form):
...     title = forms.CharField()
...     pub_date = forms.DateField()

Вы можете захотеть позволить пользователю создать несколько статей сразу. Чтобы создать набор форм из ArticleForm, вы бы сделали так:

>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)

Теперь вы создали класс набора форм под названием ArticleFormSet. Создание экземпляра набора форм даёт вам возможность итерироваться по формам в наборе форм и отображать их так же, как вы это делаете с обычной формой:

>>> formset = ArticleFormSet()
>>> for form in formset:
...     print(form.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.

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

Новое в Django 3.2.

Параметр 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.

Изменено в Django 3.2:

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

Новое в Django 3.2.

Аргумент 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>
Изменено в Django 4.0:

Добавлен дополнительный класс 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

Новое в Django 4.0.
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

Новое в Django 4.0.
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

Новое в Django 3.2.
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 или, более общим способом, заменив шаблон по умолчанию.

Изменено в Django 4.0:

Отображение наборов форм было перенесено в движок шаблонов.

Ручное отображение 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/

Spec-Zone.ru

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