Day 06 · 第一个 API 接口:从 Model 到 JSON

摘要: 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(序列化器) 是数据出口的大门,负责两件事:

  1. 把 Model 对象变成前端能用的 JSON(序列化)
  2. 把前端传来的 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 生成代码。不用逐行手写,但要知道每一行在干什么,出问题知道怎么改。

几个小技巧:

  1. 给 AI 喂 Model 定义 :把 models.py 贴进对话,生成的序列化器才准确
  2. 明确响应格式:告诉 AI 你要的 JSON 结构,不然它按 DRF 默认格式来
  3. 分模块生成:一次生成太多容易出错,先 Serializer 再 ViewSet 再 Router
  4. 人工审查: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。有问题?评论区见

相关推荐
yi0112 小时前
DAY28: LeetCode 111. 二叉树的最小深度
人工智能·笔记·python·算法·leetcode·二叉树·递归
诺伦2 小时前
从一键分发到 AI 自动运营:内容矩阵工具三代技术架构拆解 | RiseClaw玄策
python·agent·架构设计·增长运营·ai自动运营·内容营销自动化
有同事要进步3 小时前
LeetCode 14. 最长公共前缀:纵向扫描一次讲透
开发语言·python
XiaoMaqqqq3 小时前
目前正规的IP驱动产业新场景新工具哪个好
网络·python·网络协议·tcp/ip
cui_ruicheng3 小时前
FastAPI 应用开发(五)——SQLAlchemy ORM、APIRouter 与 Alembic 数据库迁移
python·fastapi·web
silvamux_3 小时前
豆包 API 调用教程:Python 接入可跑通示例
开发语言·python
卿卿的产品经理日记3 小时前
【AI产品经理实战】Day 37|拆开 JSON 与 EXE:数据都有格式
人工智能·笔记·python·json·产品经理
棉猴3 小时前
玩游戏学Python10-算术运算符应用2
开发语言·python·玩游戏·游戏编程
deepseek233 小时前
Dependabot匿名私有仓库开放后仍更新失败?组织级镜像的访问边界与排查实战
python·github·devops·软件供应链·dependabot