DRF中的序列化


这里的序列化是指把 QuerySet对象序列化为 json, xml等Python中的数据类型。
DRF中序列化的用法和django中的 FORM用法极为相似。
有以下的django模型

env_info = (
    ("qa", "测试环境"),
    ("dev", "开发环境"),
    ("stage", "准生产环境"),
    ("live", "生产环境")
)

class Redis(models.Model):

    id   = models.AutoField(primary_key=True, verbose_name="id")
    env  = models.CharField(max_length=5, choices=env_info)
    ip   = models.CharField(max_length=32, verbose_name="ip地址")
    port = models.IntegerField(verbose_name="端口号")
    info = models.CharField(max_length=50, verbose_name="说明信息")

    def __str__(self):
        return  f"{self.id}"

    class Meta:
        verbose_name = "redis信息表"

定义一个序列器,使用它可以来序列化和反序列化相应的模型数据。

from rest_framework import serializers
from .models import env_info, Redis

class RedisSerializer(serializers.Serializer):
    """
    Redis model序列化
    """
    id     = serializers.IntegerField(read_only=True)
    env    = serializers.ChoiceField(required=True, choices=env_info)
    ip     = serializers.CharField(required=True, max_length=32)
    port   = serializers.IntegerField(required=True)
    info   = serializers.CharField(required=False, max_length=50)

序列器的定义和django自带的FORM特别像,需要序列化的字段在这里定义出来,对应的每个字段都有相应的属性,这些属性主要用于在反序列化时进行数据校验。

序列化

In [1]: from drf.models import Redis

In [2]: from drf.serializers import RedisSerializer

In [3]: redis = Redis.objects.get(pk=1)

In [4]: redis
Out[4]: <Redis: 1>

In [5]: serializers = RedisSerializer(redis)

In [6]: serializers.data
Out[6]: {'id': 1, 'env': 'stage', 'ip': '172.18.0.59', 'port': 7614, 'info': '股票排行榜'}

In [7]: type(serializers.data)
Out[7]: rest_framework.utils.serializer_helpers.ReturnDict

如上的形式,使用序列器很方便的把模型中的queryset对象转换为dict类型,这个是rest_framework中定义的字典类型。
在数据传输的时候一般使用json的形式,rest_famework中提供了转换为json格式的方法如下:

In [8]: from rest_framework.renderers import JSONRenderer

In [9]: JSONRenderer().render(serializers.data)
Out[9]: b'{"id":1,"env":"stage","ip":"172.18.0.59","port":7614,"info":"\xe8\x82\xa1\xe7\xa5\xa8\xe6\x8e\x92\xe8\xa1\x8c\xe6\xa6\x9c"}'

JSONRenderer()直接字典转换为bytes类型而json.dumps则把字典转化为str类型。

反序列化

给定一个dict数据,转化为对应的模型对象。

In [35]: info          # python中的字典
Out[35]: {'id': 1, 'env': 'stage', 'ip': '172.18.0.59', 'port': 7614, 'info': '股票排行榜'}

In [36]: type(info)
Out[36]: dict

In [32]: ser1 = RedisSerializer(data =info)         # 传入字典,序列化

In [33]: ser1.is_valid()                            # 数据验证,传入的数据是否是符合约束条件 
Out[33]: True

In [34]: ser1
Out[34]: 
RedisSerializer(data={'id': 1, 'env': 'stage', 'ip': '172.18.0.59', 'port': 7614, 'info': '股票排行榜'}):
    id = IntegerField(read_only=True)
    env = ChoiceField(choices=(('qa', '测试环境'), ('dev', '开发环境'), ('stage', '准生产环境'), ('live', '生产环境')), required=True)
    ip = CharField(max_length=32, required=True)
    port = IntegerField(required=True)
    info = CharField(max_length=50, required=False)

