Django REST Framework笔记(四)序列化数据的可读性改造

前面已学习了基础的序列化器和各种情况下的视图处理,接下来便进一步学习序列化器,涉及一下三方面:

  • 修改序列化器,控制响应数据的输出格式(数据可读性)
  • 反序列化数据时的验证
  • 重写序列化器自带的create和update方法

为了方便学习,需要稍微调整一下model字段。


#models.py
from django.db import models

class Company(models.Model):
    company_name = models.CharField(max_length=50)

    def __str__(self):
        return self.company_name

class Tag(models.Model):
    tag = models.CharField(max_length=30)

class User(models.Model):
    STATUS_CHOICE = (
        ('1', '有效账号'),
        ('2', '无效账号')
    )
    user_name = models.CharField(max_length=20, unique=True)
    nick_name = models.CharField(max_length=30, unique=True)
    password = models.CharField(max_length=20)
    company = models.ForeignKey(Company, on_delete=models.DO_NOTHING)
    status = models.CharField(max_length=20, choices=STATUS_CHOICE, default=1)
    tags = models.ManyToManyField(Tag)

    def __str__(self) -> str:
        return self.user_name

除了原来的外键,还增加了choice字段及m2m字段,以便学习各种类型的字段在序列化器中的表现。

发起一波请求,看看数据。
在这里插入图片描述
响应的数据,涉及到外键、m2m及choice时,只显示id和所选中的数字,没由可读性,前端拿到这个数字也毫无意义。

所以,改造序列化器的第一步,就是要提高数据的可读性,要确保给到前端的数据具有可读性,当然,也要携带相关的id才可以,不然前端发来的数据又该如何使用呢?

可读性改造

  • 第一种 在序列化器中自定义字段覆盖model字段,并指定source来源

先看代码

#使用serializers.ModelSerializer自定义序列化器 简单得一批
class UserModelSerializer(serializers.ModelSerializer):
	#自定义一个字段,指定来源为"get_status_display"即choice字段选项
	cn_status = serializers.CharField(source='get_status_display')

    class Meta:
        model = User
        exclude = ['password']
        readonly = ('id', )

响应结果:(假设请求了id为4的user实例)


{
	"id": 4,
	"user_name": "POSTER",
	"nick_name": "阿牛哥(PUT请求修改的)",
	"cn_status": "有效帐号",
	"company": {
		"id": 1,
		"company_name": "好运来科技有限公司"
	},
	...
}

显而易见,cn_status取代了原来的status字段。
如果只是响应请求,那么这种写法也不是不可以,毕竟cn_status直观可读。

  • 第二种 使用serializers.SerializerMethodField改造

SerializerMethodField继承了Field,使用SerializerMethodField是构造一个非model字段的过程,现在接着改造。

class UserModelSerializer(serializers.ModelSerializer):
	cn_status = serializers.SerializerMethodField()

    class Meta:
        model = User
        exclude = ['password']
        readonly = ('id', )
    
    def get_cn_status(self, obj):
        if obj.status == '1':
            return '有效帐号'
        else:
            return '无效账号'

注意下方的get_cn_status的方法,这个是必须要写的,格式为get_{field_name},参数为当前在操作的实例。

学到这里,发现拿一个CHOICE字段来举例,有点不太妥当。SerializerMethodField用来显示Model中不存在的字段,也就是自定义构造的字段,所以并不能通过反序列化来修改。其实,CHOICE选项也就那么几个,和前端同步好对应的值不就可以了嘛。

不过,既然写了,还是请求一下,看看数据的结构吧。


