{% crispy %} tag with formsets

{% crispy %} tag supports formsets rendering too, all kind of Django formsets: formsets, modelformsets and inline formsets. In this section, it’s taken for granted that you are familiar with previous explained crispy-forms concepts in the docs, like FormHelper, how to set FormHelper attributes or render a simple form using {% crispy %} tag.

Formsets

It’s not the purpose of this documentation to explain how formsets work in detail, for that you should check Django official formset docs. Let’s start creating a formset using the previous ExampleForm form:

from django.forms.models import formset_factory

ExampleFormSet = formset_factory(ExampleForm, extra=3)
formset = ExampleFormSet()

This is how you would render the formset using default rendering, no layouts or form helpers:

{% crispy formset %}

Of course, you can still use a helper, otherwise there would be nothing crispy in this. When using a FormHelper with a formset compared to when you use it with a form, the main difference is that helper attributes are applied to the form structure, while the layout is applied to the formset’s forms. Let’s create a helper for our ExampleFormSet:

class ExampleFormSetHelper(FormHelper):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.form_method = 'post'
        self.layout = Layout(
            'favorite_color',
            'favorite_food',
        )
        self.render_required_fields = True

This helper is quite easy to follow. We want our form to use POST method, and we want favorite_color to be the first field, then favorite_food and finally we tell crispy to render all required fields after. Let’s go and use it, when using {% crispy %} tag in a template there is one main difference when rendering formsets vs forms, in this case you need to specify the helper explicitly.

This would be part of an hypothetic function view:

formset = ExampleFormSet()
helper = ExampleFormSetHelper()
return render(request, 'template.html', {'formset': formset, 'helper': helper})

Then in template.html you would have to do:

{% crispy formset helper %}

There are two ways you can add submit buttons to a formset. Using FormHelper.add_input method:

helper.add_input(Submit("submit", "Save"))

Or you can set FormHelper.form_tag to False and control the formset outer structure at your will writing boring HTML:

<form action="{% url 'save_formset' %}" method="POST">
    {% crispy formset helper %}
    <div class="form-actions">
        <input type="submit" name="submit" value="Save" class="btn btn-primary" id="submit-save">
    </div>
</form>

Finally, model formsets and inline formsets are rendered exactly the same as formsets, the only difference is how you build them in your Django code.

Extra context

Rendering any kind of formset with crispy injects some extra context in the layout rendering so that you can do things like:

HTML("{% if forloop.first %}Message displayed only in the first form of a formset forms list{% endif %}",
Fieldset("Item {{ forloop.counter }}", 'field-1', [...])

Basically you can access a forloop Django node, as if you were rendering your formsets forms using a for loop.

Custom templates and table inline formsets

Default formset template will render your formset’s form using divs, but many times people prefer tables for formsets. Don’t worry, crispy-forms’s got you covered. FormHelper has an attribute named template that can be used to specify a custom template for rendering a form or formset, in this case a formset. Obviously when we specify a template attribute, we are making that helper only usable with forms or formsets.

The name of the template to use is table_inline_formset.html and you use it doing:

helper.template = 'bootstrap/table_inline_formset.html'

The best part is that if this template doesn’t do exactly what you want, you can copy it into your templates folder, customize it and then link your helper to your alternative version. If you think what you are missing would be valuable to others, then please submit a pull request at github.

Warning

This template doesn’t currently take into account any layout you have specified and only works with bootstrap template pack.

Formset forms with different layouts

By default crispy-forms formset rendering shares the same layout among all formset’s forms. This is the case 99% of the times. But maybe you want to render your formset’s forms using different layouts that you cannot achieve using the extra context injected, for that you will have to create and use a custom template. Most likely you will want to do:

{{ formset.management_form|crispy }}
{% for form in formset %}
    {% crispy form %}
{% endfor %}

Where every form has a helper attribute from which crispy will grab the layout. In your view you will need to change the layout or use a different help for every formset’s form. Make sure that you have form_tag attribute set to False, otherwise you will get 3 individual forms rendered.