Number Inputs using Django Crispy with Parsley

Sometimes I use Parsley for client-side validation. When I need the user to enter an amount of money (e.g. allow 2 decimal places), I use a Django DecimalField. When used with Crispy,  the field is rendered with type=”number” and step=0.01. All just as I want.

On my desktop, the input simply does not accept invalid characters. The problem is on the iPhone. It accepts the invalid characters. On submit, Parsley removes them and the decides that the field is empty and gives the error message “This field is required”. I turns out that the easy way to fix this is to change the Parsley error message. Here is how:

class AmountForm(forms.Form):
    description = forms.CharField(max_length=256)
    amount = forms.DecimalField(decimal_places=2)

    def __init__(self, *args, **kwargs):
        super(AmountForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Field('description', required=True),
            Field('amount', required=True, 
                  data_parsley_required_message="Please enter a valid number"),
            Submit('submit', 'Submit'),
            Button('cancel', 'Cancel')

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 %}
        {% endif %}
{% endif %}

A little CSS and problem solved.

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>
  <button type="submit" class="btn btn-primary">Transfer cash</button>

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.

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.

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('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(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 class="panel-body">
        {% crispy form %}
{% 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.

Django Datetime Widget

Usually I use datetimes as timestamps that are automatically set. Thus I do not need a datetime widget. But in my current project I needed a datetime widget that was user friendly. There are a few options. Which one was best?

The answer depends on what dependencies your project already has. For example, django-bootstrap3-datetimepicker seems pretty awesome. But I had trouble getting it to work with my project. I suspect part of the problem is I am using django-crispy (I am not sure this is the problem). Anyway, I am pretty comfortable with javascript, so I thought I would just download the javascript this app uses and wire it up my self with javascript. Then I found that I needed to download even more dependencies. So I decide to look around for something else.

That’s when I rediscovered a module by Trent Richardson (jquery-ui-timepicker-addon). I have used some of his modules in the past and had good luck. I am already using jquery. This module will not interfere with django-crispy. Turns out it worked great!