Наборы форм
-
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.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 имеет несколько методов, тесно связанных с ManagementForm, total_form_count и initial_form_count.
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 строго, даже если 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.']
Примечание
Независимо от validate_min, если набор форм не содержит данных, то extra + min_num пустых форм будет отображено.
Работа с порядком и удалением форм
Функция 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
-
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-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 для пустой формы:
>>> 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, так и в случае не-POST, чтобы он отображался и обрабатывался правильно.
Префикс каждого набора форм заменяет префикс по умолчанию form , который добавляется к атрибутам HTML каждого поля name и id.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.1/topics/forms/formsets/