前言
在Django REST framework
中,关联序列化器 是解锁复杂数据关系的强大工具。从多表模型创建到外键序列化的多种方法,再到一对多、多对多关系的嵌套处理,关联序列化器让数据操作更加高效和准确。
接下来,我们将深入了解如何创建多表模型,掌握外键序列化的四种技巧,包括StringRelated、SlugRelated、PrimaryKeyRelated以及自定义外键序列化,并探讨一对多、多对多关系模型的嵌套关系处理。更重要的是,我们还将探讨关联序列化器的反序列化过程,让你的数据操作更加得心应手。
一、关联序列化器序列化
关联序列化器的序列化 主要涉及在
Django
中将具有关联关系的 模型实例 转换为适合网络传输或存储的格式(json
),并通过 嵌套序列化器 来处理关联对象。这是Django REST framework
中实现复杂数据模型序列化的重要功能之一。
- 当Django模型之间存在关联关系(如外键、多对多关系等)时,我们需要一种方式来同时序列化这些关联对象。这就是 关联序列化器 的概念。
- 关联序列化器 允许我们在 主序列化器 中嵌套另一个序列化器,以处理关联对象。例如,如果一个
Teacher
模型有一个Student
模型的外键关系,我们可以创建一个TeacherSerializer
,并在其中嵌套一个StudentSerializer
来处理Teacher
的学生列表。
1. 多表 序列化 模型类创建
之前的序列化,只是简单的对一张表进行处理,如果遇到多表,且是有关联的,如一对一,多对一,多对多的情况该怎么序列化呢?
比如现在有两张很常见的关联表,班级表和学生表
- 一个班级可以有一群学生,但是一个学生同时只能属于一个班级,对应的数据库关系就是多对一(外键关系设置在’多’,即学生表中)的关系,模型类 如下:
# models.py #班级模型类 一 class ClassModel(models.Model): name = models.CharField(max_length=50) num = models.IntegerField(max_length=18) def __str__(self): return self.name class Meta: db_table = 'cla' #学生模型类 多 class StudentModel(models.Model): GENDER_CHOICES = { (1, '男'), (2, '女'), (3, '保密'), } name = models.CharField(max_length=50) age = models.IntegerField(max_length=18) gender = models.IntegerField(choices=GENDER_CHOICES, default=1) info = models.CharField(max_length=100, null=True) cla = models.ForeignKey(to=ClassModel, on_delete=models.SET_NULL, null=True) def __str__(self): return self.name class Meta: db_table = 'student'
添 加 studentSerializer.py,view_stu.py,urls.py,代码如下:
# studentSerializer.py: from rest_framework import serializers from app.models import StudentModel class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50, min_length=2, error_messages={"min_length": "姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) class Meta: model = StudentModel fields = '__all__' ----------------------------------------------- # view_stu.py from rest_framework.views import APIView from rest_framework.response import Response from app.models import StudentModel from app.serializer.studentSerializer import StudentSerializer class StudentViews(APIView): def get(self, request): # 学生信息 #序列化 stus = StudentModel.objects.all() # stus[0].teachermodel_set.all() stuSer = StudentSerializer(instance=stus, many=True) return Response({"message":"[GET]StudentViews请求成功!", "data": stuSer.data}) ------------------------------------------------ # urls.py from django.urls import path from app.views_stu import StudentViews urlpatterns = [ path('students/', StudentViews.as_view()), ]
查询所有学生信息,效果图如下:
上图中
"cla":1
— 显示班级cla的主键,而不显示班级名?如何解决?
学生表(StudentModel)中,包含外键 cla
,外键可以通过如下一些方式进行序列化
StringRelated
外键序列化SlugRelated
外键序列化PrimaryKeyRelated
外键序列化- 外键自定义序列化方式
2. StringRelated外键序列化
将关联表的
__str__
方法作为结果返回,设置read_only 代表该字段不进行 反序列化校验
# studentSerializer.py 添加 StringRelatedField 字段 class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50,min_length=2,error_messages={"min_length":"姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) # 1.将关联表中的 __str__中的内容作为返回结果进行序列化 cla = serializers.StringRelatedField(read_only=True) #只在序列化时使用,反序列化时不需要 class Meta: model = StudentModel # 绑定StudentModel模型类 fields = "__all__"
StringRelated外键序列化后,查询所有学生信息,效果图如下:
3. SlugRelated外键序列化
使用 关联表的指定字段 作为结果返回
# studentSerializer.py 添加 SlugRelatedField字段 class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50, min_length=2, error_messages={"min_length": "姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) # 2.指定关联表中的指定字段==slug_field='num'==作为返回结果进行序列化 cla = serializers.SlugRelatedField(read_only=True,slug_field='name') # cla = serializers.SlugRelatedField(read_only=True,slug_field='num') class Meta: model = StudentModel fields = '__all__'
SlugRelatedField外键序列化(分别指定字段
name
和num
)后,查询所有学生信息,效果图如下:
cla = serializers.SlugRelatedField(read_only=True,slug_field='name')
cla = serializers.SlugRelatedField(read_only=True,slug_field='num')
4. PrimaryKeyRelated外键序列化
将关联表的主键作为结果返回,使用
PrimaryKeyRelatedField
字段,该字段需要包含queryset
或read_only
属性
# studentSerializer.py 添加 PrimaryKeyRelatedField字段 class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50,min_length=2,error_messages={"min_length":"姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) # 3.返回关联表中的主键id字段,作为返回结果并进行序列化 cla = serializers.PrimaryKeyRelatedField(read_only=True) class Meta: model = StudentModel fields = '__all__'
PrimaryKeyRelated外键序列化后(与不添加外键序列化字段时 即默认情况下效果相同,都显示班级id),查询所有学生信息,效果图如下:
5. 外键自定义序列化方式
在序列化的时候,如果想自定义获取对应的关联表中的数据的时候,就需要自己构建序列化字段, 此时默认的外键不发生改变,新添加序列化字段,来达到序列化自定义字段的效果
serializers.SerializerMethodField(read_only=True)
# studentSerializer.py 添加 SerializerMethodField 自定义字段 class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50, min_length=2, error_messages={"min_length": "姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) # 4. 自定义字段,作为序列化字段,需要配合自定义的函数使用, gender = serializers.SerializerMethodField(read_only=True) def get_gender(self,obj): ''' 函数名的构成:前面添加`get_`,后面跟的是自定义字段名,组合成函数名, return:结果就是序列化内容 obj:指的是当前模型类对象 -- 覆盖gender---- get_gender_display() ''' return obj.get_gender_display() # 4. 自定义字段 xxxx = serializers.SerializerMethodField() def get_xxxx(self, obj): #obj-当前学生对象 return obj.cla.name # 多对多不行 class Meta: model = StudentModel fields = '__all__'
添加自定义字段
gender
和xxxx
后,查询所有学生,效果图如下:
一对多关系模型序列化(反向查询)
以上介绍的都是一对多情况下,正向查询(通过student查询class),下面将介绍反向查询(通过cla查询student)
从
ClassModel
中可以看出,班级模型类并不显性持有学生信息,但可通过 反向查询 隐式获取学生信息一对多时,通过班级cla反向查询学生 student
添加classSerializer.py,view_cla.py,urls.py
# classSerializer.py class ClassSerializer(serializers.ModelSerializer): # Z自定义字段---通过班级反向查询学生---多对一时 studentname = serializers.SerializerMethodField() def get_studentname(self, obj): # obj--cla模型对象 names = "" for st in obj.studentmodel_set.all(): names += st.name return names name = serializers.CharField(max_length=50, min_length=1, validators=[nameValidators]) class Meta: model = ClassModel fields = '__all__' -------------------------------------------------------------------- # view_cla.py from rest_framework.views import APIView from rest_framework.response import Response from app.models import ClassModel from app.serializer.classSerializer import ClassSerializer class ClassViews(APIView): def get(self, request): clas = ClassModel.objects.all() # clas[0].studentmodel_set claSer = ClassSerializer(instance=clas, many=True) return Response({"message":"[get]ClassViews测试成功!",'data':claSer.data}) ------------------------------------------------------------- # urls.py from django.urls import path from app.views_stu import StudentViews from app.views_cla import ClassViews urlpatterns = [ path('students/', StudentViews.as_view()), path('classes/', ClassViews.as_view()), ]
一对多情况下,增加自定义字段
studentname
( studentname通过反向查询获得 )后,查询班级cla
信息的的前后对比图:
添加自定义字段
studentname
前:
添加自定义字段
studentname
后:
多对多关联模型序列化(正向查询)
多对多反向获取数据时与刚刚介绍的一对多情况下获取数据操作相同。正向呢?
PS:多对多时,添加teacher信息,teacher与student构成多对多关系
在 models.py 中添加 TeacherModel 模型类:
# 教师模型类 class TeacherModel(models.Model): GENDER_CHOICES = { (1, '男'), (2, '女'), (3, '保密'), } name = models.CharField(max_length=50) age = models.IntegerField(max_length=18) gender = models.IntegerField(choices=GENDER_CHOICES, default=1) info = models.CharField(max_length=100, null=True) # ++++添加与学生的多对多关系 student = models.ManyToManyField(to=StudentModel) def __str__(self): return self.name class Meta: db_table = 'teacher'
添加 teacherSerializer.py,view_teacher.py,urls.py
# teacherSerializer.py from rest_framework import serializers from app.models import TeacherModel class TeacherSerializer(serializers.ModelSerializer): class Meta: model = TeacherModel fields = '__all__' ---------------------------------------------------------------- # views_teacher.py from rest_framework.views import APIView from rest_framework.response import Response from app.serializer.teacherSerializer import TeacherSerializer from app.models import TeacherModel class TeacherViews(APIView): def get(self, request): teac = TeacherModel.objects.all() teacSer = TeacherSerializer(instance=teac, many=True) return Response({"message": "测试成功!", "teacList": teacSer.data}) ---------------------------------------------------------------------- # urls.py from django.urls import path from app.views_stu import StudentViews from app.views_cla import ClassViews from app.views_teacher import TeacherViews urlpatterns = [ path('students/', StudentViews.as_view()), path('classes/', ClassViews.as_view()), path('teachers/', TeacherViews.as_view()), ]
查询 teacher 信息,效果图如下:
上图中
student
外键字段显示的是student 的 id,如何显示学生姓名或者其他信息呢? 可以上文中介绍过的外键序列化的几种方式在
teacherSerializer.py
中通过SlugRelatedField
字段 使图中的student
字段 显示学生名:
# teacherSerializer.py 添加 SlugRelatedField 字段 指定 显示 `name` from rest_framework import serializers from app.models import TeacherModel class TeacherSerializer(serializers.ModelSerializer): student = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) class Meta: model = TeacherModel fields = '__all__'
添加
SlugRelatedField
指定字段后,查询teachers,效果图如下:(student由 id --> name)
6. 嵌套关系
嵌套序列化 :关联关系的模型类。将需要被嵌套的序列化器,引入到当前序列化器中。如果对应关系是多个,需要注意添加
many = true
反向嵌套关系的情况下:
- 需要维护模型类中的关联关系名
- 在设置外键关联的模型类中,通过
related_name
指定关联关系名- 在反向序列化器中,通过
source='关联关系名'
,指定对应关联模型
1.多对多关系模型 的嵌套关系(正向)
接上文中的
Teacher-Student
案例 ,演示 多对多的嵌套在 teacherSerializer.py 中引入学生序列化器
StudentSerializer
# teacherSerializer.py from rest_framework import serializers from app.models import TeacherModel from app.serializer.studentSerializer import StudentSerializer # PS:注意不要循环嵌套(即教师序列化器中引入学生序列化器,又在学生序列化器中引入教师序列化器) class TeacherSerializer(serializers.ModelSerializer): # student = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) # 嵌套 学生序列化器--StudentSerializer student = StudentSerializer(many=True, read_only=True) #对应TeacherModel中的student字段 class Meta: model = TeacherModel fields = '__all__'
在 teacherSerializer.py 中引入学生序列化器
StudentSerializer
后,查询 Teacher 信息,效果图如下:
PS:注意不要循环嵌套序列化器
循环嵌套的实例代码如下:
(这样写会报错,此处仅用来演示循环嵌套这种错误操作)
# TeacherSerializer.py from rest_framework import serializers from app.models import TeacherModel # 演示 循环嵌套(循环引入、循环导包) from app.serializer.studentSerializer import StudentSerializer class TeacherSerializer(serializers.ModelSerializer): # 嵌套序列化器 student = StudentSerializer(many=True, read_only=True) #对应TeacherModel中的student字段 class Meta: model = TeacherModel fields = '__all__' #--------------华丽的分割线---------------------------- # StudentSerializer.py from rest_framework import serializers from app.models import StudentModel from app.serializer.classSerializer import ClassSerializer # 注意循环嵌套 teacherSerializer 与 studentSerializer from app.serializer.teacherSerializer import TeacherSerializer class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50, min_length=2, error_messages={"min_length": "姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) class Meta: model = StudentModel fields = '__all__'
2.一对多关系模型 的嵌套关系(正向)
一对多关系模型 : 班级cla与学生student关系模型
studentSerializer.py:
# studentSerializer.py from rest_framework import serializers from app.models import StudentModel from app.serializer.classSerializer import ClassSerializer class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50, min_length=2, error_messages={"min_length": "姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) # 自定义字段 gender = serializers.SerializerMethodField(read_only=True) def get_gender(self,obj): return obj.get_gender_display() # 自定义字段 xxxx = serializers.SerializerMethodField() def get_xxxx(self, obj): #obj-当前学生对象 return obj.cla.name # 多对多不行(要用.all()), # +嵌套关系 多对一 student --- cla cla = ClassSerializer() class Meta: model = StudentModel fields = '__all__'
student-cla多对一,嵌套关系下(StudentSerializer嵌套ClassSerializer)查询students,效果图如下:
3. 多对多关系模型 的嵌套关系(反向)
多对多关系模型:学生student与教师teacher关系模型
反向嵌套关系的情况下:
- 需要维护模型类中的关联关系名
- 在设置外键关联的模型类中,通过
related_name
指定关联关系名- 在反向序列化器中,通过
source='关联关系名'
,指定对应关联模型在如下代码中会有所体现:
- studentSerializer 序列化器中引入的
TeacherSerializer
中通过source='关联关系名'
,指定对应关联模型。teacherModel 模型类中,设置student
外键时通过related_name
指定关联关系名。
- studentSerializer序列化器中:
teac = TeacherSerializer(many=True, read_only=True,source='tea')
- TeacherModel模型类中:
student = models.ManyToManyField(to=StudentModel,related_name='tea')
studentSerializer.py:
# studentSerializer.py from rest_framework import serializers from app.models import StudentModel from app.serializer.classSerializer import ClassSerializer from app.serializer.teacherSerializer import TeacherSerializer class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50, min_length=2, error_messages={"min_length": "姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) # 自定义字段 gender = serializers.SerializerMethodField(read_only=True) def get_gender(self,obj): return obj.get_gender_display() # 自定义字段 xxxx = serializers.SerializerMethodField() def get_xxxx(self, obj): #obj-当前学生对象 return obj.cla.name # 多对多不行(要用.all()) # 嵌套关系 多对一 student --- cla # cla = ClassSerializer() # 学生stu-老师teac 多对多关系模型--反向查询 # teachermodel_set.all() #不在 model设置 related_name时,默认source='teachermodel_set' # teac = TeacherSerializer(many=True, read_only=True,source='teachermodel_set') teac = TeacherSerializer(many=True, read_only=True,source='tea') class Meta: model = StudentModel fields = '__all__'
models.py:
# models.py #教师模型类 class TeacherModel(models.Model): GENDER_CHOICES = { (1, '男'), (2, '女'), (3, '保密'), } name = models.CharField(max_length=50) age = models.IntegerField(max_length=18) gender = models.IntegerField(choices=GENDER_CHOICES, default=1) info = models.CharField(max_length=100, null=True) student = models.ManyToManyField(to=StudentModel,related_name='tea') def __str__(self): return self.name class Meta: db_table = 'teacher'
多对多关系模型(students-teachers)下,students反向查询teachers,效果图如下:
二、关联序列化器反序列化
1. 默认关联反序列化方式(简单情况下)
在postman或其他手段进行测试时,在老师,学生关系模型中,想要添加学生信息,直接传老师 id、学生名、及其他所需字段即可
学生反序列化器不需要写任何字段,默认的关联字段会接受一个id数据作为校验依据并创建
以学生为例:
studentSerializer.py,studentView.py:
# studentSerializer.py class StudentSer(serializers.ModelSerializer): class Meta: model = Student fields = '__all__' #----------------------------------------------- # studentView.py class studentView(APIView): def post(self, request): ser = StudentSer(data=request.data) if ser.is_valid(): ser.save() error = ser.errors return Response({'msg': erro r if error else 'success'})
postman 测试时,只需要传如下数据即可:
{ "teacher": "2", # 外键直接传入 id 即可 "name": "小黑" }
2. 多对多关联模型反序列化方式
PS:此案例仅用来复习上述知识
学生student–教师teacher 多对多关联模型
models.py -----模型类中-----定义 StudentModel 和 TeacherModel
# models.py #学生模型类 class StudentModel(models.Model): GENDER_CHOICES = { (1, '男'), (2, '女'), (3, '保密'), } name = models.CharField(max_length=50) age = models.IntegerField(max_length=18) gender = models.IntegerField(choices=GENDER_CHOICES, default=1) info = models.CharField(max_length=100, null=True) cla = models.ForeignKey(to=ClassModel, on_delete=models.SET_NULL, null=True) def __str__(self): return self.name class Meta: db_table = 'student' #教师模型类 class TeacherModel(models.Model): GENDER_CHOICES = { (1, '男'), (2, '女'), (3, '保密'), } name = models.CharField(max_length=50) age = models.IntegerField(max_length=18) gender = models.IntegerField(choices=GENDER_CHOICES, default=1) info = models.CharField(max_length=100, null=True) # 不设置反向关系时 student = models.ManyToManyField(to=StudentModel) # 设置反向关系时 #student = models.ManyToManyField(to=StudentModel,related_name='tea') def __str__(self): return self.name class Meta: db_table = 'teacher'
序列化器 ---- 定义 StudentSerializer 和 TeacherSerializer
# studentSerializer.py from rest_framework import serializers from app.models import StudentModel from app.serializer.teacherSerializer import TeacherSerializer class StudentSerializer(serializers.ModelSerializer): name = serializers.CharField(max_length=50, min_length=2, error_messages={"min_length": "姓名不能少于两个字符"}) info = serializers.CharField(max_length=100) # 自定义字段 gender = serializers.SerializerMethodField(read_only=True) def get_gender(self,obj): return obj.get_gender_display() # 自定义字段 xxxx = serializers.SerializerMethodField() def get_xxxx(self, obj): #obj-当前学生对象 return obj.cla.name # 多对多不行 #模型类没有设置反向关系时: teac = TeacherSerializer(many=True, read_only=True) #不在model设置related_name时,默认source='teachermodel_set' # 模型类中设置反向关系后: # teac = TeacherSerializer(many=True, read_only=True,source='tea') # teac = TeacherSerializer(many=True,source='tea') class Meta: model = StudentModel fields = '__all__' #----------------------------------------------- # teacherSerializer.py from rest_framework import serializers from app.models import TeacherModel class TeacherSerializer(serializers.ModelSerializer): class Meta: model = TeacherModel fields = '__all__'
视图类:views_stu:
# views_stu.py # 测试多对多关系模型反序列化,利用反向查询,添加老师(创建新老师)(post方式 提交教师信息), class StudentTeacherViews(APIView): def get(self, request): return Response({"message":"GET测试成功!"}) # post def post(self, request): teac = TeacherSerializer(data=request.data) if teac.is_valid(): teac.save() print(teac.data) # {'id': 4, 'name': '照老师', 'age': 18, 'gender': 2, 'info': '她是敬业负责的一个老师', 'student': [1]} stu = StudentModel.objects.get(pk=teac.data["student"][0]) print(stu) # stu.tea.add(teac.data["id"]) # model和Serializer显式设置了反向关系时 stu.teachermodel_set.add(teac.data["id"]) # 默认情况下 return Response({"message":"添加成功!"}) else: return Response({"message": teac.errors})
路由:urls.py:
from django.urls import path from app.views_stu import StudentViews,StudentTeacherViews from app.views_teacher import TeacherViews urlpatterns = [ path('students/', StudentViews.as_view()), path('teachers/', TeacherViews.as_view()), path('studentstest/', StudentTeacherViews.as_view()), ]