现在位置: 首页 > Django 教程 > 正文

类视图(CBV)重构

本章你将学习 Django 的类视图(CBV),用更简洁的代码重构首页和详情页。


FBV vs CBV

到目前为止,我们所有的视图都是用函数写的(Function-Based View,FBV)。

Django 还提供了 类视图(Class-Based View,CBV),用类来封装视图逻辑。

特性FBV(函数视图)CBV(类视图)
代码量灵活,但常用逻辑需要重复写继承通用视图后只需几行代码
可读性线性流程,逻辑清晰继承链需要追踪,但熟悉后一目了然
复用性通过函数封装通过 Mixin 组合(Django 的特色)
适用场景逻辑简单、流程清晰的页面增删改查这类模式化的页面
HTTP 方法分发手动 if request.method == 'POST'自动分发到 get() / post() 方法

两者没有绝对的优劣,Django 项目通常是 FBV 和 CBV 混用:复杂逻辑用 FBV,标准 CRUD 用 CBV。


ListView — 列表页重构

首页的 index 视图本质是:查询文章列表 → 渲染模板

这类模式化的逻辑,Django 的 ListView 已经帮你写好了。

实例

# 文件路径:blog/views.py
from django.views.generic import ListView
from django.db.models import Q
from .models import Post, Category

class PostListView(ListView):
    """博客首页:继承 ListView,展现文章列表"""
    model = Post                           # 指定模型
    template_name = 'blog/index.html'       # 指定模板(默认:blog/post_list.html)
    context_object_name = 'posts'           # 模板中使用的变量名(默认:object_list)
    paginate_by = 12                        # 每页 12 条(分页)
    ordering = ['-created_at']             # 排序

    def get_queryset(self):
        """重写查询方法:支持分类筛选 + 关键词搜索"""
        queryset = super().get_queryset()

        # 分类筛选
        self.category_slug = self.request.GET.get('category', '')
        if self.category_slug:
            queryset = queryset.filter(category__slug=self.category_slug)

        # 关键词搜索
        self.keyword = self.request.GET.get('q', '')
        if self.keyword:
            queryset = queryset.filter(
                Q(title__icontains=self.keyword) |
                Q(summary__icontains=self.keyword)
            )

        return queryset

    def get_context_data(self, **kwargs):
        """重写上下文方法:往模板中注入额外数据"""
        context = super().get_context_data(**kwargs)
        context['categories'] = Category.objects.all()
        context['category_slug'] = self.category_slug
        context['keyword'] = self.keyword
        context['title'] = 'RUNOOB 博客 - 首页'
        return context

对比一下重构前后的代码量:FBV 约 40 行 → CBV 约 30 行,而且逻辑被清晰地划分到不同方法中。


DetailView — 详情页重构

实例

# 文件路径:blog/views.py 新增
from django.views.generic import DetailView

class PostDetailView(DetailView):
    """文章详情页:继承 DetailView"""
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'

    # pk_url_kwarg:URL 中传递主键的参数名(默认是 pk,这里显式声明一下)
    pk_url_kwarg = 'pk'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'{self.object.title} - RUNOOB 博客'
        return context

更新路由配置

CBV 的路由写法与 FBV 略有不同——需要调用 .as_view()

实例

# 文件路径:blog/urls.py
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    # CBV:类后面加 .as_view() 将其转为视图函数
    path('', views.PostListView.as_view(), name='index'),
    path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),

    # 其他视图不变
    path('register/', views.register, name='register'),
    path('login/', auth_views.LoginView.as_view(
        template_name='blog/login.html',
        redirect_authenticated_user=True
    ), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('post/<int:pk>/favorite/', views.toggle_favorite, name='toggle_favorite'),
]

为什么 CBV 要用 .as_view()?Django 的 URL 配置期望的是一个可调用对象(函数)。.as_view() 将类视图转换成一个符合 Django 视图函数签名的可调用对象,内部负责根据 HTTP 方法(GET/POST)自动分发到类的 get() 或 post() 方法。


常用通用视图一览

视图模板默认名用途
ListView模型名_list.html展示对象列表
DetailView模型名_detail.html展示单条对象详情
CreateView模型名_form.html创建新对象(含表单)
UpdateView模型名_form.html更新现有对象
DeleteView模型名_confirm_delete.html确认删除对象
TemplateView需指定渲染静态模板(无数据查询)

Django 的通用视图覆盖了约 80% 的 Web 开发场景。遇到不满足需求的情况再退回 FBV。


LoginRequiredMixin

CBV 不能用 @login_required 装饰器(那是给函数用的),改用 LoginRequiredMixin

实例

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView

class FavoriteListView(LoginRequiredMixin, ListView):
    """收藏列表:需要登录才能访问"""
    model = Post
    template_name = 'blog/favorites.html'
    context_object_name = 'posts'
    ordering = ['-created_at']

    # LoginRequiredMixin 未登录用户会自动跳转到 settings.LOGIN_URL

    def get_queryset(self):
        # 只显示当前用户收藏的文章
        return self.request.user.favorite_posts.all().order_by('-created_at')

Mixin 的继承顺序很重要:LoginRequiredMixin 必须写在 ListView 前面。Python 多继承的解析顺序是从左到右,LoginRequiredMixin 先检查登录状态,通过后才继续执行 ListView 的逻辑。


什么时候用 CBV,什么时候用 FBV?

经验法则:

  • FBV:逻辑复杂、条件分支多、非标准流程的视图(如 toggle_favorite)
  • CBV:标准 CRUD 操作、逻辑可以清晰拆分为 get/post 的视图(如列表页、详情页)

toggle_favorite(收藏切换)这类操作逻辑简单但流程不同于常规 CRUD,FBV 更合适。

index 和 post_detail 这类标准的数据展示页面,CBV 可以显著减少样板代码。


本章小结

本章你学会了 Django 类视图的核心用法:ListView 展示列表、DetailView 展示详情、get_queryset/get_context_data 定制查询和上下文、as_view() 在路由中启用、LoginRequiredMixin 替代 login_required。

重构后的首页和详情页代码更短、结构更清晰。