Change <input> Types and Attributes for a Form Used in a CreateView and UpdateView

Nick picture Nick · Sep 8, 2013 · Viewed 7.1k times · Source

I'm experimenting with a Django 1.4.6 project for tracking sales leads. I want this to be mobile-friendly, so I'm using Twitter Bootstrap (still on version 2.3.2) with django-crispy-forms. According to this article, the best way to do e-mail fields is <input type="email" autocapitalize="off" autocorrect="off"> and the best way to do date fields is <input type="date">. None of these attributes are implemented by default with Django and I'm wondering how best to go about implementing them. Here is the relevant code (simplified):

models.py

from django.db import models


class Lead(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField(blank=True, null=True)
    initial_contact_date = models.DateField()

    class Meta:
        ordering = ('name',)

    def __unicode__(self):
        return self.name

views.py

from django.core.urlresolvers import reverse
from django.views.generic import CreateView, ListView, UpdateView
from .models import Lead


class LeadAdd(CreateView):
    model = Lead

    def get_context_data(self, **kwargs):
        context = super(LeadAdd, self).get_context_data(**kwargs)
        context['title'] = 'Add a Lead'
        return context

    def get_success_url(self):
        return reverse('lead_list')


class LeadEdit(LeadAdd, UpdateView):
    def get_context_data(self, **kwargs):
        context = super(LeadEdit, self).get_context_data(**kwargs)
        context['title'] = 'Edit a Lead'
        return context


class LeadList(ListView):
    model = Lead

urls.py

from django.conf.urls import patterns, url
from .views import *


urlpatterns = patterns('',
    url(r'^$', view=LeadList.as_view(), name='lead_list'),
    url(r'^add/$', view=LeadAdd.as_view(), name='lead_add'),
    url(r'^edit/(?P<pk>[\d]+)/$', view=LeadEdit.as_view(), name='lead_edit'),
)

lead_form.html

{% extends 'base.html' %}

{% load crispy_forms_tags %}

{% block content %}
    <div class="page-header">
        <h1>{{ title }}</h1>
    </div>

    <form action="." method="post" class="form-horizontal">
        {% csrf_token %}

        {{ form|crispy}}

        <div class="form-actions">
            <button type="submit" class="btn btn-primary">Submit</button>
        </div>
    </form>
{% endblock content %}

Answer

paperreduction picture paperreduction · Sep 8, 2013

Use a field template to do this (see the docs on Field):

Field('email', template="custom-email.html")

OR create a custom widget. There is an abstract class you can use, but one of the existing predefined widgets should work:

# widgets.py
from django.forms.widgets import TextInput

class EmailInput(TextInput):
    input_type = 'email'

So it might look like this in your view:

class LeadAdd(CreateView):
    model = Lead
    form_class = LeadAddForm
    ...

And then that LeadAddForm class would have your custom widget defined:

from . import widgets

LeadAddForm(forms.Form):
    email = forms.CharField(
        ...
        widget = widgets.EmailInput,
        ...
    )

Or you can set the widget in the init:

class LeadAddForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(LeadAddForm, self).__init__(*args, **kwargs)

        self.fields['email'].widget = widgets.EmailInput()

You should be able to set the extra attributes (autocapitalize="off" autocorrect="off") using the crispy form config:

Field('email', autocapitalize="off", autocorrect="off")