drf序列化器之模型序列化器

### 7.3.3 模型类序列化器

如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。

ModelSerializer与常规的Serializer相同,但额外提供了:

- 基于模型类自动生成一系列字段
- 基于模型类自动为Serializer生成validators,比如unique_together
- 包含默认的create()和update()的实现

#### 7.3.3.1 定义

比如我们创建一个StudentModelSerializer

class StudentModelSerializer(serializers.ModelSerializer):
    """学生信息序列化器"""
    # 1. 转换的字段声明
    # 字段名 = 字段类型(选项=选项值)
    nickname = serializers.CharField(read_only=True)

    # 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
    # 必须给Meta声明2个属性
    class Meta:
        model = Student                                       # 必填
        fields = ["id", "name", "age", "sex","nickname"]      # 必填,可以是字符串和列表/元组,字符串的值只能是"__all__"表示返回所有字段
        # read_only_fields = []  # 选填,只读字段列表,表示设置这里的字段只会在序列化阶段采用
        extra_kwargs = {  # 选填,字段额外选项声明
            "age": {
                "min_value": 5,
                "max_value": 20,
                "error_messages": {
                    "min_value": "年龄的最小值必须大于等于5",
                    "max_value": "年龄的最大值必须小于等于20",
                }
            },
        }

    # 3. 验证代码的对象方法
    # def create(self, validated_data):
        # 密码加密
        # validated_data["password"] = make_password(validated_data["password"])
        # super().create(validated_data)

    # 4. 模型操作的方法,
