文章目录
这里的序列化是指把
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
保存数据
上面已经成功对数据做了反序列化,那么如果想要把数据保存到数据库中,就需要重写序列化器的create
和update
方法;注意这两个方法是必须重写的。
在上面序列化的时候,执行了is_valid()
方法;验证通过之后,已经验证过的数据会放入到validated_data
中,因此传入create
和update
中的数据一定验证之后的。
在上面的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()方法。
ModelSerializer
和serializer
的用法基本一样,只不过是序列化时,减少了代码量,具体的用法可以参考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
工具如下: