Django Serializer校验POST数据及更新逻辑的代码结构问题
位置选择结论
- headline的存在性校验:放在Serializer层实现,不要放在view里,也不要散在业务逻辑里
- 记录更新操作:简单场景收敛到Serializer的save方法,复杂场景抽独立service层,不要直接堆在view函数里
现有代码的问题
- 重复数据库查询:校验逻辑里查了一次News,view层更新前又查了一次,平白多了一次DB请求,属于无意义的性能浪费
- 异常捕获不严谨:validate里用裸
except,会把数据库连接异常、字段类型错误等所有报错都归为「headline不存在」,排查问题时根本找不到真实错误原因 - 逻辑分层混乱:view层直接写模型更新逻辑,后续其他接口需要复用「标记新闻已读」能力时只能复制粘贴代码,维护成本高
- 存在拼写错误:
News.object.get是错误写法,正确的管理器调用是News.objects.get
推荐实现代码
Serializer层实现
from rest_framework import serializers from .models import News class NewsMarkReadSerializer(serializers.Serializer): # 显式声明入参字段,DRF会自动做基础的类型、非空校验 headline = serializers.CharField(required=True, max_length=200) def validate_headline(self, value): # 单字段校验用DRF内置的validate_<字段名>钩子,比全局validate逻辑更清晰 try: # 校验时查到的新闻实例直接挂到serializer实例上,后续更新直接复用,避免重复查库 self.news_instance = News.objects.get(headline=value) except News.DoesNotExist: raise serializers.ValidationError("传入的headline对应新闻不存在") return value def save(self, **kwargs): # 校验通过后的更新逻辑统一放在save方法中,对外暴露统一调用入口 self.news_instance.viewed = True # 指定update_fields只更新需要的字段,减少SQL开销 self.news_instance.save(update_fields=["viewed"]) return self.news_instance
View层实现
from rest_framework.response import Response from rest_framework import status def post(self, request): serializer = NewsMarkReadSerializer(data=request.data) # 校验不通过直接抛出400响应,不需要手写if分支处理校验失败逻辑 serializer.is_valid(raise_exception=True) updated_news = serializer.save() # 返回更新后的结果即可 return Response( { "id": updated_news.id, "headline": updated_news.headline, "viewed": updated_news.viewed }, status=status.HTTP_200_OK )
额外说明
- 不要在Serializer的validate系列方法里做数据修改操作,validate的唯一职责就是校验输入是否合法,不要越权
- 如果后续标记已读的逻辑变复杂,比如需要给用户加积分、推送已读回执、记录操作日志,就把这部分逻辑抽成独立的
mark_news_as_read(news, user)之类的service函数,在Serializer的save方法里调用即可,view层代码不需要任何改动 - 不要为了「分层」硬套service层,简单CRUD场景直接用DRF自带的Serializer方法收敛逻辑足够,代码更简洁
内容的提问来源于stack exchange,提问作者user12965034
相关产品推荐
相关产品推荐

