反序列化
读是序列化,写是反序列化。
前后台drf序列化:
- 后台到前台——序列化
- 前台到后台——反序列化
序列化和反序列化
标注:序列化 => 后台到前台(读) | 反序列化 => 前台到后台(写)
1)不管是序列化还是反序列化字段,都必须在fields中进行声明,没有声明的不会参与任何过程(数据都会被丢弃)
2)用 read_only 表示只读,用 write_only 表示只写,不标注两者,代表可读可写
3)自定义字段类型
- 自定义只读字段,在model类中用@property声明,默认就是read_only
# models.py
@property
def gender(self):
return self.get_sex_display()
- 自定义只写字段,在serializer类中声明,必须手动明确write_only
# serializer.py
re_password = serializers.CharField(write_only=True)
-
覆盖字段:
覆盖字段指,例如在models类中已经定义过password字段,但在serializer类中重写该字段,即为覆盖字段。
- 在serializer类中声明,没有明确write_only,是对model原有字段的覆盖,且可读可写。
也可以在括号内使用声明是只读或者只写,括号内不声明即为可读可写。
# serializer.py
password = serializers.CharField()
password = serializers.CharField(write_only=True)
校验校验、局部钩子和全局钩子
4)用 extra_kwargs
来为 写字段 制定基础校验规则(了解)
'required':
代表是否必须参与写操作,有默认值或可以为空的字段,该值为False;反之该值为True;也可以手动修改值- 虽然height在models中设置了默认值或者可以为空,但是可以通过
'required': True
来限制必须前台提供数据。 前台提供了该字段我就校验,没提交我就不校验,可以通过
'required': False
或为该字段设置检验规则。
# 校验规则
extra_kwargs = {
'name': {
'write_only': True,
# 'read_only': True
},
'password': {
'write_only': True,
'min_length': 3,
'max_length': 8,
'error_messages': { # 可以被国际化配置替代
'min_length': '太短',
'max_length': '太长'
}
},
'height': {
'required': True,
'min_value': 0,
}
}
5)每一个 写字段 都可以用局部钩子 validate_字段(self, value)
方法来自定义校验规则,成功返回value,失败抛出 exceptions.ValidationError('异常信息')
异常
6)需要联合校验的 写字段们,用 validate(self, attrs)
方法来自定义校验规则,,成功返回attrs
,失败抛出 exceptions.ValidationError({'异常字段': '异常信息'})
异常
from rest_framework import exceptions
def validate_name(self, value):
if 'g' in value.lower():
raise exceptions.ValidationError("这个g不行")
else:
return value
def validate(self, attrs):
print(attrs)
password = attrs.get('password') # 只是拿出来校验
re_password = attrs.pop('re_password') # 必须取出校验,因为不能入库
if password != re_password:
raise exceptions.ValidationError({'re_password': '两次密码不一致'})
else:
return attrs
开发流程:
1)在model类中自定义 读字段,在serializer类中自定义 写字段
2)将model自带字段和所以自定义字段书写在fields中,用write_only和read_only区别model自带字段
3)可以写基础校验规则,也可以省略
4)制定局部及全局钩子
案例:
settings.py
# root就是将文件夹添加到 os.path 中
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# url就是配置路由 /路由名/
MEDIA_URL = '/media/'
# 后台服务器地址
BASE_URL = 'http://localhost:8000'
主urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include('api.urls')), # /api/test/
# icon/default.png => path变量
# document_root必须指向icon/default.png所在路径 - media文件路径
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
]
子urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^users/$', views.UserAPIView.as_view()),
url(r'^users/(?P<pk>\d+)/$', views.UserAPIView.as_view()),
]
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from . import models, serializers
class UserAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk: # 单查
# 1)数据库交互拿到资源obj或资源objs
# 2)数据序列化成可以返回给前台的json数据
# 3)将json数据返回给前台
try:
obj = models.User.objects.get(pk=pk)
serializer = serializers.UserModelSerializer(obj, many=False)
return Response({
'status': 0,
'msg': 'ok',
'result': serializer.data
})
except:
return Response(
data={
'status': 1,
'msg': 'pk error'
},
status=status.HTTP_400_BAD_REQUEST, # 比直接写400更具有描述性
exception=True
)
else: # 群查
# 1)数据库交互拿到资源obj或资源objs
# 2)数据序列化成可以返回给前台的json数据
# 3)将json数据返回给前台
queryset = models.User.objects.all()
serializer = serializers.UserModelSerializer(queryset, many=True)
return Response({
'status': 0,
'msg': 'ok',
'results': serializer.data
})
def post(self, request, *args, **kwargs):
# 单增
# 1)从请求request中获得前台提交的数据
# 2)将数据转换成Model对象,并完成数据库入库操作(序列化组件完成)
# 3)将入库成功的对象列化成可以返回给前台的json数据(请求与响应数据不对等:请求需要提交密码,响应一定不展示密码)
# 4)将json数据返回给前台
# 1)将前台请求的数据交给序列化类处理
# 2)序列化类执行校验方法,对前台提交的所有数据进行数据校验:校验失败就是异常返回,成功才能继续
# 3)序列化组件完成数据入库操作,得到入库对象
# 4)响应结果给前台
serializer = serializers.UserModelSerializer(data=request.data)
if serializer.is_valid():
# 校验成功 => 入库 => 正常响应
obj = serializer.save()
return Response({
'status': 0,
'msg': 'ok',
'result': '新增的那个对象'
}, status=status.HTTP_201_CREATED)
else:
# 校验失败 => 异常响应
return Response({
'status': 1,
'msg': serializer.errors,
}, status=status.HTTP_400_BAD_REQUEST)
"""
序列化类BaseSerializer
def __init__(self, instance=None, data=empty, **kwargs):
pass
instance:是要被赋值对象的 - 对象类型数据赋值给instance
data:是要被赋值数据的 - 请求来的数据赋值给data
kwargs:内部有三个属性:many、partial、context
many:操作的对象或数据,是单个的还是多个的
partial:局部修改数据的时候需要将partial=True
context:用户视图类和序列化类之间传参,例如视图类传入{'request': request},序列化类传入{'a': '234'}
"""
"""
响应类Response
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
pass
data:响应的数据 - 空、字符串、数字、列表、字段、布尔
status:网络状态码
template_name:drf说自己也可以支持前后台不分离返回页面,但是不能和data共存(不会涉及)
headers:响应头(不用刻意去管)
exception:是否是异常响应(如果是异常响应,可以赋值True,没什么用)
content_type:响应的结果类型(如果是响应data,默认就是application/json,所有不用管)
"""
serializers.py
from rest_framework import serializers
from rest_framework import exceptions
from . import models
class UserModelSerializer(serializers.ModelSerializer):
# 自定义反序列化字段(所有校验规则自己定义,也可以覆盖model已有的字段)
# 覆盖model有的字段,不明确write_only会参与序列化过程
password = serializers.CharField(min_length=4, max_length=8, write_only=True)
# 自定义字段,不明确write_only序列化会报错,序列化会从model中强行反射自定义字段,但是model没有对于字段
re_password = serializers.CharField(min_length=4, max_length=8, write_only=True)
class Meta:
# 该序列化类是辅助于那个Model类的
model = models.User
# 设置参与序列化与反序列化字段
# 插拔式:可以选择性返回给前台字段(插头都是在Model类中制造)
# fields = ['name', 'age', 'height', 'sex', 'icon]
# 第一波分析:
# 1)name和age,在fields中标明了,且没有默认值,也不能为空,入库时必须提供,所有校验时必须提供
# 2)height,在fields中标明了,但是有默认值,所有前台不提供,也能在入库时采用默认值(可以为空的字段同理)
# 3)password,没有在fields中标明了,所以校验规则无法检测password情况,但是即使数据校验提供了,
# 也不能完成入库,原因是password是入库的必备条件
# 4)gender和img是自定义插拔@property字段,默认就不会进行校验(不参与校验)
# fields = ['name', 'age', 'height', 'gender', 'img']
# 第二波分析:
# 1)如何区分 序列化反序列化字段 | 只序列化字段(后台到前台) | 只反序列化字段(前台到后台)
# 不做write_only和read_only任何限制 => 序列化反序列化字段
# 只做read_only限制 => 只序列化字段(后台到前台)
# 只做write_only限制 => 只反序列化字段(前台到后台)
# 2)对前台到后台的数据,制定基础的校验规则(了解)
# CharField(max_length, min_length, errors_kwargs)
# DecimalField(min_value, max_value, max_digits, decimal_places,error_messages)
# 3)如果一个字段有默认值或是可以为空,比如height,如何做限制
# 虽然有默认值或是可以为空,能不能强制限制必须提供,可以通过required是True来限制
# 前台提交了该字段,我就校验,没提交我就不校验:1)required是False 2)有校验规则
fields = ['name', 'age', 'password', 'height', 'gender', 'img', 're_password']
# 第三波分析
# 1)制定的简易校验规则(没有制定)后,可以再通过字段的 局部钩子 对该字段进行复杂校验
# 2)每个字段进行逐一复杂校验后,还可以进行集体的 全局钩子 校验
# 涉及对自定义反序列化字段的校验:re_password(要参与校验,但是不会入库)
# 校验规则
extra_kwargs = {
'name': {
# 'write_only': True,
# 'read_only': True
},
'password': {
'write_only': True,
'min_length': 3,
'max_length': 8,
'error_messages': { # 可以被国际化配置替代
'min_length': '太短',
'max_length': '太长'
}
},
'height': {
# 'required': True,
'min_value': 0,
}
}
# 注:局部全局钩子,是和Meta同缩减,属于序列化类的
# 局部钩子:validate_要校验的字段(self, 要校验的字段值)
# 全局钩子:validate(self, 所以字段值的字典)
# 校验规则:成功返回传来的数据 | 失败抛出异常
def validate_name(self, value):
if 'g' in value.lower():
raise exceptions.ValidationError("这个g不行")
else:
return value
def validate(self, attrs):
print(attrs)
password = attrs.get('password') # 只是拿出来校验
re_password = attrs.pop('re_password') # 必须取出校验,因为不能入库
if password != re_password:
raise exceptions.ValidationError({'re_password': '两次密码不一致'})
else:
return attrs