{
		"id": 2,
		"cn_status": "有效帐号",
		"user_name": "amo2",
		"nick_name": "好家伙2",
		"status": "1",
		"company": 2

其实,这么细细一看,也不是不行,毕竟status: 1这是可以修改的,至于cn_status: 有效帐号只为了友好显示而已,哈哈。

  • 第三种 设置模型关联深度 depth = {int}
class UserModelSerializer(serializers.ModelSerializer):
    cn_status = serializers.SerializerMethodField()

    class Meta:
        model = User
        exclude = ['password']
        readonly = ('id', )
        depth = 1
    
    # 针对SerializerMethodField构造的字段,必须要写的方法,格式为 `ge_{filed_name}`
    def get_cn_status(self, obj):
        if obj.status == '1':
            return '有效帐号'
        else:
            return '无效账号'

这种方法会序列化所有字段及关联模型的全部信息,发个请求看下结构。

[
	{
		"id": 2,
		"cn_status": "有效帐号",
		"user_name": "amo2",
		"nick_name": "好家伙2",
		"status": "1",
		"company": {
			"id": 1,
			"company_name": "好运来科技有限公司"
		},
		"tags": [
			{
				"id": 3,
				"tag": "积极的"
			},
			{
				"id": 4,
				"tag": "勇猛的"
			}
		]
	},
	{
		"id": 3,
		"cn_status": "有效帐号",
		"user_name": "amo3333",
		"nick_name": "好家伙23333",
		"status": "1",
		"company": {
			"id": 2,
			"company_name": "天天高歌电子商务有限公司"
		},
		"tags": [
			{
				"id": 1,
				"tag": "激情的"
			},
			{
				"id": 2,
				"tag": "稳重的"
			}
		]
	},
	{
		"id": 4,
		"cn_status": "有效帐号",
		"user_name": "POSTER",
		"nick_name": "阿牛哥(PUT请求修改的)",
		"status": "1",
		"company": {
			"id": 1,
			"company_name": "好运来科技有限公司"
		},
		"tags": [
			{
				"id": 2,
				"tag": "稳重的"
			},
			{
				"id": 3,
				"tag": "积极的"
			}
		]
	},
	{
		"id": 7,
		"cn_status": "有效帐号",
		"user_name": "AMO",
		"nick_name": "广州二五仔",
		"status": "1",
		"company": {
			"id": 2,
			"company_name": "天天高歌电子商务有限公司"
		},
		"tags": [
			{
				"id": 4,
				"tag": "勇猛的"
			}
		]
	},
	{
		"id": 8,
		"cn_status": "有效帐号",
		"user_name": "AAC",
		"nick_name": "中山二五仔",
		"status": "1",
		"company": {
			"id": 2,
			"company_name": "天天高歌电子商务有限公司"
		},
		"tags": [
			{
				"id": 4,
				"tag": "勇猛的"
			}
		]
	}
]

depth只和关联模型相关,如外键多对多等。配合着SerializersMethodField使用,基本上能满足大部分字段的序列化/反序列化操作了。

  • 第四种 嵌套序列化器

这种理念在django的admin.py中经常使用。


#设置一个company的序列化器,然后在User的序列化器中使用
class CompanyModelSerializer(serializers.ModelSerializer):
	class Meta:
        model = Company
        fields = "__all__"

class UserModelSerializer(serializers.ModelSerializer):
    cn_status = serializers.SerializerMethodField()
    #自定义字段使用company的序列化器
    company = CompanyModelSerializer()

    class Meta:
        model = User
        exclude = ['password']
        readonly = ('id', )

    
    # 针对SerializerMethodField构造的字段,必须要写的方法,格式为 `ge_{filed_name}`
    def get_cn_status(self, obj):
        if obj.status == '1':
            return '有效帐号'
        else:
            return '无效账号'

这里就不发起请求了,代码改来改去有点费劲,上面这个方法最后的效果是,所嵌套的company的序列化器的全部信息能显示出来。

在实际的场景中,使用哪个序列化器,取决了实际需求,大概总结一下:

  • 自定义字段 + source指定来源,这个方法会覆盖原有的字段
  • SerializersMethodField + get_{field_name},构造新的非Model字段,不适用于对反序列化有修改需求的情况
  • 嵌套序列化器,如果关联模型只有一个,这个方法还是非常好用的,毕竟在关联模型的序列化器中,还可以自定义fields来控制哪些字段不能参与序列化
  • 关联模型深度设置,depth = {int},这个方法很方便,但关联模型所有的数据都会显示出来,需要做风险管理。

8月21日调整

在后面的学习中,碰到了一个问题:


class UserModelSerializer(serializers.ModelSerializer):
    cn_status = serializers.SerializerMethodField()

    class Meta:
        model = User
        exclude = ['password']
        readonly = ('id', )
        depth = 1

depth = 1时,带外键的字段会变成只读,导致反序列化保存失败。经过好几天的百度,对序列化器和视图做出以下调整:

  • 重写序列化器,有只在序列化时使用的序列化器和只在反序列化时使用的序列化器
  • 使用视图集来编写视图(其实也挺好用)
#序列化器
class UserModelGetSerializer(serializers.ModelSerializer):
    """
        序列化时用此序列化器
    """
    cn_status = serializers.SerializerMethodField()

    class Meta:
        model = User
        exclude = ('password',)
        readonly = ('id', )
        depth = 1

    def get_cn_status(self, obj):
        if obj.status == '1':
            return '有效账号'
        return '无效账号'

class UserModelPostPut(serializers.ModelSerializer):
    """
        反序列化时用此序列化器
    """
    cn_status = serializers.SerializerMethodField()
    password = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = '__all__'
        readonly = ('id', )

    def get_cn_status(self, obj):
        if obj.status == '1':
            return '有效账号'
        return '无效账号'

#视图集
from rest_framework import viewsets
from .serializers import UserModelGetSerializer, UserModelPostPut

class UserViewSets(viewsets.ModelViewSet):
    queryset = User.objects.all()

    def get_serializer_class(self):
        serializer_class = self.serializer_class #由于封装中内置了serializer_class,所以这里必须使用self.serializer_class,否则serializer_class就变成一个新的变量了
        if self.request.method in ('PUT', 'POST',):
            serializer_class = UserModelPostPut
        if self.request.method in ('GET', ):
            serializer_class = UserModelGetSerializer
        return serializer_class
    
    #这里需要重写update方法,用来处理前端传来的数据不包含密码修改的情况
    def update(self, request, *args, **kwargs):
        if not request.data.get('password'):
            password = User.objects.get(id=request.data['id']).password
            request.data['password'] = password
            return super().update(request, *args, **kwargs)
    
    #重写destroy 返回一个`删除`的字符串,不重写也没事,这个方法不需要调用序列化器
    def destroy(self, request, *args, **kwargs):
        super().destroy(request, *args, **kwargs)
        return Response('删除')


#路由设置
    path('v6/user/', UserViewSets.as_view({'get': 'list', 'post': 'create'})),
    path('v6/user/<int:pk>', UserViewSets.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
    #视图集有自带的写法,当然也可以像我这样显式编写,不过as_view需要传入字典,"请求方法": "处理动作",key和value都是内置的

这样能实现序列化时,可以使用depth = 1 带来的效果,且不输出密码选项,反序列化时就调用常规的序列化器,且自动处理密码。

好了,这个问题终于搞定了。下一章接学习数据的验证吧。

不过这里貌似使用generics.*替代视图集或许更合适点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值