• Техногрет
  • Вложенные формы в Джанго

    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>

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