Наборы форм

класс 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() дает возможность ограничить количество форм formset будет отображаться:

>>> 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 + 1000max_numNone2000

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

>>> 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() метода в наборе форм.

Проверка количества форм в наборе форм

Django предоставляет несколько способов проверить минимальное или максимальное количество отправленных форм. Приложения, которым требуется более настраиваемая проверка количества форм, должны использовать настраиваемую проверку набора форм.

validate_max

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

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test 2',
...     'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit at most 1 form.']

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

Примечание

Независимо от того validate_max, если количество форм в наборе данных превышает absolute_max, тогда форма не сможет пройти проверку, как если бы она validate_maxбыла установлена, и, кроме того, absolute_maxбудут проверены только первые формы. Остаток будет полностью усечен. Это сделано для защиты от атак нехватки памяти с использованием поддельных запросов POST. См. Раздел Ограничение максимального количества экземпляров форм .

validate_min

Если validate_min=Trueпередано formset_factory(), проверка также проверит, что количество форм в наборе данных, за вычетом помеченных для удаления, больше или равно min_num.

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-0-title': 'Test',
...     'form-0-pub_date': '1904-06-16',
...     'form-1-title': 'Test 2',
...     'form-1-pub_date': '1912-06-23',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit at least 3 forms.']

Примечание

Независимо от того validate_min, если набор форм не содержит данных, будут отображаться пустые формы.extra + min_num

Работа с заказом и удалением форм

formset_factory()Обеспечивает два дополнительных параметров can_orderи can_deleteпомочь с упорядочением форм в FormSets и удаление форм из formset.

can_order

BaseFormSet.can_order

По умолчанию: False

Позволяет создать набор форм с возможностью заказа:

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></td></tr>

Это добавляет дополнительное поле к каждой форме. Это новое поле называется ORDER и является forms.IntegerField. Формам, полученным из исходных данных, автоматически присваивается числовое значение. Давайте посмотрим, что произойдет, когда пользователь изменит эти значения:

>>> data = {
...     'form-TOTAL_FORMS': '3',
...     'form-INITIAL_FORMS': '2',
...     'form-0-title': 'Article #1',
...     'form-0-pub_date': '2008-05-10',
...     'form-0-ORDER': '2',
...     'form-1-title': 'Article #2',
...     'form-1-pub_date': '2008-05-11',
...     'form-1-ORDER': '1',
...     'form-2-title': 'Article #3',
...     'form-2-pub_date': '2008-05-01',
...     'form-2-ORDER': '0',
... }

>>> formset = ArticleFormSet(data, initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> formset.is_valid()
True
>>> for form in formset.ordered_forms:
...     print(form.cleaned_data)
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': 'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}

BaseFormSetтакже предоставляет ordering_widgetатрибут и get_ordering_widget()метод, которые управляют используемым виджетом can_order.

ordering_widget

BaseFormSet.ordering_widget

По умолчанию: NumberInput

Установите, ordering_widgetчтобы указать класс виджета, который будет использоваться с can_order:

>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
...     ordering_widget = HiddenInput

>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)

get_ordering_widget

BaseFormSet.get_ordering_widget()

Переопределите, get_ordering_widget()если вам нужно предоставить экземпляр виджета для использования с can_order:

>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
...     def get_ordering_widget(self):
...         return HiddenInput(attrs={'class': 'ordering'})

>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)

can_delete

BaseFormSet.can_delete

По умолчанию: False

Позволяет создать набор форм с возможностью выбора форм для удаления:

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></td></tr>

Аналогично can_orderэто добавляет новое поле в каждой названной форме DELETE и является forms.BooleanField. Когда данные поступают через пометку любого из полей удаления, вы можете получить к ним доступ с помощью deleted_forms:

>>> data = {
...     'form-TOTAL_FORMS': '3',
...     'form-INITIAL_FORMS': '2',
...     'form-0-title': 'Article #1',
...     'form-0-pub_date': '2008-05-10',
...     'form-0-DELETE': 'on',
...     'form-1-title': 'Article #2',
...     'form-1-pub_date': '2008-05-11',
...     'form-1-DELETE': '',
...     'form-2-title': '',
...     'form-2-pub_date': '',
...     'form-2-DELETE': '',
... }

>>> formset = ArticleFormSet(data, initial=[
...     {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
...     {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': 'Article #1'}]

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

can_delete_extra

Новое в Django 3.2.
BaseFormSet.can_delete_extra

По умолчанию: True

При настройке can_delete=Trueуказание can_delete_extra=Falseудалит возможность удаления лишних форм.

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

Если вам нужно добавить дополнительные поля в набор форм, это легко сделать. Базовый класс набора форм предоставляет 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>

Точно так же, если набор форм имеет возможность order ( can_order=True), его можно отобразить с помощью .{{ form.ORDER }}

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

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

from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm, BookForm

def manage_articles(request):
    ArticleFormSet = formset_factory(ArticleForm)
    BookFormSet = formset_factory(BookForm)
    if request.method == 'POST':
        article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles')
        book_formset = BookFormSet(request.POST, request.FILES, prefix='books')
        if article_formset.is_valid() and book_formset.is_valid():
            # do something with the cleaned_data on the formsets.
            pass
    else:
        article_formset = ArticleFormSet(prefix='articles')
        book_formset = BookFormSet(prefix='books')
    return render(request, 'manage_articles.html', {
        'article_formset': article_formset,
        'book_formset': book_formset,
    })

Затем вы должны отрендерить наборы форм как обычно. Важно отметить, что вам необходимо передавать prefixкак POST, так и не-POST случаи, чтобы они отображались и обрабатывались правильно.

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

Copyright ©2021 All rights reserved