摘要: Model 建好了,可前端怎么拿到数据?答案是 DRF(Django REST Framework)。今天搞定 DRF 的核心三板斧------Serializer 序列化器、ViewSet 视图集、Router 路由器。从最简单的列表接口开始,一步步加上分页、排序、字段过滤、全局响应格式封装,最后用 AI 自然语言一键生成完整 CRUD API。全程零成本。
Model 有了,前端怎么拿到数据?
Day 03 我们建好了商品、分类、订单这些 Model。数据躺在数据库里,好好的。
可前端同学过来了:"给我商品列表接口,我页面要用。"
你想想,总不能让前端直接查数据库吧?那就乱套了。你需要的是 API 接口------一套标准化的数据出口。前端发请求,后端返回 JSON,各干各的,互不干扰。
问题是怎么写?手写 JSON?手写分页?手写参数校验?
如果你裸写 Django 的 JsonResponse,几百行代码打不住。而用 DRF,核心只需要三个东西:
- Serializer:决定哪些字段能出去、长什么样
- ViewSet:把增删改查的逻辑封装成方法
- Router:自动帮你生成 URL 路由,不用手写一行 path
这就是 DRF 的三板斧。今天就用这三板斧,把商城的 API 搭起来。
DRF 三板斧是怎么工作的
动手之前,先搞明白这三个东西各自干什么。
Serializer(序列化器) 是数据出口的大门,负责两件事:
- 把 Model 对象变成前端能用的 JSON(序列化)
- 把前端传来的 JSON 变成数据库能存的 Model 对象(反序列化)
可以理解成翻译官------Python 对象和 JSON 之间的翻译。
ViewSet(视图集) 是业务逻辑的容器。它把"列表、详情、新增、修改、删除"这几个标准操作封装成现成方法,不用再写 def goods_list、def goods_detail、def goods_create 这些重复函数。
Router(路由器) 是 URL 自动生成器。把 ViewSet 注册给 Router,它就自动生成 /goods/、/goods/{id}/ 这些路由,不用手写一行 path()。
整个流程:
前端请求 GET /api/v1/goods/
↓
Router 匹配到 GoodsViewSet
↓
ViewSet 调用 list() 方法
↓
list() 查询数据库,拿到商品列表
↓
Serializer 把数据序列化为 JSON
↓
返回 {code:0, message:'success', data:[...]}
三行代码就能跑起来一个接口。不信?往下看。
动手:从最简到完整
第一步:装 DRF
bash
pip install djangorestframework django-filter
settings.py 的 INSTALLED_APPS 里加上:
python
INSTALLED_APPS = [
# ... 其他 app
'rest_framework',
'django_filters',
'shop', # 我们的商城 app
]
第二步:写第一个 Serializer
打开 shop/serializers.py,最简版本:
python
from rest_framework import serializers
from .models import Goods
class GoodsSerializer(serializers.ModelSerializer):
class Meta:
model = Goods
fields = '__all__' # 所有字段都输出
就这么简单。一个 ModelSerializer,告诉它对应哪个 Model、输出哪些字段。fields = '__all__' 表示全部字段返回。
返回的 JSON 大概长这样:
json
{
"id": 1,
"name": "无线蓝牙耳机",
"price": "299.00",
"stock": 500,
"status": "on_sale",
"category": 3,
"created_at": "2025-01-15T10:30:00Z"
}
注意 category 只返回了 ID 3,前端拿到这个数字是懵的------这分类叫啥?别急,后面优化。
第三步:写第一个 ViewSet
打开 shop/views.py:
python
from rest_framework import viewsets
from .models import Goods
from .serializers import GoodsSerializer
class GoodsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Goods.objects.all()
serializer_class = GoodsSerializer
三行核心代码。ReadOnlyModelViewSet 只暴露「列表」和「详情」两个接口,不支持增删改。商品用这个完全够------后台用 Admin 管理,API 只提供读取。
第四步:注册路由
打开 shop/urls.py(没有就新建):
python
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'goods', views.GoodsViewSet, basename='goods')
urlpatterns = [
path('api/v1/', include(router.urls)),
]
项目根 urls.py 里 include 进去:
python
urlpatterns = [
path('', include('shop.urls')),
]
启动服务,访问 http://127.0.0.1:8000/api/v1/goods/,就能看到商品列表 JSON。
第一个 API 接口,搞定。 前后加起来不到 20 行代码。
第五步:加分页和排序
接口能跑了,但数据一多就出问题。数据库有 1000 个商品,一个请求全返回?前端要卡死。
加分页器:
python
from rest_framework.pagination import PageNumberPagination
class StandardPagination(PageNumberPagination):
page_size = 20 # 默认每页 20 条
page_size_query_param = 'page_size' # 前端可指定每页条数
max_page_size = 100 # 最多 100 条,防恶意请求
ViewSet 里指定分页器,加上三大过滤后端:
python
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
class GoodsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Goods.objects.select_related('category').all()
serializer_class = GoodsSerializer
pagination_class = StandardPagination
filter_backends = [
DjangoFilterBackend, # 字段过滤:?status=on_sale
filters.SearchFilter, # 搜索:?search=手机
filters.OrderingFilter # 排序:?ordering=-price
]
filterset_fields = ['status', 'category', 'is_hot', 'is_new']
search_fields = ['name', 'subtitle', 'category__name']
ordering_fields = ['price', 'created_at', 'stock']
ordering = ['-created_at']
现在前端可以这样调:
# 按价格从低到高排序,只看上架商品
GET /api/v1/goods/?ordering=price&status=on_sale
# 搜索"手机",第2页,每页10条
GET /api/v1/goods/?search=手机&page=2&page_size=10
# 只看热销商品
GET /api/v1/goods/?is_hot=true
一行代码没多写,DRF 自动处理所有参数解析和 SQL 查询。
第六步:优化序列化器
前面说 category 只返回 ID,前端不友好。优化思路(核心逻辑):
python
class GoodsListSerializer(serializers.ModelSerializer):
"""列表接口用精简版"""
category_name = serializers.CharField(
source='category.name', read_only=True # 把外键变名字
)
class Meta:
model = Goods
fields = ['id', 'name', 'subtitle', 'main_image',
'price', 'market_price', 'stock',
'category_name', 'created_at']
class GoodsDetailSerializer(serializers.ModelSerializer):
"""详情接口用完整版,嵌套分类对象"""
category = CategoryBriefSerializer(read_only=True)
class Meta:
model = Goods
fields = '__all__'
设计点:列表接口不返回富文本 detail(可能很长),详情接口才返回。列表页加载快,详情页数据全。
ViewSet 里根据 action 动态选序列化器:
python
def get_serializer_class(self):
if self.action == 'retrieve':
return GoodsDetailSerializer
return GoodsListSerializer
第七步:全局响应格式封装
这是商城的强制规范。所有接口,不管成功失败,统一返回:
json
{
"code": 0,
"message": "success",
"data": { ... }
}
前端不用猜 HTTP 状态码,统一 HTTP 200 + code 判断结果。code: 0 成功,非 0 失败。
两个工具函数(核心逻辑):
python
def success_response(data=None, message='success'):
return {'code': 0, 'message': message, 'data': data}
def error_response(message='error', code=400, data=None):
return {'code': code, 'message': message, 'data': data}
然后在 ViewSet 的 list 方法里包装(核心逻辑):
python
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return Response(success_response(data={
'results': serializer.data,
'count': self.paginator.page.paginator.count,
'page': page.number,
}))
serializer = self.get_serializer(queryset, many=True)
return Response(success_response(data=serializer.data))
最终返回:
json
{
"code": 0,
"message": "success",
"data": {
"results": [
{"id": 1, "name": "无线蓝牙耳机", "price": "299.00", ...}
],
"count": 156,
"page": 1,
"page_size": 20
}
}
前端统一用 response.data.code === 0 判断成功,response.data.data.results 拿列表,协作效率高。
第八步:注册所有 Model
分类、商品、订单,三板斧流程一模一样。最终路由表:
| 方法 | URL | 功能 |
|---|---|---|
| GET | /api/v1/categories/ |
分类列表 |
| GET | /api/v1/categories/{id}/ |
分类详情 |
| GET | /api/v1/goods/ |
商品列表(分页/排序/过滤/搜索) |
| GET | /api/v1/goods/{id}/ |
商品详情 |
| GET | /api/v1/goods/hot/ |
热销商品 |
| GET | /api/v1/goods/new/ |
新品推荐 |
| GET | /api/v1/orders/ |
订单列表 |
| POST | /api/v1/orders/ |
创建订单 |
| GET | /api/v1/orders/{id}/ |
订单详情 |
| PUT | /api/v1/orders/{id}/ |
更新订单 |
| DELETE | /api/v1/orders/{id}/ |
删除订单 |
Router 自动生成的,一行 path 都不用写。
热销和新品是 ViewSet 的 @action 装饰器自定义接口(核心逻辑):
python
@action(detail=False, methods=['get'])
def hot(self, request):
"""热销商品:GET /api/v1/goods/hot/"""
queryset = self.get_queryset().filter(
is_hot=True, status='on_sale'
)[:20]
serializer = GoodsListSerializer(queryset, many=True)
return Response(success_response(data=serializer.data))
@action(detail=False) 表示集合级别的接口(不是某个具体商品的),methods=['get'] 表示只接受 GET。
用 AI 自然语言生成完整 CRUD
你可能觉得:"道理我都懂,但每次新写一个 Model 的 API,还是要写 Serializer + ViewSet + Router 这些重复代码。"
没错。这正是 AI 擅长的场景。
打开 Trae 或 Cursor,输入:
帮我给以下 Model 生成完整的 DRF API:
1. CategorySerializer - 分类序列化器,包含 goods_count 计算字段
2. GoodsListSerializer - 商品列表,精简字段,带 category_name
3. GoodsDetailSerializer - 商品详情,完整字段
4. OrderSerializer - 订单,嵌套 OrderItem
5. CategoryViewSet - 只读
6. GoodsViewSet - 只读,支持分页/排序/过滤/搜索
7. OrderViewSet - 完整 CRUD
8. 所有接口统一返回 {code:0, message:'success', data:...} 格式
要求:
- 使用 PageNumberPagination,默认 20 条/页
- GoodsViewSet 加 hot 和 new 两个自定义 action
- 代码要带中文注释
AI 会在 10 秒内生成完整的 serializers.py 和 views.py,质量跟手写几乎一样。
这就是 AI 编程的正确姿势------你理解原理,AI 生成代码。不用逐行手写,但要知道每一行在干什么,出问题知道怎么改。
几个小技巧:
- 给 AI 喂 Model 定义 :把
models.py贴进对话,生成的序列化器才准确 - 明确响应格式:告诉 AI 你要的 JSON 结构,不然它按 DRF 默认格式来
- 分模块生成:一次生成太多容易出错,先 Serializer 再 ViewSet 再 Router
- 人工审查:AI 生成的代码一定要过一遍,特别是权限控制、数据校验这些容易遗漏的点
接口测试:用浏览器就能验证
写完代码,不需要 Postman,浏览器就行。
访问 http://127.0.0.1:8000/api/v1/goods/------装了 DRF 它会自带漂亮的浏览界面,能看到分页、过滤、排序的参数输入框。
想要更直观的 JSON?加 ?format=json:
http://127.0.0.1:8000/api/v1/goods/?format=json
或者命令行:
bash
curl http://127.0.0.1:8000/api/v1/goods/ | python -m json.tool
验证全局响应格式:
json
{
"code": 0,
"message": "success",
"data": {
"results": [
{
"id": 1,
"name": "无线蓝牙耳机",
"price": "299.00",
"category_name": "数码",
"created_at": "2025-01-15T10:30:00Z"
}
],
"count": 42,
"page": 1,
"page_size": 20
}
}
完美。前端同学看到这个结构,可以直接开始对接。
光看不练假把式,试试这几个小改造
入门题 :给商品接口加"按价格区间过滤"。提示:在 ViewSet 重写 get_queryset(),用 price__gte 和 price__lte。
进阶题 :给分类接口加 children 字段,递归展示子分类。提示:在 CategorySerializer 加 SerializerMethodField,查 obj.children.all()。
挑战题 :把全局响应封装做成 DRF 自定义 Renderer,这样不用每个方法都手动包 success_response()。提示:继承 JSONRenderer,重写 render() 方法。
配套文件 day06_serializers.py / day06_views.py 可参考学习。DRF 官方文档:django-rest-framework.org。
本系列为 梅雅达编程笔记 原创,首发于 CSDN。有问题?评论区见