- 文档
- 教程
【免费下载链接】Python-100-Days
Python - 100天从新手到大师
本篇是 Python-100-Days 教程第 55 天(Day46-60/55.RESTful架构和DRF进阶.md)的实战进阶指南,讲解在 Django 项目中用 DRF(Django REST Framework)的 CBV(基于类的视图)取代 FBV 来构建 REST 风格数据接口。读完本篇,你将掌握如何通过继承ListAPIView快速实现 GET 接口、如何用ModelViewSet一行代码集齐增删改查全套接口、如何通过路由器注册 ViewSet 完成 URL 映射,以及如何配置全局/自定义分页器并用django-filter对资源列表做灵活筛选。
1. 为什么从 FBV 走向 CBV:回顾与对比
在第 54 天的入门教程(Day46-60/54.RESTful架构和DRF入门.md)中,我们使用 FBV(基于函数的视图)配合@api_view装饰器编写数据接口:
from rest_framework.decorators import api_view from rest_framework.response import Response @api_view(('GET', )) def show_subjects(request: HttpRequest) -> HttpResponse: subjects = Subject.objects.all().order_by('no') # 创建序列化器对象并指定要序列化的模型 serializer = SubjectSerializer(subjects, many=True) # 通过序列化器的data属性获得模型对应的字典并通过创建Response对象返回JSON格式的数据 return Response(serializer.data)FBV 的优势是高度可定制:视图函数执行什么代码、返回什么数据都由开发者自由控制。缺点是每次新增一个接口都要手写请求方法检查、数据获取、序列化、响应组装等样板代码。
而 CBV 将“接收请求 → 取数据 → 序列化 → 返回 JSON”这条链路封装在基类中,开发者只需声明“数据从哪里来”和“数据如何序列化”。这就是本篇文章要介绍的核心方法。第 54 天还介绍了配套的序列化器(Day46-60/54.RESTful架构和DRF入门.md),包括SubjectSerializer、SubjectSimpleSerializer和TeacherSerializer,本文的 CBV 示例会直接复用这些序列化器。
2. 使用 CBV:继承APIView的子类
2.1 继承ListAPIView实现 GET 列表接口
DRF 提供了一套基于通用类的视图(GenericAPIView 体系)。修改投票项目中的polls/views.py,去掉show_subjects视图函数,添加一个继承自ListAPIView的SubjectView类:
from rest_framework.generics import ListAPIView class SubjectView(ListAPIView): # 通过queryset指定如何获取学科数据 queryset = Subject.objects.all() # 通过serializer_class指定如何序列化学科数据 serializer_class = SubjectSerializer关键点说明:
ListAPIView能接收 GET 请求,内部封装了获取数据列表并返回 JSON 数据的get方法;- 由于
get方法已被父类实现,我们只需要声明两件事:用queryset属性指定如何获取数据,用serializer_class属性指定如何序列化数据。
ListAPIView是APIView的子类。APIView体系下还有大量现成的子类,覆盖了 CRUD 的各种组合:
| 通用视图类 | 支持的请求方法 | 封装的核心操作 |
|---|---|---|
CreateAPIView | POST | 新建资源 |
ListAPIView | GET | 获取资源列表 |
RetrieveAPIView | GET | 获取单个资源 |
UpdateAPIView | PUT / PATCH | 更新资源 |
DestroyAPIView | DELETE | 删除资源 |
ListCreateAPIView | GET / POST | 列表 + 新建 |
RetrieveUpdateDestroyAPIView | GET / PUT / PATCH / DELETE | 单资源全操作 |
2.2 修改 URL 映射
使用上面的SubjectView,需要修改urls.py:
urlpatterns = [ path('api/subjects/', SubjectView.as_view()), ]这里的as_view()是 Django 基于类的视图的标准入口,DRF 的通用视图同样遵循这一约定。相比 FBV 的做法(Day46-60/54.RESTful架构和DRF入门.md 中的path('api/teachers/', show_teachers)),CBV 只是把视图函数换成了SubjectView.as_view(),代码量却减少了一大截。
2.3APIView的策略属性与默认配置
APIView是 DRF 所有视图类的根基。从源码结构看(参见 Day91-100/95.使用Django开发商业项目.md 中对APIView的代码展示),它把渲染器、解析器、认证、限流、权限等策略全部抽象为可覆盖的类属性:
class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASSDRF 的默认策略集中在REST_FRAMEWORK配置字典中,常用的默认项包括:
REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ), 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.AllowAny', ), 'DEFAULT_THROTTLE_CLASSES': (), }在具体视图类中覆盖同名属性即可定制某个接口的行为,例如:
class EstateViewSet(CacheResponseMixin, ModelViewSet): # 通过queryset指定如何获取数据(资源) queryset = Estate.objects.all().select_related('district').prefetch_related('agents') # 通过serializer_class指定如何序列化数据 serializer_class = EstateSerializer # 指定根据哪些字段进行数据筛选 filter_fields = ('district', 'name') # 指定根据哪些字段对数据进行排序 ordering_fields = ('hot', ) # 指定用于进行用户身份验证的类 authentication_classes = (MyAuthentication, )(以上片段引自 Day91-100/95.使用Django开发商业项目.md,展示了同一个 CBV 同时覆盖分页、筛选、排序和认证策略的实际用法。)
3. 使用 CBV:继承ModelViewSet一键集齐 CRUD
3.1 极简的SubjectViewSet
如果学科数据接口需要支持 GET、POST、PUT、PATCH、DELETE 请求,完成对学科资源的获取、新增、更新、删除全套操作,更简单的做法是继承ModelViewSet。再次修改polls/views.py,去掉SubjectView类,添加SubjectViewSet:
from rest_framework.viewsets import ModelViewSet class SubjectViewSet(ModelViewSet): queryset = Subject.objects.all() serializer_class = SubjectSerializer从源码结构看,ModelViewSet共有 6 个父类(Day46-60/55.RESTful架构和DRF进阶.md 给出了其类定义),其中前 5 个 mixin 父类分别实现对 POST、GET、PUT/PATCH、DELETE、GET 五种操作的支持:
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): """ A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. """ pass每个父类对应的核心方法是:
| Mixin 父类 | 对应操作方法 | 支持的 HTTP 动词 | 业务语义 |
|---|---|---|---|
CreateModelMixin | create() | POST | 新增学科 |
RetrieveModelMixin | retrieve() | GET | 获取指定学科 |
UpdateModelMixin | update()/partial_update() | PUT / PATCH | 更新学科 |
DestroyModelMixin | destroy() | DELETE | 删除学科 |
ListModelMixin | list() | GET | 获取学科列表 |
由于父类已经实现了这些方法,我们几乎没有编写任何业务代码就完成了学科数据全套接口的开发——只需像继承APIView子类时一样声明queryset(如何取数据)和serializer_class(如何序列化数据)。
3.2 通过DefaultRouter注册路由
ModelViewSet相当于多个视图函数的汇总,所以不能像普通视图那样直接as_view(),需要先创建一个路由器并通过它注册SubjectViewSet,再把注册生成的 URL 追加到urlpatterns:
from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('api/subjects', SubjectViewSet) urlpatterns += router.urls路由器不仅生成了列表、详情、新建、更新、删除等标准 URL,还会自动附加一个.json格式的渲染后缀(如果启用),这让 ViewSet 的接口地址天然具有 REST 风格。
3.3ReadOnlyModelViewSet与只读接口
除ModelViewSet外,DRF 还提供了ReadOnlyModelViewSet。从名字即可看出,它是只读视图的集合——继承它定制的数据接口只能支持 GET 请求,也就是获取单个资源和资源列表。这在只需要对外提供查询接口、不需要写操作的场景(如展示型数据、报表数据)中非常适用。在商业项目示例(Day91-100/95.使用Django开发商业项目.md)中,HouseInfoViewSet正是通过ReadOnlyModelViewSet实现的只读房源接口。
4. 数据分页:三种内置分页器与自定义分页
使用 GET 请求获取资源列表时,通常不会一次性加载全部数据,除非数据量真的很小。大多数列表类接口都支持分页展示——通过指定页码(或类似页码的标识)和页面大小(一次加载多少条数据)来获取不同的数据。
分页的实现方式有多种:可以手动对QuerySet对象做切片操作,也可以利用 Django 框架封装的Paginator和Page对象。使用 DRF 时最方便的做法是在 Django 配置文件中修改REST_FRAMEWORK,配置默认分页类和页面大小:
REST_FRAMEWORK = { 'PAGE_SIZE': 10, 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination' }DRF 内置了三种分页器,适用场景各不相同:
| 分页器 | 查询参数 | 特点与适用场景 |
|---|---|---|
PageNumberPagination | page(页码)、page_size(页面大小) | 最直观,适合大多数业务列表接口 |
LimitOffsetPagination | limit(数量)、offset(偏移量) | 类似 SQL 的 LIMIT/OFFSET,适合与已有参数风格一致的系统 |
CursorPagination | cursor(游标) | 基于游标的分页,避免使用页码分页时暴露网站的数据体量,适合数据不断新增、对体量敏感的接口 |
4.1 自定义分页器
如果不希望使用配置文件中的默认分页设定,可以在视图类中添加pagination_class属性重新指定分页器,通常指定为自定义分页器:
from rest_framework.pagination import PageNumberPagination class CustomizedPagination(PageNumberPagination): # 默认页面大小 page_size = 5 # 页面大小对应的查询参数 page_size_query_param = 'size' # 页面大小的最大值 max_page_size = 50class SubjectView(ListAPIView): # 指定如何获取数据 queryset = Subject.objects.all() # 指定如何序列化数据 serializer_class = SubjectSerializer # 指定如何分页 pagination_class = CustomizedPagination参数语义说明:
page_size:默认页面大小,即未显式指定时的每页数据条数;page_size_query_param:允许客户端通过哪个查询参数覆盖页面大小,这里设置为size,即请求?page=2&size=20可获取第 2 页、每页 20 条;max_page_size:客户端能指定的页面大小上限,防止一次性请求过量数据,这里上限为 50。
如果不希望数据分页,可以将pagination_class属性设置为None来取消默认的分页器。在 Day91-100/95.使用Django开发商业项目.md 中可以看到HouseTypeViewSet就通过pagination_class = None取消了分页(因为户型这类数据量很小的资源无需分页)。
5. 数据筛选:重写get_queryset与django-filter
5.1 通过重写get_queryset实现请求参数筛选
如果需要使用 CBV 定制获取老师信息的数据接口,同样可以通过继承ListAPIView实现。但因为要通过指定的学科来获取对应的老师信息,需要对老师数据做筛选而非直接返回全部老师。从请求中获取学科编号并据此筛选,可以重写get_queryset方法:
class TeacherView(ListAPIView): serializer_class = TeacherSerializer def get_queryset(self): queryset = Teacher.objects.defer('subject') try: sno = self.request.GET.get('sno', '') queryset = queryset.filter(subject__no=sno) return queryset except ValueError: raise Http404('No teachers found.')这里有几个值得注意的细节:
defer('subject')延迟加载外键字段,避免在列表场景下不必要的关联查询,与入门篇中 FBV 版show_teachers的defer('subject')用法一致;- 通过
self.request.GET.get('sno')从查询字符串中读取学科编号; - 使用
filter(subject__no=sno)完成跨模型字段筛选; - 捕获
ValueError并抛出Http404,对非法参数返回 404 而不是 500,保证接口的健壮性。
5.2 用django-filter实现声明式筛选
除了手动重写get_queryset,还可以使用三方库django-filter配合 DRF 实现数据筛选。先安装:
pip install django-filter然后在INSTALLED_APPS中注册应用,并在REST_FRAMEWORK中配置默认筛选后端(这里同时配置了排序后端):
INSTALLED_APPS = [ 'django_filters', ] REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ( 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.OrderingFilter', ), }(上述配置示例引自 Day91-100/95.使用Django开发商业项目.md 中的“过滤数据”小节。)
为视图类配置filter_backends属性并指定使用DjangoFilterBackend后,可以用filter_fields属性或filterset_class属性来指定如何筛选数据。
方式一:用filter_fields直接声明可按哪些模型字段筛选:
from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter from rest_framework.generics import RetrieveAPIView, ListCreateAPIView class EstateView(RetrieveAPIView, ListCreateAPIView): queryset = Estate.objects.all().select_related('district').prefetch_related('agents') serializer_class = EstateSerializer filter_backends = (DjangoFilterBackend, OrderingFilter) filter_fields = ('name', 'district') ordering = ('-hot', ) ordering_fields = ('hot', 'estateid')方式二:用filterset_class定义更灵活的筛选器,支持条件运算(模糊匹配、大于等于、小于等于等):
from django_filters import rest_framework as drf from common.models import HouseInfo class HouseInfoFilter(drf.FilterSet): """自定义房源数据过滤器""" title = drf.CharFilter(lookup_expr='starts') dist = drf.NumberFilter(field_name='district') min_price = drf.NumberFilter(field_name='price', lookup_expr='gte') max_price = drf.NumberFilter(field_name='price', lookup_expr='lte') type = drf.NumberFilter() class Meta: model = HouseInfo fields = ('title', 'district', 'min_price', 'max_price', 'type')(以上HouseInfoFilter及其在视图中的挂载示例均引自 Day91-100/95.使用Django开发商业项目.md。)
然后在视图类中挂载:
class HouseInfoViewSet(CacheResponseMixin, ReadOnlyModelViewSet): queryset = HouseInfo.objects.all() \ .select_related('type', 'district', 'estate', 'agent') \ .prefetch_related('tags').order_by('-pubdate') serializer_class = HouseInfoSerializer filter_backends = (DjangoFilterBackend, OrderingFilter) filterset_class = HouseInfoFilter ordering = ('price',) ordering_fields = ('price', 'area')可以看到,filterset_class比起filter_fields表达能力更强:
CharFilter(lookup_expr='starts')实现标题前缀匹配;NumberFilter(field_name='price', lookup_expr='gte')/lte实现价格区间查询(min_price、max_price);- 客户端请求形如
/api/houses/?min_price=100&max_price=500&type=3即可完成组合筛选。
6. 小结:从入门到进阶的接口开发路径
综合第 54 天(Day46-60/54.RESTful架构和DRF入门.md)与本文的内容,使用 DRF 定制 RESTful 数据接口已经形成了一条清晰的进阶路径:
| 实现方式 | 代码量 | 灵活性 | 适用场景 |
|---|---|---|---|
FBV +@api_view | 中 | 高 | 逻辑高度定制、返回结构特殊的接口 |
CBV(继承ListAPIView等) | 低 | 中 | 标准 CRUD 接口,少量定制 |
CBV(继承ModelViewSet/ReadOnlyModelViewSet) | 极低 | 中 | 资源完整 CRUD 或只读接口,配合DefaultRouter自动生成路由 |
分页 + 筛选(django-filter) | 低 | 高 | 列表类接口的数据体量控制与组合条件查询 |
在完整商业项目中(参见 Day91-100/95.使用Django开发商业项目.md),这套方法论被整体复用:DefaultRouter注册HouseTypeViewSet、HouseInfoViewSet继承ReadOnlyModelViewSet并通过filterset_class实现房源组合筛选,再配合缓存、认证等策略,构成了前后端分离项目后端 API 的完整骨架。掌握本文的 CBV、ViewSet、分页与筛选四件套,也就掌握了 DRF 从入门到进阶的核心能力。
- 文档
- 教程
【免费下载链接】Python-100-Days
Python - 100天从新手到大师
相关推荐
mesop表格组件进阶:排序、筛选与分页全实现
mesop表格组件进阶:排序、筛选与分页全实现 引言 你还在为Python Web应用中的表格功能实现而烦恼吗?从基础的数据展示到复杂的排序、筛选和分页,Mes
前端后端Web框架WinUIEx自定义背景效果:创建视觉震撼的Windows应用界面终极指南
WinUIEx自定义背景效果:创建视觉震撼的Windows应用界面终极指南 想要为你的Windows应用添加令人惊艳的视觉效果吗?WinUIEx提供了强大的自定
UI库/组件JAWS核心功能深度解析:从网络信息到服务漏洞的完整检测清单
JAWS核心功能深度解析:从网络信息到服务漏洞的完整检测清单 JAWS (Just Another Windows Enum Script)是一款专为渗透测试和
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考