Skip to main content

Forms

Form Basics

Simple Form

from django import forms

class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
subject = forms.CharField(max_length=200)
message = forms.CharField(widget=forms.Textarea)

def clean_email(self):
email = self.cleaned_data['email']
if not email.endswith('@company.com'):
raise forms.ValidationError("Please use your company email.")
return email

Model Form

from django import forms
from .models import Article, Category

class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'category', 'published']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
'category': forms.Select(attrs={'class': 'form-control'}),
}
labels = {
'title': 'Article Title',
'content': 'Article Content',
}
help_texts = {
'title': 'Enter a descriptive title for your article.',
}

Form Fields

Common Fields

from django import forms

class ExampleForm(forms.Form):
# Text fields
name = forms.CharField(max_length=100)
description = forms.CharField(widget=forms.Textarea)

# Email and URL
email = forms.EmailField()
website = forms.URLField()

# Numbers
age = forms.IntegerField(min_value=0, max_value=120)
price = forms.DecimalField(max_digits=10, decimal_places=2)
rating = forms.FloatField(min_value=0.0, max_value=5.0)

# Choices
CATEGORY_CHOICES = [
('tech', 'Technology'),
('sports', 'Sports'),
('music', 'Music'),
]
category = forms.ChoiceField(choices=CATEGORY_CHOICES)

# Multiple choice
interests = forms.MultipleChoiceField(
choices=CATEGORY_CHOICES,
widget=forms.CheckboxSelectMultiple
)

# Boolean
agree_terms = forms.BooleanField()
newsletter = forms.BooleanField(required=False)

# Date and time
birth_date = forms.DateField()
appointment_time = forms.TimeField()
event_datetime = forms.DateTimeField()

# File uploads
avatar = forms.ImageField()
document = forms.FileField()

Field Options

class UserForm(forms.Form):
username = forms.CharField(
max_length=30,
required=True,
initial='user',
help_text='Enter your username',
error_messages={
'required': 'Username is required',
'max_length': 'Username must be less than 30 characters'
}
)

email = forms.EmailField(
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your email'
})
)

password = forms.CharField(
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'autocomplete': 'new-password'
})
)

Form Widgets

Built-in Widgets

from django import forms

class ExampleForm(forms.Form):
# Text widgets
single_line = forms.CharField(widget=forms.TextInput)
multi_line = forms.CharField(widget=forms.Textarea)
password = forms.CharField(widget=forms.PasswordInput)

# Select widgets
single_choice = forms.ChoiceField(
choices=[('1', 'Option 1'), ('2', 'Option 2')],
widget=forms.Select
)

multiple_choice = forms.MultipleChoiceField(
choices=[('1', 'Option 1'), ('2', 'Option 2')],
widget=forms.SelectMultiple
)

# Checkbox and radio
single_checkbox = forms.BooleanField(widget=forms.CheckboxInput)
multiple_checkboxes = forms.MultipleChoiceField(
choices=[('1', 'Option 1'), ('2', 'Option 2')],
widget=forms.CheckboxSelectMultiple
)

radio_buttons = forms.ChoiceField(
choices=[('1', 'Option 1'), ('2', 'Option 2')],
widget=forms.RadioSelect
)

# Date and time widgets
date_input = forms.DateField(widget=forms.DateInput)
datetime_input = forms.DateTimeField(widget=forms.DateTimeInput)

# File widgets
file_input = forms.FileField(widget=forms.FileInput)
image_input = forms.ImageField(widget=forms.ClearableFileInput)

# Hidden widget
hidden_field = forms.CharField(widget=forms.HiddenInput)

Custom Widget Attributes

class StyledForm(forms.Form):
name = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your name',
'data-validation': 'required'
})
)

email = forms.EmailField(
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your email',
'autocomplete': 'email'
})
)

message = forms.CharField(
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 5,
'placeholder': 'Enter your message'
})
)

Form Validation

Field-level Validation

from django import forms
from django.core.exceptions import ValidationError

