Newforms Custom Fields and Validation

Very often on the Django Users list people ask queries on how to add custom validation to newform fields.

To answer this question in two lines:

  • Subclass the field to which you want to add custom validation.
  • Override the clean method to add your custom validation.

To give an idea, I will take an example of creating a Pincode (Indian equivalent of ZipCode) field. Before I start coding let me just give you a brief overview about Pincode so that you can understand the logic behind the validation.

Features of Pincode

  • The pincode is a 6 digit unique number that identifies the nearest post office in the locality.
Pincode Explanation Diagram

(Image taken from Wikipedia. Contents of Wikipedia are licensed under GNU Free Documentation License.)

  • From the first two digits we will be able to identify the destination state.

Motive of creating the Pincode Field

Very often people fill forms online that require address with separate fields for State, City and Pincode. Quite often people goof up the pincode (which is the main criterion for correct delivery of mail). The application too (usually) has no means of validating it. Let's create a PinCode Field which also returns the state name to be checked for consistency with the state name entered in the form.

from django import newforms as forms
from django.http import HttpResponse

class PinCodeField(forms.RegexField):
    def __init__(self,max_length=None,min_length=None,error_message=None, *args, **kwargs):
        super(forms.Regexield,self).__init__('\d{6}',max_length,min_length, *args, **kwargs)

    def clean(self,value):
       import re
       value = super(forms.RegexField, self).clean(value)
       if value == u'':
           return value
       if re.compile('\d{6}').search(value):
           if int(value[:2]) == "11":
              return ("DE",value)
           elif int(value[:2]) in range(30,35):
              return ("RAJ",value)
           elif int(value[:2]) in range(40,45):
              return ("MAH",value)
           elif int(value[:2]) in range(45,50):
              return ("MP",value)
           elif int(value[:2]) in range(50,54):
              return ("AP",value)
           else:
              return None

STATE_NAMES = (
  ("DE","Delhi"),("RAJ","Rajasthan"), ("MAH","Maharashtra"),
  ("MP", "Madhya Pradesh"), ("AP","Andhra Pradesh"),
)
# I have entered only 5 names just to show demo. No prejudices involved here!!!

class AddressForm(forms.Form):
    state_name = forms.ChoiceField(required = True,choices = STATE_NAMES)
    city_name  = forms.CharField(max_length = 50)
    pincode    = PinCodeField()

def index(request):
    f = AddressForm()
    return HttpResponse("<form action='/sub_form/' method='post'>"+\
                      f.as_p()+"<p><input type='submit' /></p></form>")

def sub_form(request):
    f = AddressForm(request.POST)
    if f.is_valid():
       if f.cleaned_data['pincode']:
           if f.cleaned_data['state_name'] == f.cleaned_data['pincode'][0]:
              return HttpResponse("Data Validated")
           else:
              return HttpResponse("Pin Code not from State")
              # Or raise a validation error
       else:
           return HttpResponse("State not in list.")
           # Or raise a validation error
    else:
       return HttpResponse(f.errors.as_ul())
       # Or raise a validation error

This was a perverted example to just show how a newform field alongwith some custom validation be created.

Note

Django provides a field for almost every type of situation. Create a custom field only when you believe you are repeating yourself often.