Сгруппированные формы

класс 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 обычной формы . Объект Grouped Forms содержит метод, 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 это список, состоящий из элементов, соответствующих каждой форме группы. Проверка была проведена для каждой из двух форм, и ожидаемое сообщение об ошибке появляется для второй.

Как и при обычном использовании форм, каждое поле сгруппированной формы может включать атрибуты 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 ), которые требовались в данных форм, сгруппированных выше. Эти данные требуются для формы управления. Это используется сгруппированными формами для управления всеми формами, содержащимися в группе. Если вы не предоставите эти данные управления, будет сгенерировано исключение:

>>> data = {
...     'form-0-title': 'Test',
...     'form-0-pub_date': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
Traceback (most recent call last):
...
django.core.exceptions.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 имеет несколько методов, тесно связанных с управлением данными total_form_count и initial_form_count формами ManagementForm .

total_form_count возвращает общее количество форм в группе форм. initial_form_count возвращает количество форм в группе, которые были предварительно заполнены, а также используется для определения количества необходимых форм. Вам, вероятно, никогда не придется перегружать какой-либо из этих методов, но будьте осторожны, чтобы понять, что они делают, если вам нужно.

empty_form

BaseFormSet предоставляет дополнительный атрибут, empty_form который возвращает экземпляр формы с префиксом, __prefix__ чтобы упростить использование динамических форм с JavaScript.

Пользовательская проверка сгруппированных форм

Сгруппированные формы имеют метод, 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-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 , даже если это значение было превышено из-за слишком большого количества исходных данных.

Заметка

Независимо от значения validate_max , если количество форм в наборе данных превышает max_num 1000, проверка формы не выполняется, как если бы она validate_max была установлена; кроме того, max_num будут проверяться только первые 1000 форм, превышающие норму. Все остальное игнорируется. Это защитная мера от атак на заполнение памяти с использованием 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'}

Raw 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

Этот параметр используется для создания сгруппированных форм, где вы можете выбрать формы для удаления:

>>> 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() вашей сгруппированной формы, потому что нет общего представления о том, что влечет за собой удаление формы.

Добавление дополнительных полей в сгруппированные формы

Добавить дополнительные поля в сгруппированные формы очень просто. Базовый класс сгруппированных форм предоставляет метод add_fields . Вы можете переопределить этот метод, чтобы добавить свои собственные поля или даже переопределить поля / атрибуты по умолчанию для полей сортировки и удаления:

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
...     def add_fields(self, form, index):
...         super().add_fields(form, index)
...         form.fields["my_field"] = forms.CharField()

>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field"></td></tr>

Передача пользовательских параметров в формы в наборах форм

Иногда класс формы принимает настраиваемые параметры, например MyArticleForm . Вы можете передать этот параметр при создании экземпляра набора форм:

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm

>>> class MyArticleForm(ArticleForm):
...     def __init__(self, *args, user, **kwargs):
...         self.user = user
...         super().__init__(*args, **kwargs)

>>> ArticleFormSet = formset_factory(MyArticleForm)
>>> formset = ArticleFormSet(form_kwargs={'user': request.user})

Настройка form_kwargs также может зависеть от конкретного экземпляра формы. Базовый класс набора форм предоставляет метод get_form_kwargs . Это принимает единственный параметр, индекс формы в наборе форм. Этот индекс действителен None для пустой формы empty_form :

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory

>>> class BaseArticleFormSet(BaseFormSet):
...     def get_form_kwargs(self, index):
...         kwargs = super().get_form_kwargs(index)
...         kwargs['custom_kwarg'] = index
...         return kwargs

Настройка префикса набора форм

При отрисовке HTML наборы форм добавляют префикс перед именем каждого поля. По умолчанию используется префикс 'form' , но его можно настроить с помощью параметра prefix набора форм.

Например, в случае по умолчанию мы могли бы увидеть:

<label for="id_form-0-title">Title:</label>
<input type="text" name="form-0-title" id="id_form-0-title">

Но вместе с этим ArticleFormset(prefix='article') он станет:

<label for="id_article-0-title">Title:</label>
<input type="text" name="article-0-title" id="id_article-0-title">

Это полезно, если вы хотите использовать более одного набора форм в представлении .

Использование сгруппированных форм в представлениях и шаблонах

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

Результатом приведенного выше кода является то, что класс сгруппированных форм вызовет свой метод as_table .

Ручное отображение 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 или нет), чтобы отображение и обработка данных могли быть выполнены правильно.

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

Copyright ©2020 All rights reserved