Multisection Django Bootstrap Forms Using Crispy

I use Bootstrap in most of my projects. It is great for getting my Django projects working on all devices. One potential drawback of Bootstrap for forms is they require a lot of markup. More than I am willing to do manually. This is where django-crispy-forms comes in. Crispy knows how to take a Django form and render it in Bootstrap. So DRY. So crispy!

If you follow all the tutorials, etc… you will cruise along making beautiful web forms that work on the big screen as well as mobile. Until one day, you realize that having one input per row is often a waste of space. If only there were a way to create a single form, with multiple sections, without abandoning crispy. Here is one way to do it.

To keep the Bootstrap goodness, we will use the Bootstrap grid system. Go read those docs and then come back.

It is a form for customer intake. Here is the model.

# models.py
class Intake(TimeStampedModel):
    received_by = models.ForeignKey(ROIUser)
    client_first_name = models.CharField(max_length=256)
    client_last_name = models.CharField(max_length=256)
    notes = models.TextField()
    status = models.IntegerField(choices=[[1, 'Open'], [2, 'Close']])

Below is the Django Crispy form. Note how we incorporate the Bootstrap grid as crispy divs.

# forms.py
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Div

class IntakeForm(forms.ModelForm):
    class Meta:
        model = Intake
        fields = ['received_by', 'client_first_name', 'client_last_name', 'notes', 'status']

    def __init__(self, *args, **kwargs):
        super(IntakeForm, self).__init__(*args, **kwargs)

        self.helper = FormHelper()
        self.helper.form_id = 'id_intake_form'
        self.helper.form_method = 'POST'
        self.helper.form_tag = True
        self.helper.layout = Layout(
            Div(
                Div('received_by', 'client_first_name', 'client_last_name', css_class='col-md-6'),
                Div('status', 'notes', css_class='col-md-6'), css_class='row'
            ),
            Div(
                Div(Submit('save', 'Save'), css_class='col-md-12'), css_class='row'
            )
        )

Here is the Django template, with the form nicely contained in a Bootstrap panel. I should mention that my {%block content %} is surrounded by a <div class=”container”></div>

{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% block title %}Intake{% endblock %}

{% block content %}
    <div class="panel panel-primary">
      <div class="panel-heading">
        <h3 class="panel-title">Client Intake</h3>
      </div>
      <div class="panel-body">
        {% crispy form %}
      </div>
    </div>
{% endblock %}

Here is the result:

Screenshot from 2015-02-14 15:26:44

Here it is on mobile:

Screenshot from 2015-02-14 15:51:43

Pretty cool.