在上面的实例中,直接传入了一个字典,然后完成了反序列化;在rest_framework中还提供了一个parser函数,直接从接口中读取数据,然后把数据转化为json格式。

In [22]: from django.utils.six import BytesIO          # 这里使用BytesIO数据流模仿接口

In [23]: from rest_framework.parsers import JSONParser

In [24]: info
Out[24]: {'id': 1, 'env': 'stage', 'ip': '172.18.0.59', 'port': 7614, 'info': '股票排行榜'}

In [25]: stream = BytesIO(json.dumps(info).encode())
In [27]: stream
Out[27]: <_io.BytesIO at 0x7f291860b290>
In [28]: data = JSONParser().parse(stream)       # 可以直接从接口中读取数据,返回一个字典

In [29]: data
Out[29]: {'id': 1, 'env': 'stage', 'ip': '172.18.0.59', 'port': 7614, 'info': '股票排行榜'}

In [30]: ser = RedisSerializer(data =data)

In [31]: ser.is_valid()
Out[31]: True

保存数据

上面已经成功对数据做了反序列化,那么如果想要把数据保存到数据库中,就需要重写序列化器的createupdate方法;注意这两个方法是必须重写的。
在上面序列化的时候,执行了is_valid()方法;验证通过之后,已经验证过的数据会放入到validated_data中,因此传入createupdate中的数据一定验证之后的。
在上面的serializers.py中加入如下代码:

    def create(self, validated_data):
        """
        根据提供的验证过的数据返回一个redis实例
        :param validated_data:
        :return:
        """
        return Redis.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        根据提供的验证过的数据更新和返回一个已经存在的`Redis`实例
        :param instance:
        :param validated_data:
        :return:
        """
        # 注意这里的get方法,若是有对应key的值则取,否则使用原属性的值。
        instance.env = validated_data.get("env", instance.env)
        instance.ip = validated_data.get("ip", instance.ip)
        instance.port = validated_data.get("port", instance.port)
        instance.info = validated_data.get("info", instance.info)
        instance.save()
        return instance

如下一个实例,向数据库中新添加一条数据。

In [23]: data = JSONParser().parse(stream)              # 注意这里stream是BytesIO流,

In [24]: data
Out[24]: {'env': 'stage', 'ip': '172.18.0.59', 'port': 8614, 'info': '股票排行榜'}

In [25]: serializer = RedisSerializer(data=data)        # 序列化
 
In [26]: serializer.is_valid()                          # 数据校验,如果通过则返回True,校验之后的数据会放入到validate_data中。
Out[26]: True

In [28]: r1 = serializer.save()                         # save函数会调用序列器中定义的create方法,然后返回的是一个新增的对象

In [29]: r1                                             # 查看新增对象的信息   
Out[29]: <Redis: 64>

In [30]: r1.info
Out[30]: '股票排行榜'

In [31]: r1.env
Out[31]: 'stage'

**定义create和update方法,怎么区分调用呢?**调用.save()方法将创建新实例或者更新现有实例,具体取决于实例化序列化器类的时候是否传递了现有实例:

# .save() will create a new instance.
serializer = RedisSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = RedisSerializer(redis, data=data)

数据验证

反序列化数据的时候,你始终需要先调用is_valid()方法,然后再尝试去访问经过验证的数据或保存对象实例。如果发生任何验证错误,.errors属性将包含表示生成的错误消息的字典。
一个实例如下:

In [40]: new_info
Out[40]: {'env': 'stage', 'ip': '172.18.0.59', 'port': '8614', 'info': '股票排行榜'}

In [42]: new_info['env'] = 'haha'                       # env在上面定义为choices类型

In [43]: serializer = RedisSerializer(data=new_info)    # 序列化

In [44]: serializer.is_valid()                          # 数据验证,验证失败
Out[44]: False

In [45]: serializer.errors                              # 返回错误的原因
Out[45]: {'env': [ErrorDetail(string='"haha" is not a valid choice.', code='invalid_choice')]}

In [46]: serializer.error_messages
Out[46]: 
{'required': 'This field is required.',
 'null': 'This field may not be null.',
 'invalid': 'Invalid data. Expected a dictionary, but got {datatype}.'}

验证失败的抛出异常

.is_valid()方法使用可选的raise_exception标志,如果存在验证错误将会抛出一个serializers.ValidationError异常。

这些异常由REST framework提供的默认异常处理程序自动处理,默认情况下将返回HTTP 400 Bad Request响应。

In [48]: serializer.is_valid(raise_exception=True)
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
<ipython-input-48-55b906b95aaf> in <module>
----> 1 serializer.is_valid(raise_exception=True)

/usr/local/python3/lib/python3.7/site-packages/rest_framework/serializers.py in is_valid(self, raise_exception)
    226 
    227         if self._errors and raise_exception:
--> 228             raise ValidationError(self.errors)
    229 
    230         return not bool(self._errors)

ValidationError: {'env': [ErrorDetail(string='"haha" is not a valid choice.', code='invalid_choice')]}

字段级别的验证

可以对某个字段做额外单独的验证。例如上面的port端口;添加一个限制条件端口必须为4位数,这个时候可以如下设置。
你可以通过向你的Serializer子类中添加.validate_<field_name>方法来指定自定义字段级别的验证。这些类似于Django表单中的.clean_<field_name>方法。

这些方法采用单个参数,即需要验证的字段值。

你的validate_<field_name>方法应该返回一个验证过的数据或者抛出一个serializers.ValidationError异常.

serializers.py文件中添加如下一个函数

    def validate_port(self, value):
        if 1000 < value < 10000:
            return value
        raise serializers.ValidationError("The Port Must Be Four Digits")

然后更改port值为大于4位数,进行验证判断;【即便这里port写的是字符串,但是好像数字类型会转换成整型吧】

In [2]: new_info = {'env': 'stage', 'ip': '172.18.0.59', 'port': '8614', 'info': '股票排行榜'}

In [3]: new_info['port'] = 12345

In [4]: ser = RedisSerializer(data=new_info)

In [5]: ser.is_valid()
Out[5]: False

In [6]: ser.errors
Out[6]: {'port': [ErrorDetail(string='The Port Must Be Four Digits', code='invalid')]}
对象级别的验证

对象级别的验证,就是对某个新增的记录进行验证操作,可能涉及记录中的多个字段,在官方文档中有个实例如下:

from rest_framework import serializers

class EventSerializer(serializers.Serializer):
    description = serializers.CharField(max_length=100)
    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, data):
        """
        Check that the start is before the stop.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data
