4.2 类视图与通用视图


文档摘要

4.2 类视图与通用视图 本节摘要:类视图(CBV)把"同一个地址的不同 HTTP 方法"组织成一个类,通用视图再进一步——列表、详情、创建、更新、删除五种页面框架都替你写好了。本节讲 CBV 的分发机制、通用视图的定制套路、Mixin 组合的艺术,以及 FBV 与 CBV 的取舍标准。接口化返回 JSON 的场景也在本节一并覆盖。 从重复到抽象 用函数视图写"创建文章"页,要手写一大段模式:GET 时渲染空表单,POST 时校验保存、失败重渲染。这类"按请求方法分支"的代码在增删改页面里高度重复。类视图的解法是把方法变成类的方法: View 基类的 asview 方法在路由里充当入口,它按请求方法名查找并调用对应的方法——GET 找 get、POST 找 post。

4.2 类视图与通用视图

本节摘要:类视图(CBV)把"同一个地址的不同 HTTP 方法"组织成一个类,通用视图再进一步——列表、详情、创建、更新、删除五种页面框架都替你写好了。本节讲 CBV 的分发机制、通用视图的定制套路、Mixin 组合的艺术,以及 FBV 与 CBV 的取舍标准。接口化返回 JSON 的场景也在本节一并覆盖。

从重复到抽象

用函数视图写"创建文章"页,要手写一大段模式:GET 时渲染空表单,POST 时校验保存、失败重渲染。这类"按请求方法分支"的代码在增删改页面里高度重复。类视图的解法是把方法变成类的方法:

from django.views import View class ArticleCreateView(View): def get(self, request): form = ArticleForm() return render(request, "blog/article_form.html", {"form": form}) def post(self, request): form = ArticleForm(request.POST) if form.is_valid(): article = form.save() return redirect("blog:article-detail", pk=article.pk) return render(request, "blog/article_form.html", {"form": form})

View 基类的 as_view 方法在路由里充当入口,它按请求方法名查找并调用对应的方法——GET 找 get、POST 找 post。方法不存在时返回 405,而不是 500,这是框架替你做的语义区分。

通用视图:三行一个页面

墨迹博客的列表页与详情页,用通用视图几乎零代码:

from django.views.generic import ListView, DetailView class ArticleListView(ListView): model = Article queryset = Article.published.select_related("author", "category") template_name = "blog/list.html" context_object_name = "articles" paginate_by = 10 class ArticleDetailView(DetailView): model = Article template_name = "blog/detail.html" def get_queryset(self): return Article.published.select_related("author")

ListView 自带分页:第 paginate_by 条属性一写,模板里就多了 page_obj 可用,URL 上加个页码参数即可翻页——第 3 章担心的深分页性能,从查询层面治理,与这里的分页器各管一段。DetailView 按路由里的 pk 或 slug 自动取对象,默认模板名、默认上下文变量名都有约定(article_list、article),覆盖它们只是为了让模板可读。

创建与更新交给 CreateView、UpdateView,删除交给 DeleteView。它们与第 6 章的 ModelForm 深度联动,届时展开。

定制的三个抓手

通用视图的黑盒感是初学者的主要顾虑。其实定制入口非常清晰,三个方法覆盖九成需求:

class ArticleListView(ListView): # 抓手一:换掉数据源(不写 model 而写 queryset) queryset = Article.published.all() # 抓手二:按请求上下文动态构造查询 def get_queryset(self): qs = Article.published.select_related("author") tag = self.kwargs.get("tag") # 路由捕获的参数 if tag: qs = qs.filter(tags__slug=tag) return qs # 抓手三:往模板塞额外上下文 def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["hot_tags"] = Tag.objects.annotate( n=Count("articles")).order_by("-n")[:10] return ctx

套路是固定的:想让页面"看什么数据"动起来,改 get_queryset;想让模板"多拿点什么",改 get_context_data;想在保存前后插逻辑,改 form_valid。三个方法名覆盖了通用视图定制的日常。

Mixin:能力拼装

类视图真正的威力在组合。LoginRequiredMixin 把"未登录踢到登录页"变成一行声明:

from django.contrib.auth.mixins import LoginRequiredMixin class ArticleCreateView(LoginRequiredMixin, CreateView): model = Article fields = ["title", "body", "category", "tags"] # 未登录访问自动跳登录页 第7章细讲

Mixin 是"可插拔的父类",每个 Mixin 贡献一小块能力。写自己的 Mixin 也不神秘——比如给全站详情页加浏览计数:

class ViewCounterMixin: def get(self, request, *args, **kwargs): response = super().get(request, *args, **kwargs) self.object.increase_views() # 模型上的计数方法 return response

多个 Mixin 排列时,Python 的方法解析顺序决定 super 链的走向,习惯上把"功能 Mixin 放前面、最泛的基类放最后"。

继承链虽然强大,但要警惕一种失败模式:Mixin 越叠越多,get_context_data 与 get_queryset 被多层覆写,后来者很难推断"这个页面的上下文到底从哪几层拼来"。经验阈值是三层——功能 Mixin 超过三层还停不下来,说明这个页面承载的职责已经过多,更该做的是拆视图而不是继续叠能力。墨迹博客的发文页曾同时挂登录校验、所有权校验、草稿计数、浏览计数四个 Mixin,重构时合并为一个内容相关的基类加两个通用 Mixin,可读性立刻回来。代码结构与项目结构一样,膨胀到一定程度就该主动收敛,而不是等到看不懂那天被动重写。

FBV 还是 CBV

别站队,按场景分配:

场景 更顺手的选择 理由
一次性页面、逻辑独特的接口 函数视图 一眼读完,无继承追踪
列表/详情/增删改同质页面 通用视图 三行一页,定制点固定
多方法接口(含 JSON 输出) View 或 APIView 风格 方法分发天然对齐 HTTP 语义
多个页面共享同一能力 Mixin 组合 能力插拔,避免复制粘贴

给 JSON 接口做个轻量示意:同一地址返回 JSON 还是 HTML,可以按请求头的接受类型分支。接口需求一旦系统化(鉴权、序列化、分页协议统一),就该请出 Django REST framework——它本质上是 CBV 思想在 API 领域的延伸,学会本节的分发与 Mixin,再去读它的序列化器与视图集会非常顺。

⚠️ 类视图的坑主要在"看不见的执行链":出问题时别猜,翻出对应视图的调度源码(django.views.generic 包内),按方法解析顺序读一遍 super 链,问题几乎总在"你以为被调用的方法其实被跳过"。

本节要点回顾

  • CBV 按方法组织代码,View 的 as_view 入口按请求方法分发。
  • 通用视图三行一页,定制三抓手:queryset、get_queryset、get_context_data。
  • Mixin 是能力拼装,共享逻辑做成 Mixin 而非复制粘贴。
  • FBV 与 CBV 按场景混用,逻辑独特用函数,同质页面用类。

视图讲完,请求链路的最后一层还没揭幕——所有请求真正的第一站:中间件。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U