Django Forms: Field Types, Validation, and Rendering Techniques

Manual Registration Flow

views.py

def legacy_signup(request):
    msg = ""
    if request.method == "POST":
        nick = request.POST.get("nick")
        secret = request.POST.get("secret")
        if len(nick) < 6:
            msg = "Nickname must be ≥ 6 chars"
        else:
            # persist user
            return HttpResponse("Account created")
    return render(request, "signup.html", {"msg": msg})

signup.html

<form method="post" action="/signup/">
  {% csrf_token %}
  <p>Nickname: <input name="="nick"></p>
  <p>Password: <input type="password" name="secret"></p>
  <button>Sign up</button>
  <span style="color:red">{{ msg }}</span>
</form>

Switching to Django Forms

forms.py

from django import forms

class SignupForm(forms.Form):
    nick = forms.CharField(label="Nickname")
    secret = forms.CharField(label="Password", widget=forms.PasswordInput)

views.py

def modern_signup(request):
    form = SignupForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        # save user
        return HttpResponse("Account created")
    return render(request, "signup2.html", {"form": form})

signup2.html

<form method="post" novalidate>
  {% csrf_token %}
  <div>
    {{ form.nick.label_tag }} {{ form.nick }} {{ form.nick.errors }}
  </div>
  <div>
    {{ form.secret.label_tag }} {{ form.secret }} {{ form.secret.errors }}
  </div>
  <button>Sign up</button>
</form>

Common Fields & Widgets

class ProfileForm(forms.Form):
    username = forms.CharField(
        min_length=6,
        label="Username",
        initial="guest",
        error_messages={
            "required": "Cannot be blank",
            "min_length": "At least 6 characters"
        }
    )
    password = forms.CharField(
        widget=forms.PasswordInput(attrs={"class": "pwd"})
    )
    gender = forms.ChoiceField(
        choices=((1, "Male"), (2, "Female"), (3, "Other")),
        widget=forms.RadioSelect
    )
    hobbies = forms.MultipleChoiceField(
        choices=((1, "Basketball"), (2, "Football"), (3, "Chess")),
        widget=forms.CheckboxSelectMultiple,
        initial=[1]
    )
    city = forms.ModelChoiceField(
        queryset=City.objects.all(),
        empty_label="-- choose --"
    )

Dynamic choices:

class OrderForm(forms.Form):
    product = forms.ChoiceField()
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["product"].choices = Product.objects.values_list("id", "name")

Built-in Field Reference

Field Purpoce
CharField Text input, supports max_length, min_length
IntegerField Numeric, max_value, min_value
DecimalField Exact decimal, max_digits, decimal_places
DateField, TimeField, DateTimeField Date/time parsing
EmailField Valid email format
URLField Valid URL format
BooleanField, NullBooleanField Checkbox or tri-state
ChoiceField, MultipleChoiceField Select options
ModelChoiceField, ModelMultipleChoiceField FK/M2M relations
FileField, ImageField File uploads
GenericIPAddressField IPv4/IPv6
UUIDField UUID strings

Validation

Built-in validators:

from django.core.validators import RegexValidator

class MobileForm(forms.Form):
    phone = forms.CharField(
        validators=[
            RegexValidator(r'^1[3-9]\d{9}$', 'Invalid mobile number')
        ]
    )

Custom vlaidator:

import re
from django.core.exceptions import ValidationError

def validate_mobile(value):
    if not re.match(r'^1[3-9]\d{9}$', value):
        raise ValidationError("Mobile format incorrect")

class RegisterForm(forms.Form):
    mobile = forms.CharField(validators=[validate_mobile])

Styling with Bootstrap

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<form method="post" class="row g-3">
  {% csrf_token %}
  <div class="col-md-6">
    <label class="form-label">{{ form.username.label }}</label>
    {{ form.username }}
    <div class="form-text">{{ form.username.errors.0 }}</div>
  </div>
  <div class="col-md-6">
    <label class="form-label">{{ form.password.label }}</label>
    {{ form.password }}
    <div class="form-text">{{ form.password.errors.0 }}</div>
  </div>
  <div class="col-12">
    <button class="btn btn-primary">Submit</button>
  </div>
</form>

Auto-apply Bootstrpa classes:

class BaseFormForm(forms.Form):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs.update({"class": "form-control"})

ModelForm

models.py

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=128)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    published = models.DateField()

forms.py

class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = "__all__"
        labels = {"title": "Book title", "price": "Price (USD)"}
        widgets = {
            "published": forms.DateInput(attrs={"type": "date"})
        }
        error_messages = {
            "title": {"required": "Title is mandatory"}
        }

views.py

def add_book(request):
    form = BookForm(request.POST or None)
    if form.is_valid():
        form.save()
        return redirect("book_list")
    return render(request, "add_book.html", {"form": form})

Tags: Django Forms Validation widgets modelform

Posted on Thu, 14 May 2026 05:38:19 +0000 by Ravi Kumar