class RegistrationForm(forms.Form):
username = forms.CharField(max_length=30)
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput)
password_confirm = forms.CharField(widget=forms.PasswordInput)

def clean_username(self):
username = self.cleaned_data['username']
if len(username) < 3:
raise ValidationError("Username must be at least 3 characters long.")
if User.objects.filter(username=username).exists():
raise ValidationError("Username already exists.")
return username

def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise ValidationError("Email already registered.")
return email

Form-level Validation

class RegistrationForm(forms.Form):
# ... field definitions ...

def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get('password')
password_confirm = cleaned_data.get('password_confirm')

if password and password_confirm:
if password != password_confirm:
raise ValidationError("Passwords don't match.")

return cleaned_data

Custom Validators

from django.core.validators import RegexValidator

def validate_phone_number(value):
if not value.startswith('+'):
raise ValidationError("Phone number must start with country code.")

phone_validator = RegexValidator(
regex=r'^\+?1?\d{9,15}$',
message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed."
)

class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
phone = forms.CharField(validators=[phone_validator, validate_phone_number])

Form Processing

Basic Form Processing

from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm

def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Process form data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']

# Send email, save to database, etc.
send_email(name, email, message)

messages.success(request, 'Thank you for your message!')
return redirect('contact_success')
else:
form = ContactForm()

return render(request, 'contact.html', {'form': form})

Model Form Processing

from django.shortcuts import render, redirect, get_object_or_404
from .models import Article
from .forms import ArticleForm

def create_article(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save(commit=False)
article.author = request.user
article.save()
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm()

return render(request, 'create_article.html', {'form': form})

def edit_article(request, pk):
article = get_object_or_404(Article, pk=pk)

if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm(instance=article)

return render(request, 'edit_article.html', {'form': form, 'article': article})

File Upload Forms

Single File Upload

from django import forms
from .models import Document

class DocumentForm(forms.ModelForm):
class Meta:
model = Document
fields = ['title', 'file']
widgets = {
'file': forms.FileInput(attrs={'accept': '.pdf,.doc,.docx'})
}

Multiple File Upload

class MultipleFileForm(forms.Form):
files = forms.FileField(
widget=forms.ClearableFileInput(attrs={'multiple': True})
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['files'].widget.attrs.update({'class': 'form-control'})

File Upload Processing

def upload_document(request):
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
document = form.save(commit=False)
document.uploaded_by = request.user
document.save()
return redirect('document_list')
else:
form = DocumentForm()

return render(request, 'upload_document.html', {'form': form})

def upload_multiple_files(request):
if request.method == 'POST':
form = MultipleFileForm(request.POST, request.FILES)
if form.is_valid():
files = request.FILES.getlist('files')
for file in files:
Document.objects.create(
title=file.name,
file=file,
uploaded_by=request.user
)
return redirect('document_list')
else:
form = MultipleFileForm()

return render(request, 'upload_multiple.html', {'form': form})

Dynamic Forms

FormSets

from django.forms import formset_factory
from .forms import ContactForm

# Create formset
ContactFormSet = formset_factory(ContactForm, extra=3)

def multiple_contacts(request):
if request.method == 'POST':
formset = ContactFormSet(request.POST)
if formset.is_valid():
for form in formset:
if form.cleaned_data:
# Process each form
pass
return redirect('success')
else:
formset = ContactFormSet()

return render(request, 'multiple_contacts.html', {'formset': formset})

Model FormSets

from django.forms import modelformset_factory
from .models import Article

ArticleFormSet = modelformset_factory(Article, fields=('title', 'content'), extra=2)

def edit_articles(request):
if request.method == 'POST':
formset = ArticleFormSet(request.POST)
if formset.is_valid():
formset.save()
return redirect('article_list')
else:
formset = ArticleFormSet(queryset=Article.objects.none())

return render(request, 'edit_articles.html', {'formset': formset})

Inline FormSets

from django.forms import inlineformset_factory
from .models import Author, Book

BookFormSet = inlineformset_factory(Author, Book, fields=('title', 'publication_date'), extra=1)

def edit_author_books(request, author_id):
author = get_object_or_404(Author, pk=author_id)

if request.method == 'POST':
formset = BookFormSet(request.POST, instance=author)
if formset.is_valid():
formset.save()
return redirect('author_detail', pk=author.pk)
else:
formset = BookFormSet(instance=author)

return render(request, 'edit_author_books.html', {
'author': author,
'formset': formset
})

Form Rendering

Manual Form Rendering

<!-- Template: contact.html -->
<form method="post">
{% csrf_token %}

<div class="form-group">
{{ form.name.label_tag }} {{ form.name }} {% if form.name.errors %}
<div class="error">{{ form.name.errors }}</div>
{% endif %}
</div>

<div class="form-group">
{{ form.email.label_tag }} {{ form.email }} {% if form.email.errors %}
<div class="error">{{ form.email.errors }}</div>
{% endif %}
</div>

<div class="form-group">
{{ form.message.label_tag }} {{ form.message }} {% if form.message.errors %}
<div class="error">{{ form.message.errors }}</div>
{% endif %}
</div>

<button type="submit" class="btn btn-primary">Submit</button>
</form>

Loop Form Rendering

<form method="post">
{% csrf_token %} {% for field in form %}
<div class="form-group">
{{ field.label_tag }} {{ field }} {% if field.errors %}
<div class="error">{{ field.errors }}</div>
{% endif %} {% if field.help_text %}
<small class="help-text">{{ field.help_text }}</small>
{% endif %}
</div>
{% endfor %}

<button type="submit" class="btn btn-primary">Submit</button>
</form>

FormSet Rendering

<!-- Template: multiple_contacts.html -->
<form method="post">
{% csrf_token %} {{ formset.management_form }} {% for form in formset %}
<div class="formset-form">
<h3>Contact {{ forloop.counter }}</h3>
{% for field in form %}
<div class="form-group">
{{ field.label_tag }} {{ field }} {% if field.errors %}
<div class="error">{{ field.errors }}</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}

<button type="submit" class="btn btn-primary">Submit</button>
</form>

Form Styling

Bootstrap Form Styling

from django import forms

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

class ContactForm(BootstrapForm):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)

