Django Forms: Creating and Handling HTML Forms

Django forms handle HTML form creation, rendering, validation, and data processing providing secure form handling with CSRF protection and automatic sanitization. Forms abstract form field rendering and validation enabling reusable form logic across views and templates. Understanding Django forms is essential for accepting user input including contact forms, search forms, user registration, and data entry applications. Django provides Form class for general forms and ModelForm for forms based on models automatically generating form fields from model definitions. Mastering Django forms supports building interactive web applications with proper validation, error handling, and user feedback from simple contact forms to complex multi-step wizards and file upload interfaces ensuring data integrity and user experience quality.
Form Basics
Django Form class defines form fields with validators and widgets controlling rendering and validation. Forms handle GET rendering and POST processing through is_valid() method. Understanding form basics enables creating secure validated forms with minimal code.
# Form Basics
from django import forms
# Basic form
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)
# View handling form
from django.shortcuts import render, redirect
def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Access cleaned data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
# Process form (send email, save to DB, etc.)
send_email(subject, message, email)
return redirect('success')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
# Template rendering
# <form method="post">
# {% csrf_token %}
# {{ form.as_p }}
# <button type="submit">Submit</button>
# </form>
# Form field types
class UserForm(forms.Form):
# Text inputs
username = forms.CharField(max_length=30)
password = forms.CharField(widget=forms.PasswordInput)
# Choices
country = forms.ChoiceField(choices=[
('us', 'United States'),
('uk', 'United Kingdom'),
('ca', 'Canada'),
])
# Multiple choice
interests = forms.MultipleChoiceField(choices=[
('sports', 'Sports'),
('music', 'Music'),
('reading', 'Reading'),
])
# Boolean
agree_terms = forms.BooleanField()
# Numeric
age = forms.IntegerField(min_value=18, max_value=100)
rating = forms.DecimalField(max_digits=3, decimal_places=1)
# Date/Time
birth_date = forms.DateField()
appointment_time = forms.DateTimeField()
# File upload
profile_picture = forms.ImageField()
document = forms.FileField()
# URL and Email
website = forms.URLField()
email = forms.EmailField()
# Initial values
form = ContactForm(initial={
'name': 'John Doe',
'email': '[email protected]'
})
# Required fields
class MyForm(forms.Form):
required_field = forms.CharField()
optional_field = forms.CharField(required=False)Form Validation
Form validation ensures data integrity through field validators and clean methods. Field-level validation uses clean_fieldname methods while form-level validation uses clean method for cross-field validation. Understanding validation enables robust data processing with comprehensive error handling.
# Form 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)
confirm_password = forms.CharField(widget=forms.PasswordInput)
age = forms.IntegerField()
# Field-level validation
def clean_username(self):
username = self.cleaned_data['username']
# Check if username exists
if User.objects.filter(username=username).exists():
raise ValidationError('Username already taken')
# Custom validation
if len(username) < 3:
raise ValidationError('Username must be at least 3 characters')
if not username.isalnum():
raise ValidationError('Username must be alphanumeric')
return username
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise ValidationError('Email already registered')
# Domain validation
if not email.endswith('@example.com'):
raise ValidationError('Must use company email')
return email
def clean_age(self):
age = self.cleaned_data['age']
if age < 18:
raise ValidationError('Must be 18 or older')
return age
# Form-level validation (cross-field)
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
if password and confirm_password:
if password != confirm_password:
raise ValidationError('Passwords do not match')
return cleaned_data
# Custom validators
from django.core.validators import RegexValidator
def validate_no_spaces(value):
if ' ' in value:
raise ValidationError('Value cannot contain spaces')
class MyForm(forms.Form):
username = forms.CharField(
validators=[
validate_no_spaces,
RegexValidator(r'^[a-zA-Z0-9_]+$', 'Only alphanumeric and underscores')
]
)
# Multiple validation errors
def clean_username(self):
username = self.cleaned_data['username']
errors = []
if len(username) < 3:
errors.append('Too short')
if not username[0].isalpha():
errors.append('Must start with letter')
if errors:
raise ValidationError(errors)
return usernameForm Rendering
Form rendering controls HTML output through templates and widgets. Django provides helper methods like as_p, as_table, and as_ul for quick rendering or manual field rendering for custom layouts. Understanding rendering enables creating forms matching application design requirements.
# Form Rendering
# Template - Automatic rendering
# As paragraphs
# <form method="post">
# {% csrf_token %}
# {{ form.as_p }}
# <button type="submit">Submit</button>
# </form>
# As table
# <form method="post">
# {% csrf_token %}
# <table>
# {{ form.as_table }}
# </table>
# <button type="submit">Submit</button>
# </form>
# Manual field rendering
# <form method="post">
# {% csrf_token %}
#
# <div class="field">
# {{ form.username.label_tag }}
# {{ form.username }}
# {% if form.username.errors %}
# <span class="error">{{ form.username.errors }}</span>
# {% endif %}
# </div>
#
# <div class="field">
# {{ form.email.label_tag }}
# {{ form.email }}
# {% if form.email.errors %}
# <span class="error">{{ form.email.errors }}</span>
# {% endif %}
# </div>
#
# <button type="submit">Submit</button>
# </form>
# Display all errors
# {% if form.errors %}
# <div class="errors">
# <ul>
# {% for field in form %}
# {% for error in field.errors %}
# <li>{{ field.label }}: {{ error }}</li>
# {% endfor %}
# {% endfor %}
# {% for error in form.non_field_errors %}
# <li>{{ error }}</li>
# {% endfor %}
# </ul>
# </div>
# {% endif %}
# Custom widgets
from django import forms
class StyledForm(forms.Form):
name = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your name'
})
)
message = forms.CharField(
widget=forms.Textarea(attrs={
'rows': 5,
'cols': 40,
'class': 'form-control'
})
)
country = forms.ChoiceField(
choices=[('us', 'USA'), ('uk', 'UK')],
widget=forms.RadioSelect
)
# Widget in Meta
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'email', 'bio']
widgets = {
'bio': forms.Textarea(attrs={'rows': 4}),
'email': forms.EmailInput(attrs={'class': 'form-control'})
}ModelForms
ModelForm automatically generates form fields from model definitions reducing code duplication and ensuring consistency between models and forms. ModelForm saves directly to database with form.save() method. Understanding ModelForm enables rapid form development for CRUD operations.
# ModelForms
from django import forms
from .models import Post
# Basic ModelForm
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'status']
# Or exclude fields
# exclude = ['created_at', 'updated_at']
# Or all fields
# fields = '__all__'
# View using ModelForm
def create_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'post_form.html', {'form': form})
# Update view
def update_post(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return redirect('post_detail', pk=pk)
else:
form = PostForm(instance=post)
return render(request, 'post_form.html', {'form': form})
# Custom labels and help text
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'status']
labels = {
'title': 'Post Title',
'content': 'Post Content',
}
help_texts = {
'title': 'Enter a catchy title',
}
error_messages = {
'title': {
'required': 'Please provide a title',
}
}
# Override fields
class PostForm(forms.ModelForm):
# Override field
title = forms.CharField(
max_length=200,
widget=forms.TextInput(attrs={'class': 'form-control'})
)
class Meta:
model = Post
fields = ['title', 'content', 'status']
def clean_title(self):
title = self.cleaned_data['title']
if 'spam' in title.lower():
raise forms.ValidationError('Invalid title')
return title
# Many-to-many handling
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
form.save_m2m() # Save many-to-many relationshipsForm Best Practices
Effective form usage follows patterns ensuring security and usability. Always include CSRF token in POST forms. Use ModelForm when forms correspond to models. Validate data thoroughly with clean methods. Provide clear error messages. Use appropriate widgets for field types. Handle file uploads securely. Implement form success messages. Test form validation thoroughly. Keep forms focused avoiding too many fields. Use crispy-forms or similar for consistent styling. These practices ensure secure, user-friendly forms supporting reliable data collection and processing.
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


