第 4 章 · 类视图 CBV 与通用 CRUD
本章目标:理解 Django 类视图(CBV) 的设计思想;熟练使用 ListView / CreateView / UpdateView / DeleteView 完成商品 CRUD;掌握 LoginRequiredMixin 与 PermissionRequiredMixin;了解 Mixin 组合模式;能将 ch02~ch03 的函数视图重构为 CBV,并保持权限与表单行为一致。
学时建议:5~6 小时(含 2 小时跟练)
前置:完成 django-web ch01~ch03(ModelForm、登录、Group 权限)。
4.1 场景说明:减少重复的 CRUD 样板代码
ch02~ch03 用函数视图实现了商品的增删改查,每个视图都重复以下模式:
GET → 构建 form / queryset → render
POST → 校验 → save / delete → redirect
权限 → @login_required + @permission_required
Django 通用类视图(GCBV) 把这些共性抽取为可复用类,通过声明 model、form_class、template_name 即可。
| 操作 | 函数视图 | 类视图 |
|---|---|---|
| 列表 | def product_list | ProductListView(ListView) |
| 创建 | def product_create | ProductCreateView(CreateView) |
| 编辑 | def product_edit | ProductUpdateView(UpdateView) |
| 删除 | 手写 POST 确认 | ProductDeleteView(DeleteView) |
4.2 CBV 请求分发:as_view()
path("create/", ProductCreateView.as_view(), name="product_create")
as_view() 返回一个函数,内部根据 request.method 调用不同方法:
| HTTP 方法 | 调用的方法 | 典型用途 |
|---|---|---|
| GET | get() | 展示表单或列表 |
| POST | post() | 提交表单 |
| PUT/PATCH | put() / patch() | REST 风格(少用) |
| DELETE | delete() | 删除确认 |
View 基类流程:
dispatch() → 按 method 分派到 get/post/...
↓
get() / post() → 调用 get_context_data()、form_valid() 等钩子
↓
render_to_response() / redirect
4.3 ListView:商品列表
catalog/views.py(CBV 版本,可与函数版二选一或并存教学):
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.urls import reverse_lazy
from django.views.generic import (
CreateView,
DeleteView,
ListView,
UpdateView,
)
from .forms import ProductForm
from .models import Product
class ProductListView(LoginRequiredMixin, ListView):
model = Product
template_name = "catalog/product_list.html"
context_object_name = "products"
paginate_by = 20 # ch06 详讲分页,此处先预留
def get_queryset(self):
qs = Product.objects.filter(is_active=True).select_related("category")
return qs.order_by("-created_at")
| 属性 | 作用 |
|---|---|
model | 自动提供 object_list / products |
template_name | 模板路径 |
context_object_name | 模板中变量名 |
get_queryset() | 自定义过滤、排序 |
4.4 CreateView:新建商品
class ProductCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
model = Product
form_class = ProductForm
template_name = "catalog/product_form.html"
permission_required = "catalog.add_product"
success_url = reverse_lazy("product_list")
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["title"] = "新建商品"
ctx["submit_label"] = "创建"
return ctx
def form_valid(self, form):
messages.success(self.request, f"商品「{form.instance.name}」创建成功。")
return super().form_valid(form)
执行链:post() → form_valid() → save() → redirect(success_url)
| 钩子 | 时机 | 典型用途 |
|---|---|---|
get_context_data() | 渲染前 | 追加 title、面包屑 |
form_valid() | 校验通过 | 消息提示、发信号 |
form_invalid() | 校验失败 | 自定义错误处理 |
get_success_url() | 保存成功后 | 动态跳转地址 |
4.5 UpdateView:编辑商品
class ProductUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = Product
form_class = ProductForm
template_name = "catalog/product_form.html"
permission_required = "catalog.change_product"
success_url = reverse_lazy("product_list")
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["title"] = "编辑商品"
ctx["submit_label"] = "保存"
ctx["product"] = self.object
return ctx
def form_valid(self, form):
messages.success(self.request, f"商品「{form.instance.name}」已更新。")
return super().form_valid(form)
UpdateView 在 get() 时自动用 self.object(根据 pk 或 slug_url_kwarg)填充表单。
4.6 DeleteView:删除商品
catalog/templates/catalog/product_confirm_delete.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>确认删除</title>
</head>
<body>
<h1>确认删除商品?</h1>
<p>将删除:<strong>{{ object.name }}</strong>({{ object.sku }})</p>
<form method="post">
{% csrf_token %}
<button type="submit">确认删除</button>
<a href="{% url 'product_list' %}">取消</a>
</form>
</body>
</html>
class ProductDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
model = Product
template_name = "catalog/product_confirm_delete.html"
permission_required = "catalog.delete_product"
success_url = reverse_lazy("product_list")