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})