Spec-Zone.ru › Django 3.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.

Валидация наборов форм

Валидация набора форм практически идентична обычной 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',
...     'form-MAX_NUM_FORMS': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
True

Мы не передали данные в набор форм, что приводит к валидной форме. Набор форм достаточно умен, чтобы игнорировать дополнительные формы, которые не были изменены. Если мы предоставим неверную статью:

>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '',
...     '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-MAX_NUM_FORMS': '',
...     'form-0-title': '',
...     'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
False

Понимание ManagementForm

Вы, возможно, заметили дополнительные данные (form-TOTAL_FORMS, form-INITIAL_FORMS и form-MAX_NUM_FORMS), которые были необходимы в данных набора форм выше. Эти данные необходимы для ManagementForm. Эта форма используется набором форм для управления коллекцией форм, содержащихся в наборе форм. Если вы не предоставите эти управляющие данные, будет поднято исключение:

>>> data = {
...     'form-0-title': 'Test',
...     'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
Traceback (most recent call last):
...
django.forms.utils.ValidationError: ['ManagementForm data is missing or has been tampered with']

Она используется для отслеживания количества экземпляров форм, отображаемых. Если вы добавляете новые формы через JavaScript, вы должны также увеличивать счётчики в этой форме. С другой стороны, если вы используете JavaScript для разрешения удаления существующих объектов, то вам необходимо убедиться, что удаляемые объекты должным образом помечены для удаления, включив form-#-DELETE в данные POST. Ожидается, что все формы присутствуют в данных POST.

Форма управления доступна как атрибут самого набора форм. При отрисовке набора форм в шаблоне вы можете включить все управляющие данные, отрисовав {{ my_formset.management_form }} (заменив имя вашего набора форм соответствующим образом).

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.

Настройка валидации наборов форм

В наборе форм есть метод clean аналогичный методу класса Form. Здесь вы определяете свою собственную валидацию, которая работает на уровне набора форм:

>>> 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 forms.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-MAX_NUM_FORMS': '',
...     '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() набора форм.

Валидация количества форм в наборе форм

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-MIN_NUM_FORMS': '',
...     'form-MAX_NUM_FORMS': '',
...     '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 1 or fewer forms.']

validate_max=True проверяет против max_num строго даже если max_num было превышено, потому что количество начальных данных, предоставленных, было избыточным.

Примечание

Независимо от validate_max, если количество форм в наборе данных превышает max_num более чем на 1000, форма не будет валидна, как если бы validate_max было установлено, и дополнительно только первые 1000 форм, превышающих max_num будут проверены. Остальные будут полностью усечены. Это делается для защиты от атак на исчерпание памяти с использованием поддельных запросов 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-MIN_NUM_FORMS': '',
...     'form-MAX_NUM_FORMS': '',
...     '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 3 or more forms.']

Обработка упорядочения и удаления форм

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-MAX_NUM_FORMS': '',
...     '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

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

Новое в Django 3.0.
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-MAX_NUM_FORMS': '',
...     '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, так как нет общепринятого понятия о том, что означает удаление формы.

Добавление дополнительных полей в 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', но его можно настроить, используя аргумент formset 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">

Это полезно, если вы хотите использовать несколько formset в представлении.

Использование formset в представлениях и шаблонах

Использование formset в представлении мало чем отличается от использования обычного класса 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>

Однако есть небольшая альтернатива вышеуказанному, позволяющая formset самому справиться с формой управления:

<form method="post">
    <table>
        {{ formset }}
    </table>
</form>

Это в конечном итоге вызывает метод as_table в классе formset.

Вручную рендер 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>

Аналогично, если у formset есть возможность упорядочивания (can_order=True), то его можно отобразить с {{ form.ORDER }}.

Использование нескольких formset в представлении

Вы можете использовать несколько formset в представлении. Formset заимствуют большую часть своего поведения от форм. С учётом этого, вы можете использовать prefix для добавления префикса к именам полей formset с заданным значением, чтобы передать более одного formset в представление без конфликтов имён. Давайте посмотрим, как это можно сделать:

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,
    })

После этого вы отображаете formset обычным способом. Важно отметить, что вам необходимо передать prefix как в случае POST, так и в случае не-POST, чтобы он отображался и обрабатывался правильно.

Каждый префикс prefix formset заменяет стандартный префикс form , который добавляется к атрибутам HTML name и id каждого поля.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.0/topics/forms/formsets/

Spec-Zone.ru

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