Django 4 表单与模型完全指南
Django 是一个高级 Python Web 框架,其内置的模型与表单系统极大地简化了数据库操作与用户输入处理。本文将全面介绍 Django 4 中模型(Model)与表单(Form)的核心概念、使用方法及最佳实践,帮助您构建健壮、可维护的 Web 应用。
虽然现在已经有很多项目切换到了vue等前后端分离,但是小型项目直接使用django表单还是高效。
目录
- Django 模型
- 1.1 模型定义与字段类型
- 1.2 关系字段
- 1.3 Meta 选项
- 1.4 模型方法
- 1.5 模型管理器与查询集
- Django 表单
- 2.1 表单类与字段
- 2.2 表单验证
- 2.3 表单渲染
- 2.4 处理表单数据
- 模型表单(ModelForm)
- 3.1 基础使用
- 3.2 字段定制
- 3.3 保存逻辑
- 3.4 高级用法
- 表单集(Formsets)
- 4.1 基础表单集
- 4.2 模型表单集
- 文件上传处理
- 自定义验证与清理
- 表单与模型的关系
- 性能与安全最佳实践
- 总结
1. Django 模型
模型是 Django 中与数据库交互的核心,它定义了一个 Python 类,映射为数据库中的一张表。
1.1 模型定义与字段类型
每个模型继承自 django.db.models.Model,其属性对应数据库字段。Django 提供了丰富的字段类型,覆盖常见需求:
from django.db import models
class Article(models.Model):
# 基本字段
title = models.CharField(max_length=200, verbose_name="标题")
content = models.TextField(verbose_name="内容")
pub_date = models.DateTimeField(auto_now_add=True, verbose_name="发布日期")
update_date = models.DateTimeField(auto_now=True, verbose_name="更新日期")
is_published = models.BooleanField(default=False, verbose_name="是否发布")
views = models.PositiveIntegerField(default=0, verbose_name="浏览量")
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
# 枚举字段(Django 3.0+)
class Status(models.TextChoices):
DRAFT = 'DF', '草稿'
PUBLISHED = 'PB', '已发布'
ARCHIVED = 'AR', '已归档'
status = models.CharField(
max_length=2,
choices=Status.choices,
default=Status.DRAFT,
verbose_name="状态"
)
常用字段类型:
CharField:短文本,必须指定max_lengthTextField:长文本IntegerField/PositiveIntegerField/SmallIntegerFieldBooleanField:布尔值DateTimeField/DateField/TimeFieldDecimalField:精确小数EmailField:邮箱,自动校验格式URLField:URLFileField/ImageField:文件上传JSONField(Django 3.1+):存储 JSON 数据
字段选项:
null:是否允许数据库为空blank:表单验证时是否允许为空default:默认值unique:唯一约束db_index:是否创建索引choices:枚举选项verbose_name:人类可读名称
1.2 关系字段
Django 支持三种关系:
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books') # 多对一
# 多对多
tags = models.ManyToManyField('Tag', blank=True)
# 一对一
detail = models.OneToOneField('BookDetail', on_delete=models.CASCADE, null=True)
class Tag(models.Model):
name = models.CharField(max_length=50)
class BookDetail(models.Model):
isbn = models.CharField(max_length=13)
pages = models.IntegerField()
ForeignKey:外键,on_delete指定删除时的行为(CASCADE、PROTECT、SET_NULL等)ManyToManyField:多对多,自动生成中间表OneToOneField:一对一
1.3 Meta 选项
在模型内部定义 class Meta 以配置模型元数据:
class Article(models.Model):
# ...
class Meta:
db_table = 'blog_article' # 自定义表名
ordering = ['-pub_date'] # 默认排序
indexes = [ # 复合索引
models.Index(fields=['pub_date', 'status']),
]
verbose_name = '文章'
verbose_name_plural = '文章'
unique_together = ['title', 'pub_date'] # 联合唯一(Django 4.0 推荐使用 UniqueConstraint)
constraints = [ # 自定义约束
models.UniqueConstraint(fields=['title', 'pub_date'], name='unique_title_per_day'),
]
1.4 模型方法
在模型中添加自定义方法,实现业务逻辑:
class Article(models.Model):
# ...
def __str__(self):
return self.title
def get_absolute_url(self):
from django.urls import reverse
return reverse('article_detail', args=[self.pk])
def is_editable(self):
return self.status != self.Status.PUBLISHED
def increase_views(self):
self.views += 1
self.save(update_fields=['views'])
1.5 模型管理器与查询集
默认管理器为 objects,可自定义:
class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='PB')
class Article(models.Model):
# ...
objects = models.Manager() # 默认管理器
published = PublishedManager() # 自定义管理器
查询集(QuerySet)是惰性执行的,常用方法:
filter()/exclude():过滤get():获取单个对象create()/update():创建/更新annotate():聚合select_related()/prefetch_related():优化关联查询
2. Django 表单
Django 的表单系统负责处理用户输入,包括生成 HTML、验证数据、清理数据等。
2.1 表单类与字段
表单类继承自 django.forms.Form,其字段映射为 HTML 输入元素:
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100, label='姓名')
email = forms.EmailField(label='邮箱')
message = forms.CharField(widget=forms.Textarea, label='消息')
agree = forms.BooleanField(required=True, label='同意条款')
常用表单字段:
CharField:文本输入EmailField:邮箱输入IntegerField:数字输入ChoiceField/ModelChoiceField:下拉选择MultipleChoiceField:多选BooleanField:复选框DateField/DateTimeField:日期/时间FileField/ImageField:文件上传
常用小部件(Widget):
TextInput/PasswordInput/HiddenInputTextareaCheckboxInput/RadioSelectSelect/SelectMultipleFileInputDateTimeInput(需配合第三方库如django.forms.widgets或django-flatpickr实现更好的 UI)
2.2 表单验证
验证分为三个层次:
- 字段级验证(
clean_<fieldname>方法) - 表单级验证(
clean方法) - 通用验证器(
validators参数)
from django.core.exceptions import ValidationError
from django.core.validators import MinLengthValidator
class ContactForm(forms.Form):
name = forms.CharField(
max_length=100,
validators=[MinLengthValidator(2, '姓名至少2个字符')]
)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
def clean_name(self):
data = self.cleaned_data['name']
if 'spam' in data.lower():
raise ValidationError('姓名不能包含敏感词')
return data
def clean(self):
cleaned_data = super().clean()
email = cleaned_data.get('email')
message = cleaned_data.get('message')
if email and message and 'spam' in message.lower():
self.add_error('message', '消息不能包含广告内容')
return cleaned_data
2.3 表单渲染
在模板中,表单可以多种方式渲染:
<!-- 手动渲染每个字段 -->
<form method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div>
{{ form.name.label_tag }}
{{ form.name }}
{{ form.name.errors }}
</div>
...
</form>
<!-- 循环渲染 -->
<form method="post">
{% csrf_token %}
{% for field in form %}
<div>
{{ field.label_tag }}
{{ field }}
{{ field.errors }}
</div>
{% endfor %}
</form>
<!-- 使用 as_p / as_ul / as_table 快速渲染 -->
<form method="post">
{% csrf_token %}
{{ form.as_p }}
</form>
2.4 处理表单数据
视图函数中处理表单的典型模式:
from django.shortcuts import render, redirect
from .forms import ContactForm
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# 处理验证后的数据
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
# 发送邮件、保存数据库等
return redirect('success')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
3. 模型表单(ModelForm)
ModelForm 将模型与表单绑定,自动根据模型生成表单字段,并简化保存操作。
3.1 基础使用
from django.forms import ModelForm
from .models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'status'] # 指定包含的字段
# fields = '__all__' # 包含所有字段
# exclude = ['views'] # 排除某些字段
视图处理与普通表单类似,但保存时调用 save() 方法:
def article_create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save() # 直接保存到数据库
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm()
return render(request, 'article_form.html', {'form': form})
3.2 字段定制
在 ModelForm 中可以自定义字段或重写字段属性:
class ArticleForm(ModelForm):
# 添加自定义字段
agree_terms = forms.BooleanField(label='同意条款', required=True)
class Meta:
model = Article
fields = ['title', 'content', 'status', 'agree_terms']
widgets = {
'content': forms.Textarea(attrs={'rows': 10, 'cols': 80}),
}
labels = {
'title': '文章标题',
}
help_texts = {
'status': '草稿状态不可被搜索',
}
3.3 保存逻辑
save() 方法接受 commit=False 参数,允许在保存前修改对象:
def article_create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save(commit=False) # 先不保存到数据库
article.author = request.user # 设置外键
article.save() # 现在保存
form.save_m2m() # 保存多对多关系(如果有)
return redirect(...)
3.4 高级用法
- 继承 ModelForm 并重写
clean方法:实现跨字段验证 - 使用
formfield_callback:动态修改字段属性 - ModelForm 与内联表单集:处理关联模型
4. 表单集(Formsets)
表单集用于处理同一页面上多个表单的集合,如批量编辑多个对象。
4.1 基础表单集
使用 formset_factory 创建普通表单集:
from django.forms import formset_factory
class BookForm(forms.Form):
title = forms.CharField()
author = forms.CharField()
BookFormSet = formset_factory(BookForm, extra=2, max_num=5)
def books_view(request):
if request.method == 'POST':
formset = BookFormSet(request.POST)
if formset.is_valid():
for form in formset:
# 处理每个表单数据
pass
else:
formset = BookFormSet()
return render(request, 'books.html', {'formset': formset})
4.2 模型表单集
使用 modelformset_factory 为模型生成表单集:
from django.forms import modelformset_factory
from .models import Book
BookFormSet = modelformset_factory(Book, fields=('title', 'author'), extra=1)
def manage_books(request):
queryset = Book.objects.filter(author=request.user)
formset = BookFormSet(request.POST or None, queryset=queryset)
if formset.is_valid():
formset.save()
return redirect('book_list')
return render(request, 'manage_books.html', {'formset': formset})
内联表单集(Inline Formset) 用于处理一对多关系:
from django.forms import inlineformset_factory
BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title', 'isbn'), extra=2)
def author_edit(request, pk):
author = get_object_or_404(Author, pk=pk)
formset = BookInlineFormSet(request.POST or None, instance=author)
if formset.is_valid():
formset.save()
return redirect('author_detail', pk=author.pk)
return render(request, 'author_edit.html', {'formset': formset})
5. 文件上传处理
5.1 模型中的文件字段
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
avatar = models.ImageField(upload_to='avatars/%Y/%m/%d/', blank=True, null=True)
resume = models.FileField(upload_to='resumes/')
5.2 表单中的文件字段
在表单中,ImageField 和 FileField 会自动处理:
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = ['avatar', 'resume']
视图需要接收 request.FILES:
def profile_edit(request):
if request.method == 'POST':
form = ProfileForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect(...)
else:
form = ProfileForm()
return render(request, 'profile.html', {'form': form})
模板中必须设置 enctype="multipart/form-data":
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<button type="submit">上传</button>
</form>
5.3 文件存储与访问
upload_to可以是一个可调用对象,动态生成路径。- 上传文件后,可以通过
model.file.url获取访问 URL,需要在settings.py中配置MEDIA_URL和MEDIA_ROOT。
6. 自定义验证与清理
除了字段级和表单级验证,Django 还提供:
6.1 验证器
可复用的验证函数:
def validate_even(value):
if value % 2 != 0:
raise ValidationError('数值必须为偶数')
class MyForm(forms.Form):
even_number = forms.IntegerField(validators=[validate_even])
6.2 模型验证
模型本身也支持验证,通过重写 clean 方法实现:
class Article(models.Model):
# ...
def clean(self):
if self.status == 'PB' and not self.pub_date:
raise ValidationError('发布状态的文章必须有发布日期')
模型验证在调用 full_clean() 时触发,但通常在 ModelForm 保存时自动调用。
6.3 清理与验证顺序
- 字段验证(运行
to_python和字段级验证) - 字段的
clean_<field>方法 - 表单的
clean方法 - 模型的
clean方法(仅限 ModelForm)
7. 表单与模型的关系
7.1 数据流转
- 用户提交数据 → 表单接收 → 验证 → 清理 → 生成
cleaned_data→ 保存到模型(或执行其他操作) ModelForm自动映射表单字段到模型字段,并处理关系字段(外键、多对多)。
7.2 选择字段与关系
ModelChoiceField:用于外键选择,可设置queryset和empty_labelModelMultipleChoiceField:用于多对多选择
示例:
class ArticleForm(ModelForm):
author = forms.ModelChoiceField(queryset=Author.objects.all(), empty_label="请选择作者")
tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all(), widget=forms.CheckboxSelectMultiple)
class Meta:
model = Article
fields = ['title', 'content', 'author', 'tags']
7.3 保存关联对象
使用 commit=False 后,需要显式保存多对多关系:
article = form.save(commit=False)
article.author = request.user
article.save()
form.save_m2m() # 保存 tags 等 ManyToMany 字段
8. 性能与安全最佳实践
8.1 性能优化
- 使用
select_related/prefetch_related:在获取表单集时预加载关联对象,减少查询次数。 - 限制字段数量:避免在表单中包含大量字段,尤其是大字段(如
TextField)可考虑延迟加载。 - 使用
only()/defer():在不需要所有字段时,限制查询的字段。 - 表单集分页:对于大量数据的编辑,使用分页处理。
8.2 安全防护
- CSRF 保护:始终在模板中包含
{% csrf_token %}。 - 验证用户输入:利用 Django 内置验证器,并自定义严格验证逻辑。
- 文件上传安全:限制上传文件类型(通过验证器或
validate_image_file),设置FILE_UPLOAD_MAX_MEMORY_SIZE。 - SQL 注入与 XSS:Django 模板自动转义,但需小心使用
safe过滤器;使用参数化查询(Django ORM 自动处理)。 - 权限控制:在视图和表单中验证用户权限(如
request.user.has_perm)。
8.3 用户体验
- 使用
django-crispy-forms:快速美化表单。 - 前端验证:结合 HTML5 属性和 JavaScript 提供即时反馈,但不能替代后端验证。
- 友好的错误信息:自定义
error_messages字段属性,提供清晰提示。
9. 总结
Django 4 的表单与模型系统提供了强大而灵活的工具,帮助开发者快速构建安全、健壮的应用。掌握以下核心要点:
- 模型:定义数据结构,使用字段类型、关系、元选项和自定义方法。
- 表单:处理用户输入,包含字段、验证、渲染和视图集成。
- 模型表单:自动映射模型与表单,简化保存逻辑。
- 表单集:处理批量数据输入。
- 文件上传:通过
FileField和ImageField轻松处理。 - 验证:多层次验证确保数据完整性。
- 安全与性能:遵循最佳实践,保障应用稳定运行。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)