Django Crispy Forms and Readonly Fields

I am constantly re-inventing the readonly field for crispy forms. Part of the reason for this is I often use TimeStampedModel, and I want to show the timestamps. I also frequently include a “created_by” field. All these fields are automatically set and not meant to be edited by the user.

One solution I have considered is to include the fields, but use the Django Crispy readonly attribute. Such as:

Field('api_token', readonly=True)

I like this solution because it does not require much extra coding and it is in the style of the form. The downside is it does not work well with model select widgets (i.e. created_by). In that case it renders a readonly widget, but it is still a selected, which kind of gives the impression its not readonly. Further, it does not work with TimeStampedModel. As of Django 1.7, forms.ModelForm cannot find the added fields: “created” or “modified”.

I have tried other solutions using the Crispy HTML function to generate all the markup of a pseudo-form field. It works, but feels hacky.

Then a voice of a Stackoverflow guru that has been nagging me for a while, finally made sense. When I was googling this a long time ago, I ended up on SO and this guru commented that making readonly form fields for fields the user never can edited is bad design for a user interface. Wow – that is so true in this case. So my DRY solution was to make a template include for these fields and put it above the form. Here is the include (it’s Bootstrap):

{%if object.created %}
    <div class="timestamp_include row">
        <div class="col-xs-3 col-xs-offset-2"><span>created: </span>{{ obj.created }}</div>
        <div class="col-xs-3"><span>modified: </span>{{ obj.modified }}</div>

        {% if obj.created_by %}
            <div class="col-xs-4"><span>created_by: </span>
                {% if obj.created_by.last_name or obj.created_by.first_name %}
                    {{ obj.created_by.first_name }} {{ obj.created_by.last_name }}
                {% else %}
                    {{ obj.created_by }}
                {% endif %}
            </div>
        {% endif %}
    </div>
{% endif %}

A little CSS and problem solved.

Advertisements

Printing Bootstrap

I love Bootstrap. Mobile is the future. Bootstrap is the means. Unfortunately many of my customers still want to print everything. Turns out Bootstrap is not great in print. If you google around you will see a few suggestions for how to hack CSS to make Bootstrap work in print. I tried them and could not get them to work.

One possible solution would be to use reportlab to generate a PDF and download it like this. I have used reportlab in the past and really liked it. If you want fine-tuned control of the PDF, reportlab is still probably the best solution. But with it’s power comes complexity. It’s overkill for my needs.

If it were not for Bootstrap, I would definitely just go with a CSS for print. But wait! I can still do that. The solution I settled on was to render the Bootstrap HTML into one div and the old-school HTML into another div. Then I used CSS to control which div is displayed:

@media screen {
    .for_print{
        display: none;
    }

    .for_screen{
        display: block;
    }

}

@media print {
    .for_print{
        display: block;
    }

    .for_screen{
        display: none;
    }
}

Currency Inputs Using Bootstrap and Django Crispy Forms

A frequent requirement is to include a ‘$’ on currency inputs. Bootstrap has functionality to do this, but it is not easy to find in the docs. Here is an example:

Screenshot from 2015-03-09 10:12:04

Which is accomplished using:

<form class="form-inline">
  <div class="form-group">
    <label class="sr-only" for="exampleInputAmount">Amount (in dollars)</label>
    <div class="input-group">
      <div class="input-group-addon">$</div>
      <input type="text" class="form-control" id="exampleInputAmount" placeholder="Amount">
      <div class="input-group-addon">.00</div>
    </div>
  </div>
  <button type="submit" class="btn btn-primary">Transfer cash</button>
</form>

Cool… except how to get that markup using crispy? Turns out the creators of crispy have already figure it out. Here is an example:

from crispy_forms.layout import Layout
from crispy_forms.bootstrap import PrependedText

Layout('a field', 'another field', PrependedText('currency field', '$'))

Keepin’ it crispy!

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.