Custom Form Mixin

class FormControlMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs.update({'class': 'form-control'})
if field.required:
field.widget.attrs.update({'required': 'required'})

class ContactForm(FormControlMixin, forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)

AJAX Forms

AJAX Form Processing

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import json

@csrf_exempt
def ajax_contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Process form data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']

# Send email, save to database, etc.
send_email(name, email, message)

return JsonResponse({
'success': True,
'message': 'Thank you for your message!'
})
else:
return JsonResponse({
'success': False,
'errors': form.errors
})

return JsonResponse({'success': False, 'error': 'Invalid request'})

AJAX Form Template

<form id="contact-form">
{% csrf_token %} {{ form.as_p }}
<button type="submit">Submit</button>
</form>

<script>
document
.getElementById('contact-form')
.addEventListener('submit', function (e) {
e.preventDefault();

const formData = new FormData(this);

fetch('/ajax-contact/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')
.value,
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(data.message);
} else {
console.log('Errors:', data.errors);
}
});
});
</script>

Form Testing

Form Testing

from django.test import TestCase
from .forms import ContactForm

class ContactFormTest(TestCase):
def test_valid_form(self):
form_data = {
'name': 'John Doe',
'email': 'john@example.com',
'message': 'Hello, world!'
}
form = ContactForm(data=form_data)
self.assertTrue(form.is_valid())

def test_invalid_email(self):
form_data = {
'name': 'John Doe',
'email': 'invalid-email',
'message': 'Hello, world!'
}
form = ContactForm(data=form_data)
self.assertFalse(form.is_valid())
self.assertIn('email', form.errors)

def test_required_fields(self):
form = ContactForm(data={})
self.assertFalse(form.is_valid())
self.assertIn('name', form.errors)
self.assertIn('email', form.errors)
self.assertIn('message', form.errors)