验证器

序列化器上的各个字段都可以包含验证器,通过在字段实例上声明,例如:

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])
    ...

序列化器类还可以包括应用于一组字段数据的可重用的验证器。这些验证器要在内部的Meta类中声明,如下所示:

class EventSerializer(serializers.Serializer):
    name = serializers.CharField()
    room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
    date = serializers.DateField()

    class Meta:
        # 每间屋子每天只能有1个活动。
        validators = UniqueTogetherValidator(
            queryset=Event.objects.all(),
            fields=['room_number', 'date']
        )

处理多个对象

序列化多个对象

上面的实例中,只序列化了一个对象,那么当序列化多个对象时,需要使用many=true作为参数传入,如下:

In [8]: redis = Redis.objects.all()[:10]
In [14]: serializers = RedisSerializer(redis, many=True)

In [15]: serializers.data
Out[15]: [OrderedDict([('id', 1), ('env', 'stage'), ('ip', '172.18.0.59'), ('port', 7614), ('info', '股票排行榜')]), OrderedDict([('id', 2), ('env', 'stage'), ('ip', '172.18.0.169'), ('port', 7614), ('info', '股票排行榜')]), OrderedDict([('id', 3), ('env', 'stage'), ('ip', '172.18.0.33'), ('port', 7615), ('info', '股票k线')]), OrderedDict([('id', 4), ('env', 'stage'), ('ip', '172.18.0.43'), ('port', 7617), ('info', '推送服务')]), OrderedDict([('id', 5), ('env', 'stage'), ('ip', '172.18.0.17'), ('port', 7618), ('info', '研报详情')]), OrderedDict([('id', 6), ('env', 'stage'), ('ip', '172.18.0.33'), ('port', 7618), ('info', '研报详情')]), OrderedDict([('id', 7), ('env', 'stage'), ('ip', '172.18.0.43'), ('port', 7619), ('info', '新闻爬取')]), OrderedDict([('id', 8), ('env', 'stage'), ('ip', '172.18.0.59'), ('port', 7620), ('info', '股票行情')]), OrderedDict([('id', 9), ('env', 'stage'), ('ip', '172.18.0.169'), ('port', 7620), ('info', '股票行情')]), OrderedDict([('id', 10), ('env', 'stage'), ('ip', '172.18.0.59'), ('port', 7622), ('info', '股票分钟k线')])]
反序列化多个对象

