如果我们想要使用序列化器对应的是 Django 的模型类,DRF 为我们提供了 ModelSerializer 模型类序列化器来帮助我们快速创建一个 Serailizer 类。通常情况下,使用ModelSerializer内置的Meta可以满足大多数开发需求使用,对于常见的其他需求,在这里做了简单的总结归纳以及代码示例。
一、ModelSerializer 与Serializer
1. ModelSerializer 与Serializer的区别
ModelSerializer包含了serializer的全部功能,但除此之外,还提供了如下功能:
- 基于 模型类自动生成一系列字段
- 基于模型类自动为 Serializer 生成 validators
- 包含默认的 create() 和 update() 的实现
2. meta选项参数
选项 | 参数 | 说明 |
---|---|---|
model | 模型类名 | 指明参照哪个模型类 |
fields | all 表名包含所有字段,也可以写明具体哪些字段 | 指明为模型类的哪些字段生成 |
exclude | 排除的字段名称列表 | 排除掉哪些字段 |
depth | 嵌套的层级数量 | ModelSerializer 使用主键作为关联字段,会自动通过主键找下一级的关联字段,可以指定向下找的层级 |
read_only_fields | 只读字段名称列表 | 仅用于序列化输出的字段 |
extra_kwargs | 要添加或修改的选项对象 | 添加或修改原有的选项参数 |
meta关键字文档:https://www.django-rest-framework.org/api-guide/serializers/。meta的参数很简单,就上述这些关键字,按照需求配置即可
二、常见模型序列化器用法
1. 枚举类型字段显示
场景举例:用户详细信息模型的性别字段的值类型分为男和女两种,使用枚举类型关联保存,默认序列化之后只显示枚举类型的值(1 2),而不显示枚举字段的label(男 女)。此时就需要添加额外的字段sex_name,用来显示对应的sex字段label值。
- 模型
# 性别枚举类型
class Sex(models.TextChoices):
men = '1', '男'
women = '2', '女'
class UserInfo(AbstractUser):
sex = models.CharField(verbose_name='性别', max_length=1, choices=Sex.choices, default=Sex.men)
class Meta:
verbose_name = '用户详细信息'
verbose_name_plural = verbose_name
ordering = ("-last_login",)
def __str__(self):
return self.username
- 序列化器
class UserInfoSerializer(serializers.ModelSerializer):
"""
用户信息序列化器
"""
# 性别显示文字
sex_name = serializers.ReadOnlyField(source='get_sex_display')
class Meta:
model = UserInfo
fields = "__all__"
2. 外键关联字段显示
- 模型
场景举例:文章分类与文章模型使用一对多外键关联,默认序列化后只显示文章分类的id,而不显示对应的name信息
class Category(models.Model):
name = models.CharField('文章分类', max_length=100)
class Meta:
verbose_name = '文章分类'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField('标题', max_length=50)
category = models.ForeignKey(Category, on_delete=models.DO_NOTHING, verbose_name='分类', blank=True, null=True)
# 使用外键关联分类表与分类是一对多关系
class Meta:
verbose_name = '文章内容'
verbose_name_plural = verbose_name
ordering = ("-created_time",)
def __str__(self):
return self.title
- 序列化器
class ArticleRetrieveSerializer(serializers.ModelSerializer):
"""
博客文章内容序列化器
"""
# 获取外键文章分类id和name
category = serializers.CharField(read_only=True)
category_id = serializers.IntegerField()
class Meta:
model = Article
fields = "__all__"
3. 多对多序列化器方法
- 外键序列化器
class KindSerializer(serializers.ModelSerializer):
"""
资源类型序列化器
"""
class Meta:
model = Kind
fields = "__all__"
- 关联外键序列化器
class SecretSerializer(serializers.ModelSerializer):
"""
密钥序列化器
"""
# 云厂商显示文字
manufacturer_name = serializers.ReadOnlyField(source='get_manufacturer_display')
# 获取资源类型多对多信息
kinds_list = KindSerializer(many=True, source='kinds')
class Meta:
model = Secret
fields = "__all__"
3. 自定义序列化器方法
场景举例1:文章模型的tags字段和tag模型使用多对多关联标签,如果使用默认序列化器,只会返回tags外键的id列表,不显示对应tags的名称
- 模型
class Tag(models.Model):
name = models.CharField('文章标签', max_length=100)
class Meta:
verbose_name = '文章标签'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField('标题', max_length=50)
tags = models.ManyToManyField(Tag, verbose_name='标签', blank=True)
# 使用外键关联标签表与标签是多对多关系
class Meta:
verbose_name = '文章内容'
verbose_name_plural = verbose_name
ordering = ("-created_time",)
def __str__(self):
return self.title
- 序列化器
class ArticleRetrieveSerializer(serializers.ModelSerializer):
"""
博客文章内容序列化器
"""
# 获取文章标签多对多信息
tags = serializers.SerializerMethodField()
class Meta:
model = Article
fields = "__all__"
def get_tags(self, obj: Article):
return [
{'id': i['id'], 'name': i['name']}
for i in obj.tags.all().values('id', 'name')
]
场景举例2:资产清单模型的date字段记录购买日期,当序列化时,需要新增使用年限字段(通过计算当前日日期与购买日期,显示资产使用多少年的信息)。
- 模型
class Cmdb(models.Model):
name = models.CharField('资产名称', max_length=20)
date = models.DateField('购买日期')
class Meta:
verbose_name = '资产清单'
verbose_name_plural = verbose_name
ordering = ("-create_time",)
def __str__(self):
return self.name
- 序列化器
class CmdbSerializer(serializers.ModelSerializer):
age = serializers.SerializerMethodField()
class Meta:
model = Cmdb
fields = '__all__'
# 计算使用年限
def get_age(self, obj):
return round((now().date() - obj.date).days / 365.25, 1)
4. 附加额外只读字段
- 模型
文章评论回复模型,使用默认序列化器默认只返回评论用户的id,而评论用户名和用户头像信息为空
class ArticleComment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, verbose_name='文章')
content = models.TextField(verbose_name='评论内容', )
user = models.ForeignKey(UserInfo, on_delete=models.CASCADE, verbose_name='用户名')
father = models.ForeignKey('self', verbose_name='父评论', on_delete=models.CASCADE, blank=True, null=True,
related_name='sub')
class Meta:
ordering = ('-time', 'article')
verbose_name = '文章评论回复记录'
verbose_name_plural = verbose_name
def __str__(self):
return self.content
- 序列化器
class ArticleCommentSerializer(serializers.ModelSerializer):
"""
文章评论回复记录序列化器
"""
username = serializers.ReadOnlyField(source='user.username')
photo = serializers.ReadOnlyField(source='user.photo')
class Meta:
model = ArticleComment
fields = "__all__"
5. 递归序列化器
具体内容参考文章:https://www.cuiliangblog.cn/detail/article/23,通常留言模型都是多级留言模型,递归调用,在序列化时,新增child字段,递归查询关联的记录
- 模型
class LeaveMessage(models.Model):
content = models.TextField(verbose_name='留言内容', )
user = models.ForeignKey(UserInfo, on_delete=models.CASCADE, verbose_name='用户名')
time = models.DateTimeField(auto_now_add=True, verbose_name='留言时间')
like = models.IntegerField(verbose_name='留言点赞数', default=0)
father = models.ForeignKey('self', verbose_name='父留言', on_delete=models.CASCADE, blank=True, null=True,
related_name='sub')
class Meta:
ordering = ('-time',)
verbose_name = '留言回复记录'
verbose_name_plural = verbose_name
# 获取子回复
def get_child(self):
return self.sub.all()
# 获取留言用户名
def get_father_name(self):
if self.father:
return self.father.user.username
return
def __str__(self):
return self.content
- 序列化器
class LeaveMessageSerializer(serializers.ModelSerializer):
"""
留言记录序列化器
"""
username = serializers.ReadOnlyField(source='user.username')
photo = serializers.ReadOnlyField(source='user.photo')
child = serializers.ListField(source='get_child', child=RecursiveField(), read_only=True)
father_name = serializers.ReadOnlyField(source='get_father_name')
class Meta:
model = LeaveMessage
fields = "__all__"
6. 自定义校验方法
默认的校验规则只检查请求体参数中传入的字段是否为空,字段类型是否与模型一致。当开发用户登录信息验证时,允许使用手机号、邮箱、用户名登录,进行需要进行正则校验。
- 模型
class UserInfo(AbstractUser):
source = models.ForeignKey(UserSource, verbose_name='用户来源', on_delete=models.CASCADE, default=1)
phone = models.CharField(verbose_name='手机号', max_length=20, blank=True, null=True)
class Meta:
verbose_name = '用户详细信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
- 序列化器
class LoginSerializer(serializers.ModelSerializer):
"""
用户登录序列化器
"""
username = serializers.CharField() # 重写 username , 否则会它会认为你想存数据
class Meta:
model = UserInfo
fields = ['username', 'password']
def validate(self, attrs):
# username phone email 都可能是登录账户
username = attrs.get('username')
password = attrs.get('password')
if re.match('^1[0-9]\d{9}$', username):
# 手机号正则
user = UserInfo.objects.filter(phone=username).first()
elif re.match('^.+@.+$', username):
# 邮箱登录正则
user = UserInfo.objects.filter(email=username).first()
else:
# 用户名登录
user = UserInfo.objects.filter(username=username).first()
if user and user.check_password(password):
# 如果登录成功,手动创建新令牌
user.last_login = timezone.now()
user.save()
refresh = RefreshToken.for_user(user)
token = str(refresh.access_token)
# 视图类和序列化类之间通过context这个字典来传递数据
self.context['token'] = token
self.context['userid'] = user.id
self.context['username'] = user.username
return attrs
else:
raise serializers.ValidationError('账号或密码错误')
- views视图
class LoginAPIView(APIView):
"""
用户登录
"""
def post(self.request):
# 实例化得到一个序列化类的对象
serializer = LoginSerializer(data=request.data)
# 序列化类的对象的校验方法
serializer.is_valid(raise_exception=True)
# 如果通过,表示登录成功,返回手动签发的token
token = serializer.context.get('token')
userid = serializer.context.get('userid')
username = serializer.context.get('username')
result = dict()
result['token'] = token
result['userid'] = userid
result['username'] = username
return Response(result, status=status.HTTP_200_OK)