赞
踩
在前后端分离开发中,对于RESTfulAPI设置,我们一般需要将查询/更新数据以JSON方式进行返回,而Django本身所带的ORM,仅仅支持数据的查询,并不能将查询得到的数据转换为JSON/Python的字典易读格式,而DRF为我们提供了方便的序列化器自定义类,可以通过定义序列化类,方便的返回我们所需要的数据。
DRF的序列化器,位于以下位置
from rest_framework import serializers
序列化:
ORM查询结果/对象 —> 转换为Python原生数据类型/其他可读类型(如字典或列表)—> 便于转换JSON/其他传输格式
反序列化:
请求数据(通常为JSON) -> Python可读取数据结构,对象/字典
简单来讲,其实就是实现了序列与反序列化的过程,并且支持了数据验证
!但是,必须明白的是,序列化器是在Model模型层只后的,也就是说,序列化器的验证数据必须首先能通过ORM模型的验证!
使用序列化器需要实现以下步骤:
serializers.Serializer的序列化器类。在序列化器类中,定义字段,每个字段对应一个模型中的属性或数据。CharField、IntegerField等),以及其他可选参数(如required、default、label等)。serialize()方法,将数据模型转换为Python原生数据类型,然后可以将其转换为JSON或其他格式。deserialize()方法,将接收的请求数据转换为Python对象,以便在视图中进行处理。创建序列化器之前,我们需要先定义模型类
models.py 模型类
from django.db import models
class User:
username = models.CharField(max_length=32, verbose_name='用户名', null=False, blank=False)
password = models.CharField(max_length=128, verbose_name='密码', null=False, blank=False)
balance = models.PositiveIntegerField(verbose_name='余额', null=False, blank=False, default=0)
# 余额使用正整数
register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)
phone = models.CharField(max_length=11, verbose_name='电话号码', blank=True)
# 电话号码使用字符串
email = models.CharField(max_length=32, verbose_name='邮箱', blank=True)
# 邮箱使用字符串
birthday = models.DateField(verbose_name='生日', blank=True)
序列化器的创建必须是建立在models之上的,所以对于字段的限制,只能比model更加严格,否则在save的时候就会报错
class UserSerialize(serializers.Serializer):
"""
创建序列化器,必须继承serializers.Serializer
"""
username = serializers.CharField(max_length=32, label='用户名', required=True,)
password = serializers.CharField(max_length=128, label='密码', required=True,)
balance = serializers.IntegerField(min_value=0, default=0, label='余额', required=False,)
phone = serializers.CharField(max_length=11, label='电话号码', required=True)
# 电话号码使用字符串
email = serializers.CharField(max_length=32, label='邮箱', required=False)
# 邮箱使用字符串
birthday = serializers.DateField(label='生日', required=False)
图上可以看到serializers本身就带有字段验证功能,所以这里使用serializers.Field类型即可
Tips:对于余额(正整数)在models中定义的是PositiveIntegerField,但是这里并没有这个类型,但是序列化器中的整数类型,可以设置max_value以及min_value参数,用于限制最大最小值,并且序列化器,对于是否为空的控制,使用required参数控制,label标签类似model中的verbose_name
具体可选参数,可见2.3所示
| 字段类型 | 说明 | 常见坑 |
|---|---|---|
| CharField | 字符串字段,通常用于文本或短字符串。 | 默认情况下,max_length 参数必须提供,并且不能为空。 |
| TextField | 长文本字段,用于存储较长的文本内容。 | 在使用数据库存储时,注意数据库的文本类型与DRF中的TextField的映射。 |
| IntegerField | 整数字段,用于存储整数值。 | 限制范围时,要谨慎设置min_value和max_value参数。 |
| FloatField | 浮点数字段,用于存储浮点数值。 | 精度问题,浮点数运算可能会导致精度损失,建议使用DecimalField处理货币等敏感数据。 |
| DecimalField | 十进制字段,用于存储高精度的十进制数值。 | 使用时要指定max_digits和decimal_places参数,避免精度丢失。 |
| BooleanField | 布尔字段,用于存储True或False值。 | 序列化时,布尔字段的默认值可能是null,要注意处理这种情况。 |
| DateField | 日期字段,用于存储日期值。 | format参数用于设置日期格式,默认:“YYYY-MM-DD” |
| TimeField | 时间字段,用于存储时间值。 | format参数用于设置时间格式,默认:“HH:MM:SS” |
| DateTimeField | 日期时间字段,用于存储日期和时间值。 | 同上,默认格式:“YYYY-MM-DDTHH:MM:SS.ssssss” |
| DurationField | 时间间隔字段,用于存储时间间隔值。 | format设置时间间隔格式,默认“ISO 8601”->“14 03:20:30.123456” |
| UUIDField | UUID字段,用于存储全局唯一标识符。 | UUID格式问题,要根据实际需求指定UUID格式。 |
| URLField | URL字段,用于存储URL地址。 | URL格式问题,要根据实际需求指定URL格式。 |
| EmailField | 电子邮件字段,用于存储电子邮件地址。 | 邮箱格式问题,要根据实际需求指定邮箱格式。 |
| FileField | 文件字段,用于存储上传的文件。 | 文件上传问题,要注意处理文件上传时的文件类型、大小和保存路径。 |
| ImageField | 图片字段,用于存储上传的图片文件。 | 图片上传问题,要注意处理图片上传时的文件类型、大小和保存路径。 |
| SerializerMethodField | 序列化方法字段,用于将序列化器中的方法返回的值作为字段。 | 要确保序列化器方法返回的数据类型与预期的字段类型一致。 |
| ListField | 列表字段,用于存储列表值。 | 列表元素类型问题,要注意指定列表元素的类型,确保列表元素类型与预期一致。 |
| DictField | 字典字段,用于存储字典值。 | 字典键值类型问题,要注意指定字典键值的类型,确保键值类型与预期一致。 |
| ChoiceField | 选择字段,用于存储预定义选项中的一个值。 | 选项值问题,要确保字段值在预定义的选项中,否则可能会导致序列化或反序列化失败。 |
| MultipleChoiceField | 多选字段,用于存储预定义选项中的多个值。 | 多选项值问题,要确保字段值都在预定义的选项中,否则可能会导致序列化或反序列化失败。 |
| PrimaryKeyRelatedField | 主键关联字段,用于表示与其他模型的主键相关联。 | 要确保关联的对象在数据库中存在,否则可能会导致反序列化失败。 |
| SlugRelatedField | Slug关联字段,用于表示与其他模型的slug字段相关联。 | 要确保关联的对象在数据库中存在,否则可能会导致反序列化失败。 |
| HyperlinkedIdentityField | 超链接标识字段,用于返回对象的超链接标识。 | 需要配合view_name参数和url配置,确保返回正确的超链接标识。 |
| HyperlinkedRelatedField | 超链接关联字段,用于表示与其他模型的URL字段相关联。 | 需要配合view_name参数和url配置,确保返回正确的关联URL。 |
| Serializer | 嵌套序列化器字段,用于处理复杂的嵌套关系。 | 嵌套序列化器的使用要注意循环引用和性能问题。 |
| 参数 | 类型 | 说明 |
|---|---|---|
| required | bool | 反序列化时是否为必填字段。默认为True。 |
| default | Any | 字段的默认值。如果字段缺失或为None,则使用默认值。 |
| allow_null | bool | 是否允许字段的值为None。默认为False。 |
| read_only | bool | 是否将字段标记为只读。只读字段在反序列化时会被忽略,仅在序列化输出中显示。 |
| write_only | bool | 是否将字段标记为只写。只写字段在序列化输出时会被忽略,仅在反序列化时用于验证和保存数据。 |
| label | str | 字段的可读标签,用于生成用户友好的字段名称,与Django原生的verbose_name参数类似。 |
| help_text | str | 字段的帮助文本,用于提供关于字段的附加说明。 |
| validators | list | 验证器列表,用于验证字段的值是否满足预期的规则。 |
| error_messages | dict | 自定义错误消息,用于在字段验证失败时返回自定义的错误信息。 |
| style | dict | 字段的显示样式,用于在HTML表单中设置字段的样式。 |
| trim_whitespace | bool | 是否自动去除字段值前后的空格。默认为False。 |
| source | str | 字段的数据源,用于指定序列化器从模型中获取数据的属性名称。 |
| write_only | str | 字段的数据目标,用于指定序列化器将数据保存到模型时的属性名称。 |
| child | Serializer | 用于嵌套字段的子序列化器。用于处理复杂的嵌套关系。 |
序列化即将ORM的查询结果QuerySet/单个查询对象转换为原生类型/其他可读类型
这里通过get请求的视图函数进行序列化
# 序列化数据|语法格式:
序列化器类(
instance=QuerySet/model_obj, # 需要序列化的数据模型实例(ORM的数据集或单个数据),
many=Bool, # 默认False,如果是True就是多个数据(例如返回多个用户就要设置True,返回一个用户就是False)
)
class UserAPIView(APIView): def get(self, request): """ 获取所有用户的信息 """ # ORM获取全部信息 userlist = User.objects.all() # 使用序列化器序列化数据 serializer = UserSerialize(instance=userlist, many=True) # 因为返回所有用户,所以many=True # 返回一个序列化实例化对象 print(serializer) # 返回一个OrderedDict(有序字典) print(serializer.data) return serializer.data
通过上面可以看到serializer.data返回一个OrderedDict(有序字典),打印出来如下:
# OrderedDict数据示例: [ OrderedDict([ ('username', 'user01'), ('password', '123456'), ('balance', 8888), ('phone', '13939333333'), ('email', 'xxxx@qq.com'), ('birthday', None) ]), OrderedDict([ ('username', 'Jack'), ('password', '6666666'), ('balance', 32156), ('phone', '17666666666'), ('email', 'yyyy@gmail.com'), ('birthday', None) ]), ...... ]
这样看,其实就是将里面的多个 [(),()]列表嵌套元组,传入到OrderedDict对象中,如果把OrderedDict当成与dict类似的类
其实本质上返回的结果就是一个列表嵌套字典的数据
但是,我们怎么才能得到这样的数据呢?如果直接返回这个请求会显示什么?
from django.http import HttpResponse # 导入Django的HttpResponse
def get(self,request):
......省略
return HttpResponse(serializer.data) # 直接返回序列化后的对象
大错特错了!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yzd1U3NB-1690975268747)(C:\Users\RYF55\AppData\Roaming\Typora\typora-user-images\image-20230802160028394.png)]
可以发现,最后返回的,不过就是上面的数据帮你转换成了字符串,但是我们要的是JSON啊
这时,我们就要用到DRF的Response
如果我们将数据序列化,必须使用DRF的Response对象进行返回数据
from rest_framework.response import Response # 导入DRF的Response
返回数据
from rest_framework.response import Response # 导入DRF的Response
def get(self,request):
......省略
return Response(serializer.data) # 使用DRF的Response返回数据
返回的数据示例如下:
[ { "username": "user01", "password": "123456", "balance": 8888, "phone": "13939333333", "email": "xxxx@qq.com", "birthday": null }, { "username": "Jack", "password": "6666666", "balance": 32156, "phone": "17666666666", "email": "yyyy@gmail.com", "birthday": null }, { "username": "Tom", "password": "98999999", "balance": 1324, "phone": "17688888866", "email": "mmmm@vip.com", "birthday": null } ]
这才是我们需要获取到的JSON数据
反序列化:将请求数据(JSON)转换为Python数据类型
# 反序列化|语法格式:
序列化器类(
data=request.data, # 需要序列化的数据模型实例(POST请求的JSON),
)
查看反序列化后的数据
# 查看反序列化结果|语法格式
# 查看结果之前,必须先调用ser.is_valid()对数据进行验证
serializer = 序列化器类(data=request.query_params)
if serializer.is_valid():
print(serializer.data)
现在,我们定义一个POST方法
class UserAPIView(APIView):
def post(self,request):
"""
创建用户信息
"""
serializer = UserSerialize(data=request.query_params)
print(serializer.is_valid()) # 查看数据验证结果
---> 任意一个数据只要和类字段创建的时候的字段类型和验证情况不满足就返回False,全部满足才会True
print(serializer.data) # 查看反序列化结果
---> 返回dict -> {'username': '666', 'password': '123456', 'balance': 66666, 'phone': '13966666666'}
在3.2中讲解返回JSON数据时,既然GET请求是获取所有数据,那么不就把我的密码也给返回出来了??但是这种敏感数据怎么能随便的在这里返回呢?
对于此类敏感数据,往往只会用于做数据判断,并不会真正返回,所以在序列化定义对象时,我们就应该设置"只写属性"
class UserSerialize(serializers.Serializer):
"""
创建序列化器
"""
password = serializers.CharField(max_length=128, label='密码', required=True,write_only=True)
# 定义密码属性只能写,不能进行读取!
此时返回的数据,可以发现,已经没有密码这一字段了
[
{
"username": "user01",
"balance": 8888,
"phone": "13939333333",
"email": "xxxx@qq.com",
"birthday": null
}
]
当然还有只读字段,例如对于学号等唯一标识符,我们不能进行更新,所以要定义只读,但是对于序列化器的保存,将在下一章节讲解!!
如果按照正常的思路,当我们通过is_valid数据验证后,就要将数据往数据库里面存,那就得 自己调用XXX.objects.create(…),如果每次数据验证,都要写这么一长串,那还是非常麻烦的,所以DRF在这里,为我们提供了钩子函数,让我们通过调用.save()就可以将数据保存到数据库。
DRF本身不带自动存储,需要我们自己去重写方法
DRF添加钩子函数的目的是,让我们只用调用一个save()方法,就可以自动识别到底是该update还是create,例如现在这个数据已经存在了,就调用update,不存在就调用create去创建,所以我们可以查看"序列化器的基类- BaseSerializer"中的save方法源码
.save()方法实现了什么?
.save实现的就是,在我们更新/创建数据的请求时,例如RESTful中的POST/PUT/PATCH请求中,根据请求参数,直接识别,我们是需要更新参数,还是新建参数,让我们只需要调用一个save就可以实现保存,不需要自己区分update或者create方法。
class BaseSerializer(Field): """ 序列化器的基类- BaseSerializer """ ...... # 其他代码省略 def update(self, instance, validated_data): """ 这是原本的update方法,无任何实现,只有一个raise,翻译就是必须让我们自己实现update方法。 """ raise NotImplementedError('`update()` must be implemented.') def create(self, validated_data): """ create和update一样 """ raise NotImplementedError('`create()` must be implemented.') ############ 重点 ############# def save(self, **kwargs): """ 原本的save方法,实现了保存逻辑(具体保存内容,需要自行实现create以及update) """ ...... ...... 省略上述代码,均为查找属性 validated_data = {**self.validated_data, **kwargs} # 这一句是定义验证通过的数据字典 if self.instance is not None: """ 如果self.instance存在(不是None),也就是说如果我们实例化序列化器的时候 传入了instance,且不是None,就代表这个数据原本是存在的 现在我们再去调用save就是为了更新数据 """ # 所以DRF帮我们直接调用了update方法 self.instance = self.update(self.instance, validated_data) assert self.instance is not None, ( '`update()` did not return an object instance.' ) else: """ 如果self.instance不存在,那就是数据没有创建 """ # 就帮我们调用create方法 self.instance = self.create(validated_data) assert self.instance is not None, ( '`create()` did not return an object instance.' ) # 最后返回一个模型类对象的实例(更新后的) # 这个self.instance最后是通过create/update获取的 # 所以我们在定义update以及create的时候,也要返回一个更新后的模型类对象实例 return self.instance
create仅仅是提供的一个钩子函数,需要我们自己进行重写
# 重写格式:
class UserSerialize(serializers.Serializer):
username = serializers.CharField(max_length=32, label='用户名', required=True, )
...... # 其他省略
def create(self, validated_data):
"""
序列化器的create方法
"""
return User.objects.create(**validated_data) # 执行创建ORM新数据的函数,必须返回一个更新后的模型类实例
# 这里的self.validated_data,就是已经通过is_vaild方法验证过的参数
update方法用于更新数据
# 重写格式:
class UserSerialize(serializers.Serializer):
username = serializers.CharField(max_length=32, label='用户名', required=True, )
...... # 其他省略
def update(self, instance, validated_data):
instance.username = validated_data.get('username', instance.username)
instance.password = validated_data.get('username', instance.password)
instance.balance = validated_data.get('username', instance.balance)
instance.phone = validated_data.get('username', instance.phone)
instance.email = validated_data.get('username', instance.email)
instance.birthday = validated_data.get('username', instance.birthday)
instance.save()
return instance # 必须返回一个更新后的模型类实例
urls.py 定义路由
from django.urls import path
import bookmanage.views
urlpatterns = [
path("user/", bookmanage.views.UserView.as_view()), # 不传入主键的用于get方法查询全部数据,以及POST方法创建数据
path("user/<int:pk>/", bookmanage.views.UserDetailView.as_view()), # pk参数传入主键id的用于其他方法查询单条/更新单条信息
# 这里因为pk不存在需要单独定义一个视图,所以这里要使用两个视图
]
接下来我们通过定义POST方法对数据进行创建
class UserView(APIView): def post(self, request): """ 创建用户信息 """ serializer = UserSerialize(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) # 保存成功就返回保存后的数据 else: return Response({"msg":"保存失败!!!"}) #### 在save和update方法中分别添加 class UserSerialize(serializers.Serializer): ...... ...... def update(self, instance, validated_data): print(f'我更新了数据:{validated_data}') ...... def create(self, validated_data): print(f'我创建了数据:{validated_data}') ......
请求体body
{
"username":"666",
"password":"123456",
"balance":66666,
"phone":"13966666666",
"birthday":"2002-12-14"
} // 第一次创建666用户,所以调用save时,一定会提示创建用户
结果:
我创建了数据:{'username': '666', 'password': '123456', 'balance': 66666, 'phone': '13966666666', 'birthday': datetime.date(2002, 12, 14)}
class UserDetailView(APIView):
def put(self, request, pk):
"""
put方法用于更新用户全部信息
:param pk: url解析式传来的主键<pk>->id
"""
user = User.objects.get(id=pk)
serializer = UserSerialize(instance=user, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response({"msg": "更新失败!!!"})
请求体body
{
"username":"666",
"password":"123456",
"balance":888888,
"phone":"13966666666",
"birthday":"2002-12-14"
} // put方法用于更新数据,所以调用save时,一定会提示更新用户
我更新了数据:{'username': '666', 'password': '123456', 'balance': 88888, 'phone': '13966666666', 'birthday': datetime.date(2002, 12, 14)}
对于一些额外的验证功能,例如需要根据正则去实现的功能,或者说DRF本身没有额外提供的数据验证,我们可以自定义validators参数,进行数据验证
示例:验证你有没有500块钱,如果没有500,就报错”穷B,这不是你该买的东西!“
def money_min_validator(money):
if money < 500:
raise serializers.ValidationError("穷B,这不是你该买的东西!")
# 如果验证失败,需要按以下格式抛出异常
class UserSerializer(serializers.Serializer):
name = serializers.CharField(label='姓名', max_length=6, )
money = serializers.IntegerField(label="年龄", validators=[money_validator,]) # 可以添加多个数据验证器,会依次传入并验证
# serializer.errors可以返回验证失败的原因
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
print(serializer.errors) # 打印错误信息
->>> {'balance': [ErrorDetail(string='穷逼,这不是你该买的!', code='invalid')]}
return Response({"msg": "保存失败!!!")
另外两种定义额外验证器的方法
class UserSerializer(serializers.Serializer):
money = serializers.IntegerField(label="存款")
# ------------------------------------------ #
def validate_money(self, value): # 这里命名必须是 : validate_{字段名}命名
if value < 500:
raise serializers.ValidationError("穷B,这不是你该买的")
return value
class UserSerializer(serializers.Serializer):
money = serializers.IntegerField(label="存款")
name = serializers.IntegerField(label="用户名")
# ------------------------------------------ #
money = attrs['money'] # 使用attrs局部变量取值
name = attrs['name']
if money < 500:
raise serializers.ValidationError("穷B,这不是你该买的")
elif re.search("^[a-zA-Z]",name):
raise serializers.ValidationError("用户名必须以字母开头!!!")
raise serializers.ValidationError("穷B,这不是你该买的")
return value
### 5.2.2 类中创建validate方法进行多字段补充验证 ```python class UserSerializer(serializers.Serializer): money = serializers.IntegerField(label="存款") name = serializers.IntegerField(label="用户名") # ------------------------------------------ # money = attrs['money'] # 使用attrs局部变量取值 name = attrs['name'] if money < 500: raise serializers.ValidationError("穷B,这不是你该买的") elif re.search("^[a-zA-Z]",name): raise serializers.ValidationError("用户名必须以字母开头!!!")
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。