```

Meta类里面必须声明2个属性:

- model 指明参照哪个模型类
- fields 指明为模型类的哪些字段生成

 

视图代码,测试:

class StudentView(View):
    """模型序列化器"""
    def get1(self,request):
        """序列化一个模型对象"""
        # 1. 获取数据集
        student = Student.objects.first()
        student.nickname = "小学生"
        # 2. 实例化序列化器,获取序列化对象
        serializer = StudentModelSerializer(student)
        # 3. 调用序列化对象的data属性方法获取转换后的数据
        data = serializer.data
        # 4. 响应数据
        return JsonResponse(data=data, status=200, safe=False, json_dumps_params={"ensure_ascii": False})

    def get2(self,request):
        """序列化多个模型对象"""
        # 1. 获取数据集
        student_list = Student.objects.all()
        # 2. 实例化序列化器,得到序列化对象[传递到序列化器的模型对象如果是多个,务必使用many=True]
        serializer = StudentModelSerializer(student_list, many=True)

        # 3. 调用序列化对象的data属性方法获取转换后的数据
        data = serializer.data

        # 4. 响应数据
        return JsonResponse(data=data, status=200, safe=False, json_dumps_params={"ensure_ascii": False})

    def get3(self,request):
        """反序列化-采用字段选项来验证数据[验证失败不抛出异常]"""
        # 1. 接收客户端提交的数据
        # data = json.dumps(request.body)
        # 模拟来自客户端的数据
        data = {
            "name": "xiaoming",
            "age": 211,
            "sex": True,
            "classmate": "301",
            "description": "这家伙很懒,什么都没有留下~"
        }

        # 1.1 实例化序列化器,获取序列化对象
        serializer = StudentModelSerializer(data=data)

        # 1.2 调用序列化器进行数据验证
        ret = serializer.is_valid()  # 不抛出异常
        print(f"ret={ret}")
        # 1.3 获取验证以后的结果
        if ret:
            return JsonResponse(dict(serializer.validated_data))
        else:
            return JsonResponse(dict(serializer.errors))

        # 2. 操作数据库


        # 3. 返回结果

    def get4(self,request):
        """反序列化-采用字段选项来验证数据[验证失败抛出异常,工作中最常用]"""
        # 1. 接收客户端提交的数据
        # 模拟来自客户端的数据
        data = {
            "name": "xiaoming",
            "age": 11,
            "sex": True,
            "classmate": "301",
            "description": "这家伙很懒,什么都没有留下~"
        }

        # 1.1 实例化序列化器,获取序列化对象
        serializer = StudentModelSerializer(data=data)

        # 1.2 调用序列化器进行数据验证
        serializer.is_valid(raise_exception=True)  # 抛出异常,代码不会往下执行
        # 1.3 获取验证以后的结果
        print(serializer.validated_data)

        # 2. 操作数据库


        # 3. 返回结果
        return JsonResponse({})

    def get5(self,request):
        """反序列化-验证完成以后,添加数据入库"""
        # 1. 接收客户端提交的数据
        # 模拟来自客户端的数据
        data = {
            "name": "xiaoming",
            "age": 11,
            "sex": True,
            "classmate": "301",
            "description": "这家伙很懒,什么都没有留下~"
        }

        # 1.1 实例化序列化器,获取序列化对象
        serializer = StudentModelSerializer(data=data)

        # 1.2 调用序列化器进行数据验证
        serializer.is_valid(raise_exception=True)  # 抛出异常,代码不会往下执行

        # 2. 获取验证以后的结果。操作数据库
        serializer.save()  # 会根据实例化序列化器的时候,是否传入instance属性来自动调用create或者update方法。传入instance属性,自动调用update方法;没有传入instance属性,则自动调用create

        # 3. 返回结果
        return JsonResponse(serializer.data, status=201)

    def get(self,request):
        """反序列化-验证完成以后,更新数据入库"""
        #1. 根据客户端访问的url地址中,获取pk值
        # sers/students/2/    path("/students/(?P<pk>)\d+/", views.StudentView.as_view()),
        pk = 5
        try:
            student = Student.objects.get(pk=pk)
        except Student.DoesNotExist:
            return JsonResponse({"errors": "当前学生不存在!"}, status=400)

        # 2. 接收客户端提交的修改数据
        # 模拟来自客户端的数据
        data = {
            "name": "xiaolv",
        }

        # 3. 修改操作中的实例化序列化器对象
        serializer = StudentModelSerializer(instance=student, data=data, partial=True)

        # 4. 验证数据
        serializer.is_valid(raise_exception=True)

        # 5. 入库
        serializer.save() # 可以在save中,传递一些不需要验证的数据到模型里面

        # 6. 返回结果
        return JsonResponse(serializer.data, status=201)

 

#### 7.3.3.2 指定字段

1) 使用**fields**来明确字段,`__all__`表名包含所有字段,也可以写明具体哪些字段,如

class StudentModelSerializer(serializers.ModelSerializer):
    """学生信息序列化器"""
    # 1. 转换的字段声明
    # 字段名 = 字段类型(选项=选项值)
    nickname = serializers.CharField(read_only=True)

    # 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
    # 必须给Meta声明2个属性
    class Meta:
        model = Student        # 必填
        fields = "__all__"     # 必填,可以是字符串和列表/元组,字符串的值只能是"__all__"表示返回所有字段

2) 使用**exclude**可以明确排除掉哪些字段

class StudentModelSerializer(serializers.ModelSerializer):
    """学生信息序列化器"""
    # 1. 转换的字段声明
    # 字段名 = 字段类型(选项=选项值)
    nickname = serializers.CharField(read_only=True)

    # 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
    # 必须给Meta声明2个属性
    class Meta:
        model = Student                                       # 必填
        exclude = ['description']                             # 排除,少用,与fields互斥的。

3) 显示指明字段,如:

class StudentModelSerializer(serializers.ModelSerializer):
    """学生信息序列化器"""
    # 1. 转换的字段声明
    # 字段名 = 字段类型(选项=选项值)
    nickname = serializers.CharField(read_only=True)

    # 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
    # 必须给Meta声明2个属性
    class Meta:
        model = Student                                       # 必填
        fields = ["id", "name", "age", "sex","nickname"]      # 必填,可以是字符串和列表/元组

4) 指明只读字段

可以通过**read_only_fields**指明只读字段,即仅用于序列化输出的字段

class StudentModelSerializer(serializers.ModelSerializer):
    """学生信息序列化器"""
    # 1. 转换的字段声明
    # 字段名 = 字段类型(选项=选项值)
    nickname = serializers.CharField(read_only=True)

    # 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
    # 必须给Meta声明2个属性
    class Meta:
        model = Student                                       # 必填
        fields = ["id", "name", "age", "sex","nickname"]      # 必填,可以是字符串和列表/元组,字符串的值只能是"__all__"表示返回所有字段
        read_only_fields = ["id","sex"]  # 选填,只读字段列表,表示设置这里的字段只会在序列化阶段采用

#### 7.3.3.3 添加额外参数

我们可以使用**extra_kwargs**参数为ModelSerializer添加或修改原有的选项参数

class StudentModelSerializer(serializers.ModelSerializer):
    """学生信息序列化器"""
    # 1. 转换的字段声明
    # 字段名 = 字段类型(选项=选项值)
    nickname = serializers.CharField(read_only=True)

    # 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
    # 必须给Meta声明2个属性
    class Meta:
        model = Student                                       # 必填
        fields = ["id", "name", "age", "sex","nickname"]      # 必填,可以是字符串和列表/元组,字符串的值只能是"__all__"表示返回所有字段
        read_only_fields = ["id","sex"]  # 选填,只读字段列表,表示设置这里的字段只会在序列化阶段采用
        extra_kwargs = {  # 选填,字段额外选项声明
            "age": {
                "min_value": 5,
                "max_value": 20,
                "error_messages": {
                    "min_value": "年龄的最小值必须大于等于5",
                    "max_value": "年龄的最大值必须小于等于20",
                }
            },
        }

什么时候声明的序列化器需要继承序列化器基类Serializer,什么时候继承模型序列化器类ModelSerializer?

继承序列化器类Serializer
    字段声明
    验证
    添加/保存数据功能
继承模型序列化器类ModelSerializer
    字段声明[可选,看需要]
    Meta声明
    验证
    添加/保存数据功能[可选]

看数据是否从mysql数据库中获取,如果是则使用ModelSerializer,不是则使用Serializer

 

7.4 序列化器得嵌套

 

 

url.py

from django.urls import path
from rest_framework.routers import SimpleRouter
from . import views

router = SimpleRouter()
router.register("students", views.StudentModelViewSet, basename="students")

urlpatterns = [

] + router.urls

models.py

from django.db import models
from django.utils import timezone as datetime
# Create your models here.
# 学生     sch_student        1
# 成绩     sch_achievement    n    n
# 课程     sch_course              1    n
# 老师     sch_teacher                  1

class Student(models.Model):
    name = models.CharField(max_length=50, verbose_name="姓名")
    age  = models.SmallIntegerField(verbose_name="年龄")
    sex  = models.BooleanField(default=False)
    class Meta:
        db_table = "sch_student"

    def __str__(self):
        return self.name

    # 自定义模型方法
    # 属性方法,
    @property
    def achievement(self):
        """成绩列表"""
        return self.s_achievment.values("course__teacher__name", "course__name", "score")

class Course(models.Model):
    name = models.CharField(max_length=50, verbose_name="课程名称")
    teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, related_name="course", db_constraint=False) # db_constraint=False 虚拟外键开启

class Meta: db_table = "sch_course" def __str__(self): return self.name class Teacher(models.Model): name = models.CharField(max_length=50, verbose_name="姓名") sex = models.BooleanField(default=False) class Meta: db_table = "sch_teacher" def __str__(self): return self.name class Achievement(models.Model): score = models.DecimalField(default=0, max_digits=4,decimal_places=1, verbose_name="成绩") student = models.ForeignKey(Student,on_delete=models.DO_NOTHING, related_name="s_achievment", db_constraint=False) course = models.ForeignKey(Course,on_delete=models.DO_NOTHING,related_name="c_achievement", db_constraint=False) create_dtime = models.DateTimeField(auto_created=datetime.now) class Meta: db_table = "sch_achievement" def __str__(self): return str(self.score)

数据迁移

python manage.py makemigrations
python manage.py migrate

测试数据代码(注意库名)

import pymysql

# 1.连接
conn = pymysql.connect(host='47.107.57.201', port=3306, user='root', password='bojie123456', db='students', charset='utf8')

# 2.创建游标
cursor = conn.cursor()


sch_teacher = """
insert into students.sch_teacher (id,name,sex) values(1,'李老师',0) ;
insert into students.sch_teacher (id,name,sex) values(2,'王老师',1) ;
insert into students.sch_teacher (id,name,sex) values(3,'黑老师',1) ;
"""

sch_student = """
insert into students.sch_student (id,name,age,sex) values(1,'小黑',18,1);
insert into students.sch_student (id,name,age,sex) values(2,'小黑2',1,0) ;
insert into students.sch_student (id,name,age,sex) values(3,'小黑3',2,0) ;
insert into students.sch_student (id,name,age,sex) values(4,'小黑4',3,1) ;
insert into students.sch_student (id,name,age,sex) values(5,'小黑',18,1) ;
insert into students.sch_student (id,name,age,sex) values(6,'小黑6',18,0) ;
insert into students.sch_student (id,name,age,sex) values(7,'小黑7',180,0) ;
insert into students.sch_student (id,name,age,sex) values(8,'小黑8',118,1) ;
insert into students.sch_student (id,name,age,sex) values(9,'小黑9',18,0) ;
insert into students.sch_student (id,name,age,sex) values(10,'小1',18,1) ;
insert into students.sch_student (id,name,age,sex) values(11,'小2',18,1) ;
"""


sch_course = """
insert into students.sch_course (id,name,teacher_id) values(1,'Python',1); 
insert into students.sch_course (id,name,teacher_id) values(2,'Java',2) ;
insert into students.sch_course (id,name,teacher_id) values(3,'GO',3) ;
insert into students.sch_course (id,name,teacher_id) values(4,'DJANGO',1); 
"""

sch_achievement = """
insert into students.sch_achievement (create_dtime,id,score,course_id,student_id) values('2023-02-07 16:00:00',2,100.0,1,1); 
insert into students.sch_achievement (create_dtime,id,score,course_id,student_id) values('2023-02-07 16:10:00',3,101.0,2,2); 
insert into students.sch_achievement (create_dtime,id,score,course_id,student_id) values('2023-02-07 16:20:00',4,10.0,3,3); 
insert into students.sch_achievement (create_dtime,id,score,course_id,student_id) values('2023-02-07 16:30:00',5,20.0,4,4); 
insert into students.sch_achievement (create_dtime,id,score,course_id,student_id) values('2023-02-07 16:40:00',6,30.0,5,5); 
insert into students.sch_achievement (create_dtime,id,score,course_id,student_id) values('2023-02-07 16:50:00',7,40.0,6,6); 
insert into students.sch_achievement (create_dtime,id,score,course_id,student_id) values('2023-02-07 16:55:00',8,100.0,7,7); 
"""


def insert_func(str_sql: str):
    sql_list = str_sql.split(';')
    print(len(sql_list))
    for sql in sql_list:
        print(sql)
        if sql.strip():
            effect_row = cursor.execute(sql)
            conn.commit()

# insert_func(sch_achievement)
# insert_func(sch_course)
# insert_func(sch_student)
insert_func(sch_teacher)
# 4.关闭游标
cursor.close()
# 5.关闭连接
conn.close()

 

serializers.py

from rest_framework import serializers

from school.models import Student, Achievement, Course

class CourseModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = ["name"]

class AchievementModelSerializer(serializers.ModelSerializer):
    # course = CourseModelSerializer()
    course_name = serializers.CharField(source="course.name")
    teacher_name = serializers.CharField(source="course.teacher.name")

    class Meta:
        model = Achievement
        fields = ["id", "course_name", "teacher_name", "score", "create_dtime"]

class AchievementModelSerializer2(serializers.ModelSerializer):
    class Meta:
        model = Achievement
        # 指定关联深度
        # 从成绩模型->课程 = 1
        # 从成绩模型->课程->老师 = 2
        fields = "__all__"
        depth = 3

class Student3ModelSerializer(serializers.ModelSerializer):
    # s_achievment = AchievementModelSerializer2(many=True)  # s_achievment 就是模型中声明的外键字段,非外键字段不能指定序列化器对象
    class Meta:
        model = Student
        fields = ["id", "name", "achievement"]

view.py

from rest_framework.viewsets import ModelViewSet
from .models import Student
from .serializers import Student3ModelSerializer
# Create your views here.

class StudentModelViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = Student3ModelSerializer

 

 

 

 虚拟外键示例

可以通过left join 展示其关系

 

 

 当我想修改Course表中数据时候,da_constraint= False 时,就不会管teacher表中数据是否受影响,相当于我关闭了外键约束,此时由物理外键转成了虚拟外键

后果:可能会损失一部分安全性得校验,可以通过代码来进行弥补,数据库得性能确提高了

 

示例

我们在序列化器中可以加上外键,在ORM执行过程中会把外键分配给student表中

 

 

 看下图

 

 

 注意这里报了一个错,在返回信息的时候不是一个字符串而是一个数组

 

 

 此时进行强制转换

 

 

 展示:

 

 

 此时s_achievment 显示的是id,我们要的是分数,显示是有问题的,默认情况下模型经过序列化器的数据转换,对于外键信息,仅仅把数据库中的外键ID返回.

解决方案1:在声明一个序列化器,对外键在次序列化,因为返回的是列表,我们加上many=True

 

 

 查看效果

 

 

 当然,也可以指定想要字段,course_id 和 course其实一样

 上面这种一个序列化器调用另一个序列化器,一般就叫序列化器嵌套,此时上面还存在问题:

 

 

 我们不知道课程是哪个,因为返回是是课程的id,此时我们准备把课程在进行嵌套,变成小套娃

 

 

 

 

 

另一种嵌套方式

多对一或一对一返回一个序列化器的情况,通过source选项

 

 看示例

 

 上面这是第二种嵌套的方式

 

第三种嵌套方式:深度属性depth

 

 

 

 

 

 

 自定义模型属性方法

 

 

 未完待续...

 

posted @ 2023-01-31 22:22  断浪狂刀忆年少  阅读(95)  评论(0编辑  收藏  举报