DRF入门 · 模型类序列化器 | 嵌套序列化器 | 外键约束字段的查询处理
1. ModelSerializer模型类序列化器
在上一篇文章中,我们讲解了基类序列化器Serializer的用法,以及基本字段的定义,但是其实对于大部分的字段,我们在定义模型类的时候,就已经对其规则进行了相关定义,序列化的时候只要按照我模型的参数和数据类型做验证就够了,完全没必要再重新定义字段类型等杂七杂八的参数,而ModelSerializer序列化器就来了,它和我们的Model高度关联,只要定义好了Model,那后面的基本就不用管了。
models.py
from django.db import models
class Author(models.Model):
class Meta:
verbose_name = '作者信息表'
name = models.CharField(max_length=128, verbose_name='作者姓名')
introduce = models.CharField(max_length=512, verbose_name='作者简介')
class Book(models.Model):
class Meta:
verbose_name = '图书信息表'
name = models.CharField(max_length=128, verbose_name='书名')
pub_date = models.DateField(verbose_name='发布日期')
# Book表中author字段与作者表形成外键约束
author = models.ForeignKey(to=Author, on_delete=models.DO_NOTHING, verbose_name='作者')
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
如果按照我们之前的普通序列化器的用法,那这个就太麻烦了,下面还得给这些字段,让他再定义验证的格式
1.1 ModelSerializer和基类Serializer复杂度对比
使用旧方法serializers.Serializers
class AuthorSerializer(serializers.ModelSerializer):
name = serializers.CharField(max_length=128, label='作者姓名')
introduce = serializers.CharField(max_length=128,label='作者简介')
def create(self, validated_data):
"""这里还得定义create"""
pass
def update(self, instance, validated_data):
"""这里还得定义update"""
pass
可以发现啊,旧方法,非常非常的麻烦,还得处理保存的关系,下来,我们看看ModelSerializer有多方便
使用ModelSerializer
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = "__all__"
好了,就这四行代码,实现了上面几十行代码的功能,就这么简单!
那么大家肯定一脸懵逼,这怎么实现的,这代码什么意思呢?
1.2 ModelSerializer用法
ModelSerializer详解
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
"""
ModelSerializer类的Meta类,用于指定序列化器类对应的模型类以及序列化的字段
"""
model = Author # 指定模型类
#### 指定需要序列化的字段 ####
# 1. 序列化全部字段fields="__all__",则表示序列化所有字段
fields = "__all__"
# 2. 序列化指定字段
# fields = ["name", "price"] # 指定序列化的字段,如果是列表/元组,则表示序列化指定的字段
# 元组或者列表都可以
# 3. 排除字段(与fields相反)
# exclude = ["id","pub_date"] # 表示除了id和pub_date字段,都需要序列化
### 现在有个需求,前端小姐姐希望我返回的日期字段是"date"而不是pub_date,也就是说,我现在数据库model模型关联的字段名是pub_date,但是前端小姐姐,希望我返回一个date,那我该怎么处理??
# 非常简单,对于需要单独处理的字段,我们可以单独拎出来
class BookSerializer(serializers.ModelSerializer):
date = serializers.DateField(source='pub_date') # 指定date字段,来源于pub_date字段
class Meta:
model = Book
exclude = ["pub_date","id"] # 因为我们自己单独定义了date字段,所以原本返回的pub_date,这里直接排除掉
# 此时我们请求下看看
[
{
"date": "2023-08-03",
"name": "时间简史",
"price": "50.00",
"author": 1
}
]
# 返回的就是date,并且没有pub_date
2. 处理外键约束字段的显示
2.1 需求一 分析
现在我的Book表和Author表之间的author字段关联,所以序列化的时候,可以发现,返回的author字段也是id,但是我现在的需求是,你不仅要给我返回一个author的name,你还得吧id给我,也就是说你得把id和name都得给我。
# 原本的返回
{
"date": "2023-08-03",
"name": "时间简史",
"price": "50.00",
"author": 1
}
# 希望的返回
{
"date": "2023-08-03",
"name": "时间简史",
"price": "50.00",
"author": "霍金",
"author_id": 1"
}
好,这是个好需求, 那该怎么实现呢?
2.2 SerializerMethodField引入
DRF中为我们提供了SerializerMethodField,听名字就知道方法字段序列化器,也就是说,我们一个字段的值,可以通过指定的方法去返回,比如我定义一个当前时间的字段,我就需要让他返回一个获取当前时间的函数。
DRF在使用SerializerMethodField字段的指定方法时,会将此时序列化器中的model对象,传入方法中。
使用SerializerMethodField实现2.1需求
from rest_framework import serializers
from BookManage.models import *
class BookSerializer(serializers.ModelSerializer):
author = serializers.SerializerMethodField()
# 如果MethodField不指定method_name,那函数名必须命名为 "get{变量名}"的形式
author_id = serializers.SerializerMethodField(method_name='xxxx') # 指定方法名=xxxx
# 指定了方法名,则会通过getattr进行查找
def get_author(self,book):
"""
没有指定author字段的method_name,函数必须按get_author命令
在调用查询字段方法时,会将当时的model对象传入
:param book: book的model对象
"""
return book.author.name
def xxxx(self,book):
"""
关联的时author_id字段
"""
return book.author.id
返回结果示例:
[
{
"id": 1,
"author": "霍金",
"author_id": 1,
"name": "时间简史",
"pub_date": "2023-08-03",
"price": "50.00"
}
]
3. 处理外键约束-使用嵌套序列化器
3.1 需求二 分析
现在我的需求变了,我要查的是,作者的全部信息,并且需要单独给我拎出来,变成一个嵌套JSON数据,你该怎么处理?
如下是示例:
[
{
"id": 1,
"name": "时间简史",
"pub_date": "2023-08-03",
"author": {
"id": 1,
"name": "霍金",
"introduce": "斯蒂芬·威廉·霍金(Stephen William Hawking,1942年1月8日—2018年3月14日),男,出生于英国牛津,英国剑桥大学著名物理学家,现代最伟大的物理学家之一、20世纪享有国际盛誉的伟人之一。"
},
"price": "50.00"
}
]
也许有人说了,我和2.1一样,把author单独拎出来,然后定义一个MethodField字段,然后让方法返回一个字典,是,你说的也可以做到,但是如果我这个作者信息,有几十个字段呢?我的作者信息非常的杂乱无章,你怎么办?不可能一个个去返回把?
所以,DRF为了方便,便提供了嵌套序列化器
首先,创建一个新的序列化器来表示作者信息:
from rest_framework import serializers
from .models import Author, Book
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'name', 'introduce') # 定义作者需要返回的字段
接下来,在Book序列化器中,使用嵌套序列化器来表示作者信息:
class BookSerializer(serializers.ModelSerializer):
# 使用AuthorSerializer作为author字段的嵌套序列化器
author = AuthorSerializer()
# 自己额外定义的字段,一定不能排除,如果排除就会报错!
class Meta:
model = Book
fields = ('id', 'name', 'pub_date', 'author', 'price')
此时我们进行GET请求的时候,可以发现,就已经返回了我们想要的数据。
[
{
"id": 1,
"name": "时间简史",
"pub_date": "2023-08-03",
"author": {
"id": 1,
"name": "霍金",
"introduce": "斯蒂芬·威廉·霍金(Stephen William Hawking,1942年1月8日—2018年3月14日),男,出生于英国牛津,英国剑桥大学著名物理学家,现代最伟大的物理学家之一、20世纪享有国际盛誉的伟人之一。"
},
"price": "50.00"
}
]
3.2 需求二的坑!
如果你按照3.1的方式进行实现,那的确没问题,返回的数据也没有错误,但是你会发现,当你使用POST请求去创建新数据的时候,就会有问题了!
{
"name":"果壳中的宇宙",
"pub_date":"2005-08-03",
"author":1,
"price":66
}
# 传入创建信息
# 报错:
{
"author": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int." # 期望得到一个字典?
]
}
}
其实,如果按这种方式处理,那你创建的时候,作者也得传入一个字典才能创建成功
{
"name":"果壳中的宇宙",
"pub_date":"2005-08-03",
"author": {
"id": 1,
"name":"霍金",
"introduce":"xxxx",
},
"price":66
}
但是,如果我非要实现这样的查询呢??
下面我就来演示,如何实现外键约束且读写不同的序列化器
4. 外键约束且读写不同的复杂序列化器
需求:
1. 查询信息时,可以返回以下信息:
{
"id": 1,
"name": "时间简史",
"pub_date": "2023-08-03",
// 查询时,返回作者的完整字段信息,字典类型
"author": {
"id": 1,
"name": "霍金",
"introduce": "斯蒂芬·威廉·霍金(Stephen William Hawking,1942年1月8日—2018年3月14日),男,出生于英国牛津,英国剑桥大学著名物理学家,现代最伟大的物理学家之一、20世纪享有国际盛誉的伟人之一。"
},
"price": "50.00",
}
2. 创建信息的时候,只需要传入author_id就能够实现创建
{
"name":"大设计",
"pub_date":"2008-08-03",
"author_id":88, //只需要传入author_id就可以
"price":33
}
4.1 实现方法 PrimaryKeyRelatedField+嵌套序列化器
PrimaryKeyRelatedField是序列化器字段中,主键相关字段
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'name', 'introduce')
class BookSerializer(serializers.ModelSerializer):
# 使用AuthorSerializer作为author字段的嵌套序列化器
author = AuthorSerializer(read_only=True)
# 使用主键字段类型
author_id = serializers.PrimaryKeyRelatedField(queryset=Author.objects.all(), source='author', write_only=True)
# 并且将author_id设置为"只写",也就是读取的时候不会返回!
class Meta:
model = Book
fields = ['id', 'name', 'pub_date', 'author', 'price', 'author_id']
PrimaryKeyRelatedField介绍
PrimaryKeyRelatedField(queryset=Author.objects.all(), source='author', write_only=True)
queryset:
关联的主表的模型类对象QuerySet
soure:
从表的关联键的名称
案例:
serializers.PrimaryKeyRelatedField(queryset=Author.objects.all(), source='author', write_only=True)
这里当前序列化器的model是Book(从表)
所以传入就是上面的参数