Django 表单是处理 HTML 表单创建和验证的强大工具。它们提供了一种简单的方法来定义表单字段、验证用户输入、显示错误消息以及在模板中渲染表单。
| 类型 | 描述 | 使用场景 |
|---|---|---|
Form |
普通表单 | 通用表单需求 |
ModelForm |
模型表单 | 与数据库模型交互 |
FormSet |
表单集 | 处理多个表单 |
ModelFormSet |
模型表单集 | 处理多个模型实例 |
在 Django 中创建表单需要定义一个继承自 forms.Form 的类。
from django import forms
class ContactForm(forms.Form):
# 文本字段
name = forms.CharField(
max_length=100,
label='您的姓名',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入姓名'
})
)
# 邮箱字段
email = forms.EmailField(
label='邮箱地址',
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'example@email.com'
})
)
# 选择字段
subject = forms.ChoiceField(
choices=[
('', '请选择主题'),
('general', '一般咨询'),
('support', '技术支持'),
('billing', '账单问题'),
],
label='主题',
widget=forms.Select(attrs={'class': 'form-control'})
)
# 文本区域
message = forms.CharField(
label='留言内容',
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': '请输入您的留言...'
})
)
# 布尔字段
newsletter = forms.BooleanField(
required=False,
label='订阅新闻简报',
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
)
| 字段类型 | 描述 | HTML 控件 |
|---|---|---|
| CharField | 文本字段 | <input type="text"> |
| EmailField | 邮箱字段 | <input type="email"> |
| IntegerField | 整数字段 | <input type="number"> |
| BooleanField | 布尔字段 | <input type="checkbox"> |
| DateField | 日期字段 | <input type="date"> |
| ChoiceField | 选择字段 | <select> |
表单处理通常在视图函数中进行,需要处理 GET(显示空表单)和 POST(处理提交数据)请求。
from django.shortcuts import render, redirect
from .forms import ContactForm
def contact_view(request):
if request.method == 'POST':
# 使用提交的数据创建表单实例
form = ContactForm(request.POST)
if form.is_valid():
# 表单数据已验证和清理
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
newsletter = form.cleaned_data['newsletter']
# 处理数据(保存到数据库、发送邮件等)
print(f"收到来自 {name} 的留言: {message}")
# 重定向到成功页面
return redirect('contact_success')
else:
# GET 请求,显示空表单
form = ContactForm()
# 渲染模板(包含表单实例)
return render(request, 'contact.html', {'form': form})
def contact_success(request):
return render(request, 'contact_success.html')
from django.views.generic import FormView
from .forms import ContactForm
from django.urls import reverse_lazy
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = reverse_lazy('contact_success')
def form_valid(self, form):
# 表单验证通过时的处理
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
# 处理表单数据
print(f"收到来自 {name} 的留言")
# 调用父类方法完成重定向
return super().form_valid(form)
form.cleaned_data 访问form_valid 逻辑form.errorsform_invalid 逻辑Django 提供了多种在模板中渲染表单的方式,从自动渲染到手动控制每个字段。
<form method="post">
{% csrf_token %}
{# 自动渲染所有表单字段 #}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">提交</button>
</form>
<form method="post">
{% csrf_token %}
{# 显示非字段错误 #}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{# 手动渲染每个字段 #}
<div class="form-group">
{{ form.name.label_tag }}
{{ form.name }}
{% if form.name.errors %}
<div class="invalid-feedback">
{% for error in form.name.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<!-- 其他字段类似 -->
<button type="submit" class="btn btn-primary">提交</button>
</form>
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{# 添加 Bootstrap 类到表单控件 #}
{{ field|add_class:"form-control" }}
{# 显示字段帮助文本 #}
{% if field.help_text %}
<small class="form-text text-muted">
{{ field.help_text }}
</small>
{% endif %}
{# 显示字段错误 #}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
{{ form.as_p }} - 包裹在 <p> 标签中{{ form.as_table }} - 表格行(需要在 <table> 中){{ form.as_ul }} - 列表项(需要在 <ul> 中)from django import template
register = template.Library()
@register.filter
def add_class(field, css_class):
"""为表单字段添加 CSS 类"""
return field.as_widget(attrs={"class": css_class})
@register.filter
def add_placeholder(field, placeholder):
"""为表单字段添加占位符"""
return field.as_widget(attrs={"placeholder": placeholder})
Django 表单提供了强大的数据验证功能,包括字段级验证和表单级验证。
from django import forms
from django.core.validators import validate_email, MinLengthValidator
class UserForm(forms.Form):
username = forms.CharField(
max_length=30,
min_length=3,
validators=[MinLengthValidator(3)],
error_messages={
'required': '用户名不能为空',
'min_length': '用户名至少3个字符',
'max_length': '用户名不能超过30个字符'
}
)
email = forms.EmailField(
validators=[validate_email],
error_messages={
'required': '邮箱不能为空',
'invalid': '请输入有效的邮箱地址'
}
)
age = forms.IntegerField(
min_value=0,
max_value=150,
error_messages={
'min_value': '年龄不能小于0',
'max_value': '年龄不能大于150'
}
)
from django import forms
from django.core.exceptions import ValidationError
def validate_even(value):
"""自定义验证器:检查是否为偶数"""
if value % 2 != 0:
raise ValidationError('%(value)s 不是偶数',
params={'value': value})
class CustomForm(forms.Form):
number = forms.IntegerField(
validators=[validate_even]
)
def clean_username(self):
"""字段级清理和验证"""
username = self.cleaned_data['username']
# 检查用户名是否已存在
if User.objects.filter(username=username).exists():
raise ValidationError('用户名已存在')
return username
def clean(self):
"""表单级验证"""
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
# 检查密码是否匹配
if password and confirm_password and password != confirm_password:
raise ValidationError('两次输入的密码不匹配')
return cleaned_data
def register_view(request):
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
# 处理有效数据
return redirect('success')
else:
# 处理验证错误
print("表单错误:", form.errors)
print("特定字段错误:", form['username'].errors)
else:
form = UserForm()
return render(request, 'register.html', {'form': form})
<form method="post">
{% csrf_token %}
{# 显示表单级错误 #}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<div class="form-group">
<label for="{{ form.username.id_for_label }}">用户名</label>
{{ form.username }}
{# 显示字段级错误 #}
{% if form.username.errors %}
<div class="alert alert-danger mt-2">
{{ form.username.errors }}
</div>
{% endif %}
</div>
<button type="submit">注册</button>
</form>
ModelForm 可以根据 Django 模型自动创建表单,简化了模型实例的创建和更新操作。
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.CharField(max_length=100)
published_date = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=False)
def __str__(self):
return self.title
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'author', 'is_published']
# 或者使用 exclude 排除字段
# exclude = ['published_date']
labels = {
'title': '文章标题',
'content': '内容',
'author': '作者',
'is_published': '是否发布'
}
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入文章标题'
}),
'content': forms.Textarea(attrs={
'class': 'form-control',
'rows': 5,
'placeholder': '请输入文章内容...'
}),
'author': forms.TextInput(attrs={
'class': 'form-control'
}),
'is_published': forms.CheckboxInput(attrs={
'class': 'form-check-input'
})
}
from django.shortcuts import render, redirect
from .forms import ArticleForm
from .models import Article
def create_article(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
# 自动创建 Article 对象并保存
article = form.save()
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm()
return render(request, 'article_form.html', {'form': form})
def update_article(request, pk):
article = Article.objects.get(pk=pk)
if request.method == 'POST':
# 使用 instance 参数更新现有对象
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, 'article_form.html', {'form': form})
form.save() 自动创建/更新对象fields 或 exclude 控制显示字段fields = '__all__' 可以包含所有模型字段,但在生产环境中建议明确列出需要的字段。
from django.forms import formset_factory
from .forms import ContactForm
# 创建表单集
ContactFormSet = formset_factory(
ContactForm,
extra=2, # 初始显示的表单数量
max_num=5 # 最大表单数量
)
def manage_contacts(request):
if request.method == 'POST':
formset = ContactFormSet(request.POST)
if formset.is_valid():
for form in formset:
if form.cleaned_data: # 跳过空表单
# 处理每个表单的数据
name = form.cleaned_data['name']
email = form.cleaned_data['email']
# ... 保存数据
return redirect('success')
else:
formset = ContactFormSet()
return render(request, 'contact_formset.html', {'formset': formset})
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div class="form-row">
<div class="col">
{{ form.name.label_tag }}
{{ form.name }}
</div>
<div class="col">
{{ form.email.label_tag }}
{{ form.email }}
</div>
</div>
<hr>
{% endfor %}
<button type="submit" class="btn btn-primary">保存所有联系人</button>
</form>
{{ formset.management_form }},它包含了表单集所需的元数据。
from django import forms
class UploadForm(forms.Form):
title = forms.CharField(max_length=100)
file = forms.FileField(
label='选择文件',
widget=forms.FileInput(attrs={
'class': 'form-control-file'
})
)
# 多文件上传
files = forms.FileField(
widget=forms.FileInput(attrs={
'multiple': True,
'class': 'form-control-file'
})
)
def upload_file(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
# 处理上传的文件
uploaded_file = request.FILES['file']
# 保存文件
with open(f'uploads/{uploaded_file.name}', 'wb+') as destination:
for chunk in uploaded_file.chunks():
destination.write(chunk)
return redirect('success')
else:
form = UploadForm()
return render(request, 'upload.html', {'form': form})
enctype="multipart/form-data" 并且在视图中同时传递 request.POST 和 request.FILES。
{% csrf_token %}
# 限制文件类型
from django.core.validators import FileExtensionValidator
file = forms.FileField(
validators=[FileExtensionValidator(
['pdf', 'doc', 'docx']
)]
)
<input type="email"
class="form-control"
placeholder="name@example.com"
required>
forms.py 文件中select_related 和 prefetch_related)