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
自定义模型属性方法
未完待续...