Вложенные формы в Джанго

HTML и CSSXSLTJavaScriptИзображенияСофтEtc
Григорий Жижилкин

13 марта 2010


Задача.

Сделать удобную форму стандартными инструментами.

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

Например, мы хотим сделать HTML-форму из нескольких полей и дополнить ее повторяющимся набором полей.

Хозяйке на заметку

В Джанго формой называется набор полей, представляющий данные одного объекта. HTML-форма может строиться из нескольких форм Джанго.

Повторяющиеся поля реализуются при помощи модуля formsets. Обычно нужно описать основную форму и расширенную форму, а затем вывести их в шаблоне по отдельности. Но, допустим, нам хочется вывести повторяющиеся поля между простыми полями формы. Сложность в том, что форма в Джанго стандартными методами выводится последовательно и непрерывно.

Переопределение шаблона вручную

Чтобы поместить повторяющиеся поля внутри простой формы, достаточно разбить форму на поля в шаблоне, но это противоречит принципу DRY: придется продублировать код, выводящий сообщения об ошибках, названия полей и текстовые подсказки.

Решение

Создадим поле и виджет, которые будут выводить вложенную форму:

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
class FieldsetWidget(forms.widgets.Widget):
    # Виджет для вывода формы
    def render(self, name, value, attrs=None):
        return self.attrs['form_html']

class FieldsetField(forms.Field):
    # Поле формы, содержащее другую форму
    def __init__(self, fieldset, *args, **kwargs):
        # Html формы передается параметром этого виджета
        widget = FieldsetWidget(attrs={
            'form_html': '<table>%s</table>' % fieldset.as_table()
        })
        kwargs.update({
            'widget': widget,
            'required': False
        })
        super(FieldsetField, self).__init__(*args, **kwargs)

Использование

В том месте, где форма должна содержать вложенную форму, ставим поле класса FieldsetField:

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
if (request.method == 'POST'):
    post, files = request.POST, request.FILES
else:
    post, files = None, None

class InlineForm(forms.Form):
    # Вложенная форма
    file = forms.FileField(label='файл')
    comment = forms.CharField(label='название')

# Стандартный formset
InlineFormSet = formset_factory(InlineForm, extra=2)
formset = InlineFormSet(post, files, prefix='formset')

class SuperForm(forms.Form):
    # Форма с полями и вложенной формой
    name = forms.CharField(label='Имя')
    inline_formset = FieldsetField(fieldset=formset, label='Файлы')
    comment = forms.CharField(label='Комментарий', widget=forms.Textarea({'rows': 3}))

form = SuperForm(post, files)

В шаблоне ничего не изменилось:

01 
02 
03 
04 
<form action="." method="post" enctype="multipart/form-data">
    <table class="main_form">{{ form.as_table }}</table>
    <input type="submit" value="Сохранить" />
</form>

Способы размножения полей будут описаны в следующей статье.