ListSerializer类能够序列化和一次验证多个对象。你通常不需要直接使用ListSerializer,而是应该在实例化一个序列化器时简单地传递一个many=True参数。
当一个序列化器在带有many=True选项被序列化时,将创建一个ListSerializer实例。该序列化器类将成为ListSerializer类的子类。
例如上面序列化的时候传递了many=True的参数,但是在反序列化的时候,就需要我们定义一个ListSerializer序列化器,用来存储多个数据。
serialisers.py文件中添加一个RedisListSerializer,然后在RedisSerializer类中的class Meta中添加如下定义。

class RedisListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        redis_list = [Redis(**item) for item in validated_data]
        return Redis.objects.bulk_create(redis_list)

#####
class Meta:
    list_serializer_class = RedisListSerializer               # 当反序列化时,传入many=True时,指定调用的类实例

测试如下:

In [4]: info
Out[4]: 
[{'env': 'stage', 'ip': '172.18.0.59', 'port': 7614, 'info': '股票排行榜'},
 {'env': 'stage', 'ip': '172.18.0.59', 'port': 8768, 'info': '股票排行榜'}]

In [5]: src = RedisSerializer(data=info, many=True)

In [6]: src.is_valid()
Out[6]: True

In [7]: src.save()               # 这里返回了更新的两个redis实例,因为在模型的__str__我们定义了返回id,id是自增的;这个应该在使用bulk_create时,返回值时,自增id1还未确定因此没有返回,但是这两个数据是已经插入成功了。
Out[7]: [<Redis: None>, <Redis: None>]

处理嵌套对象

这个是涉及当模型中含有外键时的处理方式。
定义如下两个模型

env_info = (
    ("qa", "测试环境"),
    ("dev", "开发环境"),
    ("stage", "准生产环境"),
    ("live", "生产环境")
)

class Redis(models.Model):

    id   = models.AutoField(primary_key=True, verbose_name="id")
    env  = models.CharField(max_length=5, choices=env_info)
    ip   = models.CharField(max_length=32, verbose_name="ip地址")
    port = models.IntegerField(verbose_name="端口号")
    info = models.CharField(max_length=50, verbose_name="说明信息")

    def __str__(self):
        return  f"{self.id}"

    class Meta:
        verbose_name = "redis信息表"

class line(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="业务id")
    name = models.CharField(max_length=30, verbose_name="业务名字")
    redis = models.ForeignKey(Redis, db_constraint=False, on_delete=models.CASCADE)

    class Meta:
        verbose_name = "业务表"

    def __str__(self):
        return f"{self.name}"

在序列化器中添加一个序列化其,如下:

class lineSerialiser(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=30)
    redis = RedisSerializer(required=False)

    def create(self, validated_data):
        redis_info = validated_data.pop("redis")
        reds = Redis.objects.create(**redis_info)
        return line.objects.create(redis=reds, **validated_data)

