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)