Django-drf框架之与模型类关联的序列化器

前言

这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题

于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。

微信小程序搜索:Python面试宝典

或可关注原创个人博客:https://lienze.tech

也可关注微信公众号,不定时发送各类有趣猎奇的技术文章:Python编程学习

关联序列化

普通的序列化,只是简单的对一张表进行处理,进行基本的单表增删改查,但是你一不小心遇到了多表的情况,比如一对一多对一多对多的情况该咋办呢

序列化

比如现在有两张很常见的关联表老师表学生表

  • 一个老师可以有一群学生,但是一个学生只能有一个老师
class Teacher(models.Model):

    name = models.CharField(max_length=30, verbose_name='老师名')
    age = models.IntegerField(verbose_name='年纪')
    
    def __str__(self):
        return self.name

class Student(models.Model):

    name = models.CharField(max_length=30, verbose_name='学生名')
    teacher = models.ForeignKey(
        Teacher, on_delete=models.SET_NULL, 
        null=True, blank=True, 
        verbose_name='关联老师'
    )

    def __str__(self):
        return self.name

学生表中,包含外键 teacher,外键可以通过如下一些方式进行序列化

PrimaryKeyRelatedField

关联表主键作为结果返回,使用 ***PrimaryKeyRelatedField***字段,该字段需要包含 ***queryset***或 ***read_only***属性

设置 **read_only代表该字段不进行反序列化校验

class StudentSer(serializers.ModelSerializer):
    teacher = serializers.PrimaryKeyRelatedField(read_only=True)
    class Meta:
        model = Student
        fields = '__all__'

编写返回一个学生的接口

class StudentDetail(APIView):
    def get(self, request):
        id_ = request.query_params.get('id')
        # 通过 GET 传参获取当前学生 ID
        try:
            stu = Student.objects.get(pk=id_)
        except Student.DoesNotExist:
            return Response({
    
    'error': '查不到结果'})
        ser = StudentSer(instance=stu)
        return Response(ser.data, status=200)

返回的数据类似如下效果

{
    
    
    "id": 1,
    "teacher": 1,
    "name": "小红"
}

StringRelatedField

关联表的***str***方法作为结果返回

class StudentSer(serializers.ModelSerializer):
    teacher = serializers.StringRelatedField()

    class Meta:
        model = Student
        fields = '__all__'

接口返回效果如下

{
    
    
    "id": 1,
    "teacher": "张老师", # teacher 表的__str__方法所返回的
    "name": "小红"
}

SlugRelatedField

使用关联表的指定字段作为结果返回

class StudentSer(serializers.ModelSerializer):
    teacher = serializers.SlugRelatedField(read_only=True, slug_field='name')

    class Meta:
        model = Student
        fields = '__all__'

效果就很直观了,返回的就是 ***slug_field***字段所对应的模型层里这个字段代表的值

{
    
    
    "id": 1,
    "teacher": 20,
    "name": "小红"
}

嵌套序列化器关联

上面几种办法只能返回单独定义的字段,适合某些返回数据简单的情况下,如果希望能返回当前关联字段的详细数据,那么可以通过额外定义关联表的序列化器,让当前外键字段使用关联表序列化器即可

class TeacherSer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = '__all__'



class StudentSer(serializers.ModelSerializer):
    teacher = TeacherSer(read_only=True)

    class Meta:
        model = Student
        fields = '__all__'

此时外键 teacher 字段所对应的序列化器是关联表的序列化器,需要返回的关联表数据可以通过另外这个序列化器进行单独定义,返回的接口数据如下

{
    
    
    "id": 1,
    "teacher": {
    
    
        "id": 1,
        "name": "张老师",
        "age": 20
    },
    "name": "小红"
}

关联反向序列化

反向关联:model_set

如果查询一个老师,想返回所有关联这个老师的学生们,通过序列化器可以使用**关联表模型_set字段完成,还要记得由于是多个学生关联,所以这个序列化字段要加 ***many=True***属性

查询所有老师的信息,并且连带返回所有老师所拥有的学生

class TeacherFullDetail(APIView):
    def get(self, request):
        teachers = Teacher.objects.all()
        ser = TeacherSerializer(instance=teachers, many=True)
        return Response(ser.data, status=200)

PrimaryKeyRelatedField

使用反向关联表的主键进行返回

class TeacherSerializer(serializers.ModelSerializer):
    
    student_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True)

    class Meta:
        model = Teacher
        fields = '__all__'

结果如下,反向关联字段使用关联表的主键进行返回, 并且封装在一个列表中

[
    {
    
    
        "id": 1,
        "student_set": [
            1,
            2
        ],
        "name": "张老师",
        "age": 20
    },
    {
    
    
        "id": 2,
        "student_set": [],
        "name": "李老师",
        "age": 18
    }
]

StringRelatedField

通过关联表**__str__**方法进行返回

class TeacherSerializer(serializers.ModelSerializer):
   
   student_set = serializers.StringRelatedField(read_only=True, many=True)

    class Meta:
        model = Teacher
        fields = '__all__'

结果就是反向关联外键的结果列表中包含的都是学生表***str***方法返回的结果

[
    {
    
    
        "id": 1,
        "student_set": [
            "小红",
            "小明"
        ],
        "name": "张老师",
        "age": 20
    },
    {
    
    
        "id": 2,
        "student_set": [],
        "name": "李老师",
        "age": 18
    }
]

SlugRelatedField

通过指定表中字段作为序列化字段的结果

class TeacherSerializer(serializers.ModelSerializer):
    student_set = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)

    class Meta:
        model = Teacher
        fields = '__all__'

结果如下所示