通过一个实例来说明,如何新增两个表的数据。这种更新形式,对数据的格式很依赖。

In [2]: info = {'name':'a','redis':{'env':'stage', 'ip': '172.18.0.59', 'port':7614,'info': '股票排行榜'}}

In [3]: lineSer = lineSerialiser(data=info)

In [4]: lineSer.is_valid()
Out[4]: True

In [5]: lineSer.save()
Out[5]: <line: a>

ModelSerializer

ModelSerializer类似于ModelFrom,相比于serializer有如下的不同:

  • 它根据模型自动生成一组字段。
  • 它自动生成序列化器的验证器,比如unique_together验证器。
  • 它默认简单实现了.create()方法和.update()方法。
    ModelSerializerserializer的用法基本一样,只不过是序列化时,减少了代码量,具体的用法可以参考ModelSerializer
    会在下面的更新实例中使用ModelSeriallizer
update更新操作

上面各种例子都是对模型进行插入操作,下面的实例对模型进行更新操作。更新操作对应http请求方法中的put操作
首先定义序列化器,在serializers.py文件定义如下类:

from rest_framework import serializers
from .models import Redis, line

class RedisListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        redis_list = [Redis(**item) for item in validated_data]
        return Redis.objects.bulk_create(redis_list)


class RedisModelSerializer(serializers.ModelSerializer):
    def update(self, instance, validated_data):
        instance.env = validated_data.get("env", instance.env)
        instance.ip = validated_data.get("ip", instance.ip)
        instance.port = validated_data.get("port", instance.port)
        instance.info = validated_data.get("info", instance.info)
        instance.save()
        return instance

    class Meta:
        model = Redis
        # fields = ("id", "env", "ip", "port")
        fields = "__all__"       # 此种形式包含所有的字段
        # exclude = ('info',)    # 这里的fileds和eclude只能出现一个

序列器定义完成之后,定义视图函数:ModelSerialiser类默认包含了create方法,因此这里只需要定义update即可。
视图函数如下:

@csrf_exempt
def table_list(request,id, *args, **kwargs):

    if request.method == "GET":
        redis = Redis.objects.all()
        serializers = RedisModelSerializer(redis, many=True)
        return JsonResponse(serializers.data)
    if request.method == "POST":
        content = JSONParser().parse(request)
        serializer = RedisModelSerializer(data=content)   # 这里只传入了请求的数据,因此是创建
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return HttpResponse(status=204)
    if request.method == "PUT":           # id值由路由规则定义
        print(id)
        try:
            redis = Redis.objects.get(pk=id)
        except Redis.DoesNotExist:
            return HttpResponse(status=404)
        content = JSONParser().parse(request)
        serializer = RedisModelSerializer(redis, data=content)    # 这里如果传入了redis实例则是更新,否则就是创建
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return HttpResponse(status=400)

定义路由

path('tables/',table_list),
path('tables/<int:id>', table_list)
# django2以上的版本,可以使用此种形式捕捉变量,

完成如上操作之后,可以使用get访问;这里只定义了访问全部数据的视图函数。

[root@vmtest mysite]# http http://10.9.68.201:8881/tables/
HTTP/1.1 200 OK
Content-Length: 161
Content-Type: application/json
Date: Wed, 04 Aug 2021 10:31:54 GMT
Server: WSGIServer/0.2 CPython/3.7.6
X-Frame-Options: SAMEORIGIN

[
    {
        "env": "stage",
        "id": 68,
        "info": "股票排行榜",
        "ip": "172.18.0.59",
        "port": 7614
    },
    {
        "env": "stage",
        "id": 69,
        "info": "股票排行榜",
        "ip": "172.18.0.59",
        "port": 6614
    }
]

然后使用put方法更新一条记录,借助于postman工具如下:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值