前言
这几年一直在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_字段
这种办法是单独字段的校验,可以对序列化器类对象所写的字段进行判断验证
- 在序列化器里定义校验字段的钩子方法:validate_字段
- 获取字段的数据,就是参数接收到的***value***值
- 验证不通过,抛出异常:raise serializers.ValidationError(“校验不通过的说明”)
- 验证通过,返回字段数据
def validate_name(self, value):
if len(value) == 1:
raise serializers.ValidationError('名字长度无法为1')
return value
validate
多字段联合校验,这种校验方法是可以一次性获取到所有序列化器字段里的全部值,可以进行一个统一整体的校验判断
- 在序列化器定义**validate方法,参数为attrs
- attrs是所有数据组成的字典
- 不符合抛出异常:raise serializers.ValidationError(“校验不通过的说明”)
- 如果校验全部成功,那么返回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