HTML and CSS   XSLT   JavaScript   Images   Soft   Etc  
Gregory Zhizhilkin

Inline forms in Django March 13, 2010


Task:

to make a convenient form using standard tools.

For the convenience of users we often make complex forms which standard classes may not provide for.

For example, we want to make an HTML form composed of several fields and supplement it with a repeating set of fields.

Note

In Django a form is a set of fields representing data of a single object. HTML form can be created using several Django forms.

Repeating fields are created using the formsets module. Usually you need to describe the main form and the extended form and then display them in a template individually. But say we want to display repeating fields between simple form fields. The problem is that when using standard methods, a form in Django is displayed sequentially and continuously.

Redefining the template manually

To place the repeating fields inside a simple form we can simply break down the form into individual fields in the template, but that conflicts with the DRY principle: we would have to duplicate all the code responsible for error messages, field names and text prompts.

Solution

Let’s create a field and a widget that would display the inline form.

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
class FieldsetWidget(forms.widgets.Widget):
    # Form display widget
    def render(self, name, value, attrs=None):
        return self.attrs['form_html']

class FieldsetField(forms.Field):
    # Form field containing another form
    def __init__(self, fieldset, *args, **kwargs):
        # HTML of the form is passed by a parameter of this widget
        widget = FieldsetWidget(attrs={
            'form_html': '<table>%s</table>' % fieldset.as_table()
        })
        kwargs.update({
            'widget': widget,
            'required': False
        })
        super(FieldsetField, self).__init__(*args, **kwargs)

Usage

We place a field of FieldsetField class where we need the form to contain an inline form:

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):
    # Inline form
    file = forms.FileField(label='file')
    comment = forms.CharField(label='name')

# Standard formset
InlineFormSet = formset_factory(InlineForm, extra=2)
formset = InlineFormSet(post, files, prefix='formset')

class SuperForm(forms.Form):
    #Form with fields and inline form
    name = forms.CharField(label='Name')
    inline_formset = FieldsetField(fieldset=formset, label='Files')
    comment = forms.CharField(label='Comment', widget=forms.Textarea({'rows': 3}))

form = SuperForm(post, files)

Nothing changed in the template:

01 
02 
03 
04 
<form action="." method="post" enctype="multipart/form-data">
    <table class="main_form">{{ form.as_table }}</table>
    <input type="submit" value="Save" />
</form>

Multiplying fields will be covered in the next article.


Order a design...