[
    {
    
    
        "id": 1,
        "student_set": [
            "小红",
            "小明"
        ],
        "name": "张老师",
        "age": 20
    },
    {
    
    
        "id": 2,
        "student_set": [],
        "name": "李老师",
        "age": 18
    }
]

嵌套序列化器关联

使用关联表的单独序列化器,和关联外键序列化器一样样的

class StudentSer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = '__all__'



class TeacherSerializer(serializers.ModelSerializer):
    student_set = StudentSer(many=True, read_only=True)

    class Meta:
        model = Teacher
        fields = '__all__'

效果就是学生的反向外键成为了具体的数据信息,而不是单独以指定字段或方式进行维护定义的

[
    {
    
    
        "id": 1,
        "student_set": [
            {
    
    
                "id": 1,
                "name": "小红",
                "teacher": 1
            },
            {
    
    
                "id": 2,
                "name": "小明",
                "teacher": 1
            }
        ],
        "name": "张老师",
        "age": 20
    },
    {
    
    
        "id": 2,
        "student_set": [],
        "name": "李老师",
        "age": 18
    }
]

外键字段反序列化

还是上面的例子,反序列化创建一个学生,使用模型序列化器很简单,默认是接受外键关联表的主键数据作为创建的依据,也就是代表着外键可以不像原始使用 ORM一样得传一个外键表数据 object,而是直接传一个 id就可以了

主键传值

在 **postman或其他手段进行测试时,直接传老师 id学生名、及其他所需字段即可

学生反序列化器不需要写任何字段,默认的关联字段会接受一个 id数据作为校验依据并创建

class StudentSer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = '__all__'

创建接口如下

class StudentCreate(APIView):
    def post(self, request):
        ser = StudentSer(data=request.data)
        if ser.is_valid():
            ser.save()
        error = ser.errors
        return Response({
    
    'msg': error if error else 'success'})

postman 测试时,只需要传如下数据即可

{
    
    
    "teacher": "2", # 外键直接传入 id 即可
    "name": "小黑"
}

外界传值

在序列化器使用过程中,可以通过 context 传递参数,这个参是一个字典数据对象,设置一些额外的值,使你的序列化器可以更灵活的操作参数

Serializer(instance=objects, data=request.data,context={
    
    })

现在序列化器不会自动验证接受并处理 ***teacher***字段,需要手动维护

class StudentSer(serializers.ModelSerializer):
    teacher = TeacherSer(read_only=True)
    # read_only 将该字段设置为只是序列化使用,不会经过反序列化处理
    class Meta:
        model = Student
        fields = '__all__'
        # fields = 'name' # 指明字段也可以实现屏蔽 teacher 字段反序列化的作用

通过之前的 Json数据提交 ***teacher***与 ***name***字段,此时 ***teacher***是无效的,不会被反序列化处理
但是我们可以单独把提交的 ***teacher***提取出来,传入 ***context***参数,重写 ***create***方法,在 ***create***方法中获取 ***context***参数,拿到 teacher,看下面的新接口

新的序列化器是这样的

class StuSer(serializers.ModelSerializer):
    teacher = TeacherSer(read_only=True)

    def create(self, validated_data):
        validated_data['teacher_id'] = self.context.get('teacher')
        ModelClass = self.Meta.model
        object = ModelClass._default_manager.create(**validated_data)
        return object

    class Meta:
        model = Student
        fields = '__all__'

视图稍微处理一下 ***teacher***数据

class StuCreate(APIView):
    def post(self, request):
        teacher = request.data.get('teacher')
        ser = StuSer(data=request.data, context={
    
    'teacher': teacher})
        if ser.is_valid():
            ser.save()
        error = ser.errors
        return Response({
    
    'msg': error if error else 'success'})

反序列化校验

在之前的操作中,我们已经接触到了 update 以及 create 方法进行更新创建的反序列化操作,验证主要通过调用***is_valid***方法触发

其实在反序列化过程中,还可以通过由 DRF 所指定的一系列校验方法对字段进行更加细致的验证,比如对于密码强弱判断,直接通过字段属性是无法做到的,但是可以放在以下所提供的校验方法中,自定义其校验规则

validate_字段

这种办法是单独字段的校验,可以对序列化器类对象所写的字段进行判断验证


  1. 在序列化器里定义校验字段的钩子方法:validate_字段
  2. 获取字段的数据,就是参数接收到的***value***值
  3. 验证不通过,抛出异常raise serializers.ValidationError(“校验不通过的说明”)
  4. 验证通过,返回字段数据

def validate_name(self, value):
    if len(value) == 1:
        raise serializers.ValidationError('名字长度无法为1')
    return value

validate

多字段联合校验,这种校验方法是可以一次性获取到所有序列化器字段里的全部值,可以进行一个统一整体校验判断


  1. 在序列化器定义**validate方法,参数为attrs
  2. attrs是所有数据组成的字典
  3. 不符合抛出异常:raise serializers.ValidationError(“校验不通过的说明”)
  4. 如果校验全部成功,那么返回attrs参数对象

def validate(self,attrs):
    name = attrs.get('name')
    if name != '张三':
        raise serializers.ValidationError('抱歉,不是张三不让注册')
    return attrs

验证器

封装重复校验规则,使用函数机制,定义校验规则,然后将函数作用至序列化器的字段上

def my_name_validate(value):
    if value != '张三':
        raise serializers.ValidationError('你不是张三,不可以创建')
    else:
        return value

生效该验证器

name = serializers.CharField(
    max_length=30,
    validators=[my_name_validate] # 生效校验器,可以有多个
)

权重:验证器方法 > validate_字段 > validate

猜你喜欢

转载自blog.csdn.net/HeroicLee/article/details/121529981