DRF学习03-反序列化校验、定制返回格式、多表反序列化

一、MySQL环境小知识点

  1. Django4+不再支持MySQL5.7,推荐使用 MySQL8.0
  2. 连接mysql两种库
    • pip install mysqlclient
    • 使用pymysql:需要额外写两行配置代码
  3. PATH环境变量:命令执行时,系统会按PATH里面的路径依次查找可执行程序。

二、反序列化校验(Serializer校验三层)

serializer.is_valid() 执行校验;校验通过数据存到validated_data;失败存到serializer.errors

1)字段本身校验(写在字段参数上)

age=serializers.IntegerField(min_value=1,max_value=110)
name=serializers.CharField(max_length=8,min_length=3)
school=serializers.CharField(max_length=8,min_length=3)

2)局部钩子:validate_字段名(self,value)

只针对某一个字段做自定义校验

  • value:字段经过基础校验之后的值
  • 校验不通过:抛出ValidationError("错误提示")
  • 校验通过:必须把valuereturn返回
from rest_framework.exceptions import ValidationError

def validate_name(self,value):
    # 不能姓刘
    if value.startswith('刘'):
        raise ValidationError('你不配姓刘')
    return value

3)全局钩子:validate(self, attrs)

针对多个字段联合校验

  • attrs:字典,所有字段完成字段校验、局部钩子校验后的干净数据
  • 校验失败抛ValidationError
  • 校验通过必须return attrs
def validate(self, attrs):
    # 人名前2字符和学校前2字符不能相同
    name = attrs.get('name')[:2]
    school = attrs.get('school')[:2]
    if name == school:
        raise ValidationError("人名和学校的前两个字符不能一样")
    return attrs

✅校验执行顺序:字段参数校验 → 局部钩子validate_xxx → 全局钩子validate

StudentSerializer完整示例(普通Serializer,不是ModelSerializer)

⚠️普通Serializer必须手动写create()、update();ModelSerializer不用手写。

class StudentSerializer(serializers.Serializer):
    age=serializers.IntegerField(min_value=1,max_value=110)
    name=serializers.CharField(max_length=8,min_length=3)
    school=serializers.CharField(max_length=8,min_length=3)

    def create(self, validated_data):
        return Student.objects.create(**validated_data)

    def update(self, instance, validated_data):
        for k,v in validated_data.items():
            setattr(instance,k,v)
        instance.save()
        return instance

    # 局部钩子
    def validate_name(self,value):
        if value.startswith("刘"):
            raise ValidationError("你不配姓刘")
        return value
    # 全局钩子
    def validate(self, attrs):
        name = attrs.get('name')[:2]
        school = attrs.get('school')[:2]
        if name == school:
            raise ValidationError("人名和学校的前两个字符不能一样")
        return attrs

views.py

class StudentView(APIView):
    def post(self,request):
        ser = StudentSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)

三、定制返回格式(序列化输出,给前端看)

1. source参数

作用:序列化字段映射模型的其他属性(普通字段、模型方法、@property属性)

# 1.映射模型普通字段
sch = serializers.CharField(source="school")

# ⚠️坑:字段名不能和source指向名字重名!
# school=serializers.CharField(source="school")  报错!

# 2.跨表取值(外键对象的属性)
# publish是外键,取publish对象的name
publish_name = serializers.CharField(source="publish.name")

# 3.读取模型@property装饰的方法
# models
# @property
# def new_school(self):
#     return "NB_"+self.school

sch = serializers.CharField(source="new_school")

2. SerializerMethodField 【重点】

序列化类中新增模型不存在的自定义字段;
语法:字段名 = serializers.SerializerMethodField()
配套方法必须叫 get_字段名(self,obj)

  • obj:当前正在序列化的模型对象,在方法内做逻辑,return什么,字段就输出什么。
publish_detail = serializers.SerializerMethodField()
def get_publish_detail(self,obj):
    return {"name":obj.publish.name,"addr":obj.publish.addr}

3. 一对多、多对多关系三种定制返回方案

模型:Book(图书),Publish(出版社,一对多外键),Author(作者,多对多)

方案 说明
方案1:模型写@property 在models写装饰属性,序列化用DictField/ListField接收
方案2:SerializerMethodField 在序列化类写get_xxx方法,写逻辑拼接字典列表,工作最常用
方案3:子序列化 单独写子序列化类;多对多必须加many=True

子序列化示例(多对多)

class AuthorSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField()

class BookSerializer(serializers.Serializer):
    name = serializers.CharField()
    authors = AuthorSerializer(many=True) #多对多,many=True不能丢

四、多表关联反序列化(新增带外键/多对多)

场景:新增图书,前端传publish_id(出版社主键)、authors:[1,2]作者id列表
反序列化接收主键ID,不能直接用子序列化(子序列化多用于序列化输出)

models

class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    publish = models.ForeignKey(to="Publish",on_delete=models.SET_NULL,null=True)
    authors = models.ManyToManyField(to="Author")

class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

serializer.py

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    publish_id = serializers.IntegerField() #接收外键id
    authors = serializers.ListField() #接收作者id列表 [1,2]

    def create(self, validated_data):
        # validated_data: {name:"xxx",price:88,publish_id:1,authors:[1,2]}
        authors = validated_data.pop("authors") # 先把多对多数据弹出来
        book = Book.objects.create(**validated_data) #保存一对多外键
        book.authors.add(*authors) #多对多添加中间表记录
        return book

views.py

class BookView(APIView):
    def post(self,request):
        ser = BookSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({"code":100,"msg":"创建成功"})
        return Response(ser.errors)

重要细节

  1. 前端传:{"name":"三国演义","price":88,"publish_id":1,"authors":[1,2]}
  2. 前端多传序列化类没有定义的字段,会直接丢弃,不会进入validated_data
  3. 多对多新增:先pop()拿出列表,创建主表对象,再用.add(*列表)添加多对多关系。

📌重点记忆总结

  1. 校验三层:字段参数校验 → 局部钩子validate_字段名 → 全局钩子validate;钩子校验失败抛ValidationError。
  2. source:映射模型字段、跨表、模型@property方法;字段名不能与source目标重名。
  3. SerializerMethodField():自定义输出字段,配套get_xxx(self,obj),obj是序列化对象。
  4. 关联查询返回:三种方案:模型@property、SerializerMethodField、子序列化(多对多要many=True)。
  5. 多表反序列化新增:接收id,pop出多对多数据;obj.add(*ids)保存多对多中间表。
  6. 普通Serializer必须手写create/update;ModelSerializer自动生成。

课后作业

  1. 搭建MySQL8环境
  2. 敲完source、三种定制返回格式代码
  3. 实现多表关联修改(put)
  4. 练习:Author-AuthorDetail、Book完整5个接口,增加自定义校验。

如果你需要,我可以把这一节的完整可运行整套代码整理一份给你直接复制测试。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容