表单处理通常有三条路径:
- 初始 GET 请求:显示空白表单或预填充表单。
- 携带无效数据的 POST 请求:通常重新显示表单及错误。
- 携带有效数据的 POST 请求:处理数据,通常随后重定向。
自己实现这些流程会产生大量重复样板代码,参见 在视图中使用表单。Django 提供了一组处理表单的通用基于类的视图来避免这个问题。
基础表单
假设有下面的联系表单,定义在 forms.py:
from django import forms
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
def send_email(self):
# send email using the self.cleaned_data dictionary
pass
可以使用 FormView 构建视图,views.py:
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactFormView(FormView):
template_name = "contact.html"
form_class = ContactForm
success_url = "/thanks/"
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)
注意:
FormView继承 TemplateResponseMixin,因此可使用template_name。- 默认的
form_valid()实现只是重定向到success_url。
模型表单
与模型一起使用时,通用视图尤为便利。只要能确定模型类,它们就会自动创建 ModelForm:
- 如果给出了
model属性,就使用这个模型类。 - 如果
get_object()返回一个对象,就使用该对象所属的类。 - 如果给出了
queryset,就使用该查询集的模型。
模型表单视图提供会自动保存模型的 form_valid() 实现。有特殊需求时可以覆盖它,下文提供了示例。
如果模型对象提供了 get_absolute_url(),那么 CreateView 和 UpdateView 会使用它,你甚至不必设置 success_url。
需要自定义 ModelForm,例如增加验证时,在视图上设置 form_class。指定自定义表单类时仍必须指定模型,即使 form_class 本身就是一个 ModelForm。
首先,为 Author 添加 get_absolute_url(),models.py:
from django.db import models
from django.urls import reverse
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse("author-detail", kwargs={"pk": self.pk})
然后让通用视图完成实际工作。下面的配置无需自己编写处理逻辑,views.py:
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author
class AuthorCreateView(CreateView):
model = Author
fields = ["name"]
class AuthorUpdateView(UpdateView):
model = Author
fields = ["name"]
class AuthorDeleteView(DeleteView):
model = Author
success_url = reverse_lazy("author-list")
这里必须用 reverse_lazy() 而非 reverse(),因为导入文件时 URL 尚未加载。
fields 的作用与 ModelForm 内部 Meta 类的 fields 属性相同。除非通过其他方式定义表单类,否则必须提供该属性;缺少它会引发 ImproperlyConfigured。同时指定 fields 和 form_class 也会引发该异常。
最后,在 URLconf 中连接这些视图,urls.py:
from django.urls import path
from myapp.views import AuthorCreateView, AuthorDeleteView, AuthorUpdateView
urlpatterns = [
# ...
path("author/add/", AuthorCreateView.as_view(), name="author-add"),
path("author/<int:pk>/", AuthorUpdateView.as_view(), name="author-update"),
path("author/<int:pk>/delete/", AuthorDeleteView.as_view(), name="author-delete"),
]
这些视图继承 SingleObjectTemplateResponseMixin,后者使用 template_name_suffix 构造基于模型的 template_name。本例中:
CreateView和UpdateView使用myapp/author_form.html。DeleteView使用myapp/author_confirm_delete.html。
如需让 CreateView 和 UpdateView 使用不同模板,可在视图类上设置 template_name 或 template_name_suffix。
模型与 request.user
要跟踪哪个用户通过 CreateView 创建了对象,可以使用自定义 ModelForm。先为模型添加外键关系,models.py:
from django.contrib.auth.models import User
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
# ...
在视图中,确保 created_by 不在可编辑字段列表内,并覆盖 form_valid() 来添加用户,views.py:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreateView(LoginRequiredMixin, CreateView):
model = Author
fields = ["name"]
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
LoginRequiredMixin 会阻止未登录用户访问表单。若省略该混入,就需要在 form_valid() 中自行处理未授权用户。
内容协商示例
下面的示例展示如何同时支持 API 工作流与普通 POST 表单:
from django.http import JsonResponse
from django.views.generic.edit import CreateView
from myapp.models import Author
class JsonableResponseMixin:
"""
Mixin to add JSON support to a form.
Must be used with an object-based FormView (e.g. CreateView)
"""
def form_invalid(self, form):
response = super().form_invalid(form)
if self.request.accepts("text/html"):
return response
else:
return JsonResponse(form.errors, status=400)
def form_valid(self, form):
# We make sure to call the parent's form_valid() method because
# it might do some processing (in the case of CreateView, it will
# call form.save() for example).
response = super().form_valid(form)
if self.request.accepts("text/html"):
return response
else:
data = {
"pk": self.object.pk,
}
return JsonResponse(data)
class AuthorCreateView(JsonableResponseMixin, CreateView):
model = Author
fields = ["name"]
此示例假定:只要客户端支持 text/html,就更偏好 HTML。但这个假定并不总成立。请求 .css 文件时,许多浏览器发送 Accept: text/css,*/*;q=0.1,表示更偏好 CSS,同时也接受其他格式。此时 request.accepts("text/html") 依然为 True。
要考虑客户端的偏好并确定正确格式,应使用 HttpRequest.get_preferred_type():
from django.http import HttpResponse
class JsonableResponseMixin:
"""
Mixin to add JSON support to a form.
Must be used with an object-based FormView (e.g. CreateView).
"""
accepted_media_types = ["text/html", "application/json"]
def dispatch(self, request, *args, **kwargs):
if request.get_preferred_type(self.accepted_media_types) is None:
# No format in common.
return HttpResponse(
status=406, headers={"Accept": ",".join(self.accepted_media_types)}
)
return super().dispatch(request, *args, **kwargs)
def form_invalid(self, form):
response = super().form_invalid(form)
accepted_type = self.request.get_preferred_type(self.accepted_media_types)
if accepted_type == "text/html":
return response
elif accepted_type == "application/json":
return JsonResponse(form.errors, status=400)
def form_valid(self, form):
# We make sure to call the parent's form_valid() method because
# it might do some processing (in the case of CreateView, it will
# call form.save() for example).
response = super().form_valid(form)
accepted_type = self.request.get_preferred_type(self.accepted_media_types)
if accepted_type == "text/html":
return response
elif accepted_type == "application/json":
data = {
"pk": self.object.pk,
}
return JsonResponse(data)
继续阅读:内置的基于类的通用视图、在基于类的视图中使用混入。
原文:Django 6.1 文档:使用基于类的视图处理表单。Copyright © Django Software Foundation and individual contributors. 本版本整理中文措辞、补译英文段落并调整版式;并修正内容协商示例的 HttpResponse 导入和 status 参数。采用 BSD 3-Clause 许可。
Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Django nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.











暂无评论内容