DRF模型序列化器常见需求场景总结

如果我们想要使用序列化器对应的是 Django 的模型类,DRF 为我们提供了 ModelSerializer 模型类序列化器来帮助我们快速创建一个 Serailizer 类。通常情况下,使用ModelSerializer内置的Meta可以满足大多数开发需求使用,对于常见的其他需求,在这里做了简单的总结归纳以及代码示例。

一、ModelSerializer 与Serializer

1. ModelSerializer 与Serializer的区别

ModelSerializer包含了serializer的全部功能,但除此之外,还提供了如下功能:

  • 基于 模型类自动生成一系列字段
  • 基于模型类自动为 Serializer 生成 validators
  • 包含默认的 create() 和 update() 的实现

在这里插入图片描述

2. meta选项参数

选项参数说明
model模型类名指明参照哪个模型类
fieldsall 表名包含所有字段,也可以写明具体哪些字段指明为模型类的哪些字段生成
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)

查看更多

微信公众号

微信公众号同步更新,欢迎关注微信公众号第一时间获取最近文章。在这里插入图片描述

博客网站

崔亮的博客-专注devops自动化运维,传播优秀it运维技术文章。更多原创运维开发相关文章,欢迎访问https://www.cuiliangblog.cn

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值