DRF的序列化组件
首先我们要知道序列化是干嘛的,在此之前我们应该知道json格式的数据,一般在前后端交互或者是跨平台交互的时候,会默认使用Json格式拉进行数据的传输,所以当我们把普通的数据转换成json格式的时候就会使用序列化组件,将其序列化成json格式,然后前端接收到json格式之后,再反序列化把json格式数据转换成普通格式的数据,再进行逻辑运算,判断以及渲染页面等.
而DRF的序列化组件,所完成的功能也是如此,负责将对象数据序列化前台所需要的数据,或者反序列化前台的数据,进行校验,来确保数据的安全.
下面我们就介绍三种DRF中最常用的序列化组件,Serializer,ModelSerializer,以及ListModelSerializer.
Serializer组件
在使用Serializer组件之前,我们要先生成一个序列化器,在我们django的项目名(这里我们定义项目名为api)下面新建一个serializer.py文件,在里面继承serializers,生成序列器,供我们在视图函数里面调用:
#/api/models.py
from django.db import models
class User(models.Model):
CHOICES_SEX = ((0, '男'), (1, '女'))
name = models.CharField(max_length=64)
pwd = models.CharField(max_length=64, null=True)
age = models.IntegerField(default=0)
height = models.DecimalField(max_digits=5, decimal_places=2, default=0)
icon = models.ImageField(upload_to='icon', default='default.png')
sex = models.IntegerField(choices=CHOICES_SEX, default=0)
#/api/serializers.py,在这里生成序列化器
from rest_framework import serializers
class UserSerializer(serializers.Serializer):
'''
这里是声明序列化类,都是models.py已有的字段
如果要参与序列化,这里的字段名字一定要和models.py里面的属性同名,如果不参与序列化,就不要在这里声明字段
'''
name = serializers.CharField()
age = serializers.IntegerField()
height = serializers.DecimalField(max_digits=5, decimal_places=2)
'''
下面是自定义序列化字段,序列化的属性值由方法来提供,
方法的名字:固定为 get_属性名,
方法的参数:self为序列化对象,obj为序列化的model对象
注意 : 建议自定义序列化字段名不要与model已有的属性名重名,否则会覆盖model已有字段的声明
注意 : 自定义序列化字段要用SerializerMethodField()作为字段类型
'''
gender = serializers.SerializerMethodField()
def get_gender(self, obj):
return obj.get_sex_display()
在完成以上序列化器生成之后,我们就可以在views.py里面去定义我们的方法,从而来使用序列化器,当然,在views.py定义之前,我们需要在urls.py里面写入路由匹配关系,实例如下,分为序列化和反序列化:
序列化
序列化数据通常是通get请求里面取出来的,常用来查询数据库
# api/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^v1/users/$', views.UserAPIView.as_view()),
url(r'^v1/users/(?P<pk>\d+)/$', views.UserAPIView.as_view()),
]
# api/views.py,
'''在视图函数里写我们的业务逻辑,大致分三步:
1. 通过ORM操作数据库取到前端需要的数据
2. 将数据序列化
3. 将序列化后的数据返回给前端
'''
from rest_framework.views import APIView
from rest_framework.response import Response
from django.conf import settings
from . import models
class UserAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
# 这里是单查的写法,即前端发来的url里面有拼接参数的时候,会走if里面的流程,即我们有匹配条件,通过这些条件去数据库里面查询相应的数值
if pk:
# 1. 通过ORM取数据
user_obj = models.User.objects.filter(pk=pk).first()
if not user_obj:
return Response({
'status': 1,
'msg': '单查 error'
})
# 2. 将数据序列化
# 序列化时里面除了需要序列化的对象以外,还有一个many参数,该参数的意义是: 如果要序列化的数据是单个对象,many=False,如果要序列化的对象数据是多个对象,many=True
user_ser = serializers.UserSerializer(user_obj, many=False)
user_data = user_ser.data
# class MySerializer: pass
# user_data = MySerializer(user_obj)
# 3. 把序列化后的数据返回给前端
return Response({
'status': 0,
'msg': '单查 ok',
'results': user_data
})
# 群查,即前端发来的url连接不携带拼接参数,会将所有的数据都查出来
# 1. 通过ORM取数据
user_query = models.User.objects.all()
# 2. 将数据序列化
user_list_data = serializers.UserSerializer(user_query, many=True).data
# 3. 把序列化后的数据返回给前端
return Response({
'status': 0,
'msg': '群查 ok',
'results': user_list_data
})
反序列化
反序列化我们需要生成另一个序列化生成器,反序列化的数据一般是从post请求里面取出来的,且通常用来增加数据库里面的数据
# /api/serializers.py,生成反序列化生成器
class UserDeserializer(serializers.Serializer):
'''
反序列器的生成要注意以下几点:
1. 系统的字段,可以在Field类型中设置系统校验规则,比如(name=serializers.CharField(min_length=3))
2. required校验规则绝对该字段是必校验还是可选校验字段(默认required为True,数据库字段有默认值或可以为空的字段required可以赋值为False)
3. 自定义的反序列字段,设置系统校验规则同系统字段,但是需要在自定义校验规则中(局部、全局钩子)将自定义反序列化字段取出(返回剩余的数据与数据库交互)
4. 局部钩子的方法命名 validate_属性名(self, 属性的value),校验规则为 成功返回属性的value 失败抛出校验错误的异常
5. 全局钩子的方法命名 validate(self, 所有属性attrs),校验规则为 成功返回attrs 失败抛出校验错误的异常
'''
name = serializers.CharField(min_length=3, max_length=64, error_messages={
'required': '姓名必填',
'min_length': '太短',
})
pwd = serializers.CharField(min_length=3, max_length=64)
# 系统可选的反序列化字段:没有提供不进行校验(数据库中有默认值或可以为空),提供了就进行校验
age = serializers.IntegerField(min_value=0, max_value=150, required=False)
# 自定义反序列化字段:一定参与校验,且要在校验过程中,将其从入库的数据中取出,剩余与model对应的数据才会入库
re_pwd = serializers.CharField(min_length=3, max_length=64)
# 自定义校验规则:局部钩子,全局钩子
# 局部钩子:validate_字段名(self, 字段值)
# 规则:成功返回value,失败抛异常
def validate_aaa(self, value):
if 'g' in value.lower():
raise serializers.ValidationError('名字中不能有g')
return value
# 全局钩子:validate(self, 所有校验的数据字典)
# 规则:成功返回attrs,失败抛异常
def validate(self, attrs):
# 取出联合校验的字段们:需要入库的值需要拿到值,不需要入库的需要从校验字段中取出
pwd = attrs.get('pwd')
re_pwd = attrs.pop('re_pwd')
if pwd != re_pwd:
raise serializers.ValidationError({'re_pwd': '两次密码不一致'})
return attrs
# create重写,完成入库
def create(self, validated_data):
return models.User.objects.create(**validated_data)
# /api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.conf import settings
from . import models
class UserAPIView(APIView):
def post(self, request, *args, **kwargs):
'''
反序列化通常也有三步操作:
1. 从请求对象中拿到前台的数据
2. 校验前台数据是否合法
3. 反序列化成后台Model对象与数据库交互
'''
request_data = request.data
# 反序列化时里面除了需要反序列化的对象以外,同样一个many参数,如果要序列化的数据是单个对象,many=False,如果要序列化的对象数据是多个对象,many=True,不写的话默认是False
user_ser = serializers.UserDeserializer(data=request_data)
# 调用反序列化的校验规则有两种,即系统规则和自定义规则(局部钩子,全局钩子)
result = user_ser.is_valid()
if result:
# 校验通过,可以与数据库进行交互:增(create),改(update)
user_obj = user_ser.save()
return Response({
'status': 0,
'msg': 'ok',
'results': serializers.UserSerializer(user_obj).data
})
else:
# 校验失败,返回错误信息
return Response({
'status': 1,
'msg': user_ser.errors
}, status=status.HTTP_400_BAD_REQUEST)
以上就是Serializer组件序列化以及反序列化的用法和注意事项,实际上这是一种较底层的用法,所以我们在生产中常用ModelSerializer来完成数据的序列化和反序列化,整个的代码量会更少,开发效率更高.
ModelSerializer组件
ModelSerializer组件除了Serializer组件所有的功能以外,另外提供了一些功能,比如:
- 可以基于模型类自动生成一系列字段,无需手动生成
- 可以基于模型类自动为Serializer生成validators,比如uinque_together
- ModelSerializer组件会自动实现默认的create()和update()功能,无需手动去实现
序列化和反序列化
# ModelSerializer可以将序列化和反序列化的功能整合成一个类,这个类继承自rest_framework.serializers.ModelSerializer
# api/serializers.py
from rest_framework.serializers import ModelSerializer
from . import models
class UserModelSerializer(ModelSerializer):
'''
该生成器包括三个部分:
1. Meta子类:
里面用model来绑定关联model表
用fields来设置所有的序列化反序列化字段
用extra_kwargs来设置系统的校验规则,比如长短,报错信息提示等
2. 局部钩子
3. 全局钩子
'''
'''
该生成器里可以完成的事情:
1. 将序列化类与Model类进行绑定
2. 设置序列化与反序列化所有字段(并划分序列化字段与反序列化字段)
3. 设置反序列化的局部钩子与全局钩子
'''
# 自定义反序列化字段,校验规则只能在声明自定义反序列化字段时设置,且一定是write_only
re_pwd = serializers.CharField(min_length=3, max_length=64, write_only=True)
class Meta:
model = models.User
fields = ['name', 'age', 'height', 'gender', 'pwd', 're_pwd']
extra_kwargs = {
'name': {
'required': True,
'min_length': 3,
'error_messages': {
'min_length': '太短'
}
},
'age': {
'required': True, # 数据库有默认值或可以为空字段,required默认为False
'min_value': 0
},
'pwd': {
'required': True,
'write_only': True, # 只参与反序列化,这里注意,required不能和read_only一起使用,规则会冲突
},
'gender': {
'read_only': True, # 只参与序列化
},
}
def validate_name(self, value):
if 'g' in value.lower():
raise serializers.ValidationError('名字中不能有g')
return value
def validate(self, attrs):
pwd = attrs.get('pwd')
re_pwd = attrs.pop('re_pwd')
if pwd != re_pwd:
raise serializers.ValidationError({'re_pwd': '两次密码不一致'})
return attrs
# 项目名/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^v2/users/$', views.UserV2APIView.as_view()),
url(r'^v2/users/(?P<pk>\d+)/$', views.UserV2APIView.as_view()),
]
#/api/views.py
from rest_framework.views import APIView
class UserV2APIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
# 单查,即url链接有拼接内容
if pk:
user_obj = models.User.objects.filter(pk=pk).first()
if not user_obj:
return Response({
'status': 1,
'msg': '单查 error'
})
# 完成序列化
user_data = serializers.UserModelSerializer(user_obj, many=False).data
return Response({
'status': 0,
'msg': '单查 ok',
'results': user_data
})
# 群查,url链接没有拼接内容,全查
user_query = models.User.objects.all()
# 完成序列化
user_list_data = serializers.UserModelSerializer(user_query, many=True).data
return Response({
'status': 0,
'msg': '群查 ok',
'results': user_list_data
})
# 单增
def post(self, request, *args, **kwargs):
request_data = request.data
user_ser = serializers.UserModelSerializer(data=request_data)
# 校验失败,直接抛异常,反馈异常信息给前台,只要校验通过,代码才会往下执行
result = user_ser.is_valid()
if result:
user_obj = user_ser.save()
return Response({
'status': 0,
'msg': 'ok',
'results': serializers.UserModelSerializer(user_obj).data
})
else:
# 校验失败,返回错误信息
return Response({
'status': 1,
'msg': user_ser.errors
}, status=status.HTTP_400_BAD_REQUEST)
自定义Response方法
在上面的序列化与反序列化方法中,我们多次用到return Response({})
返回值,且里面的内容有诸多相似,经常都有status
,msg
,results
等,所以其实我们可以自定义一个Response方法,继承自原来DRF的response方法,并对其做二次封装,类似于我们之前所了解的把相似类提取出来生成一个基类,别的都继承自该基类即可.
所以我们在应用名下面新建一个response.py
文件,用以写我们二次封装的Response方法,如下:
# api/response.py
from rest_framework.response import Response # 这里导入DRF原本的Response,并在下面作为父类导入,继承
class APIResponse(Response):
'''
这里我们继承自父类的init,然后重写其中的__init__,其中status和msg给默认值,然后其余默认值均为None,最后的**kwargs可以接收多余的所有键值对并传给前端
'''
def __init__(self,status=0,msg='ok',results=None,http_status=None,headers=None,exception=False,**kwargs):
# data里面所写的是response的基础数据状态码和数据状态信息
data = {
'status':status,
'msg':msg
}
# results是后端传给前端的数据,如果有的话,就在data里面添加,一起返回给前端,如果没有就不添加
if results is not None:
data['results']=results
# 更新**kwargs里面接收到的所有键值对,并添加到data里面
data.update(**kwargs)
# 下面是直接调用父类的init方法,然后把相应的数据赋值进去
super().__init__(data=data,status=http_status,headers=headers,exception=exception)
# 在封装完我们自己的response之后,我们就可以在View.py方法里面导入然后直接使用了
# /api/views.py
from .response import APIResponse
from rest_framework import status
return Response({
'status': 1,
'msg': user_ser.errors
}, status=status.HTTP_400_BAD_REQUEST)
# 上面这个就可以改成下面这种一行的形式,可以极大简化代码量.
return APIResponse(1,user_ser.errors,http_status=status.HTTP_400_BAD_REQUEST)
基表相关
基表和基类的概念比较相似,其实就是定义一个继承自models.Model
的类,然后用到的表类里面都继承该基表,从而减少代码量,节省操作.
基表配置的关键属性是abstract=True
,且要定义在内部的Meta类里面,实例如下:
# api/models.py
class BaseModel(models.Model):
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
# 该属性就表示该表是基表,可以供普通Model类继承使用
# 另外还有一点就是,设置了abstract的表类不会在执行数据库迁移命令(makemigration | migrate)的时候新建表
abstract = True
DRF中ORM的多表关联操作
外键设计
跳出DRF这个概念来说的话,ORM的多表关联我们应该是知道一些的,多表关系一共三种,其外键常存在的位置也不尽相同,如下:
- 一对多关系:外键放在多的一方,也就是一对多关系中多的那一方
- 多对多关系:外键放在常用的一方
- 一对一关系:外键放在不常用的一方
而跨表操作我们通常也有一个口诀,就是正向跨表直接点属性,反向跨表表名小写加属性.
DRF中的跨表操作有些区别,使用起来更加简单,不过models
里面定义的时候需要加上related_name
反向查询字段,实例如下:
# api/models.py
class Author(BaseModel):
name = models.CharField(max_length=16)
class AuthorDetail(BaseModel):
mobile = models.CharField(max_length=11)
# 这里正向查询点Author,反向查询直接点detail即可,
author = models.OneToOneField(to='Author',related_name='detail')
断级联
级联我们都知道是什么,级联的意义在于两个有级联关系的表,一旦一个数据被删除了,跟其级联相关的另一个表的数据也会被删除,实际上这对于数据的增删来说是非常麻烦的一件事,所以在DRF中我们要执行断级联这种操作,断级联的优点在于:
- 表与表之间不会再有外键关联,但是有逻辑关联
- 断级联之后不会影响数据库查询表的效率,但是会极大的提高数据库的增删改的效率
- 断级联之后一定要通过逻辑代码来保证表与表之间数据的安全,而且有特定的关键字
on_delete
来表示其不同的级联关系
DRF的models.py
里面断级联的参数为db_constraint
,我们将其设置为False即可断级联,不同级联关系的表示方式如下:
# /api/models.py
'''
我们假设四种情况,可以用四种on_delete级联方式:
'''
# 1. on_delete=models.CASCADE,作者和作者详情表是一对一级联,且详情表会随着作者表的删除而删除,就需要在详情表里面的author字段里加入该属性
class AuthorDetail(BaseModel):
author=models.OneToOneField(to='Author',db_constraint=False,on_delete=models.CASCADE)
# 2. on_delet=models.DO_NOTHING,假设有出版社表和图书表,是一对多的关系,且图书不会随着出版社的删除而随之删除,那么就可以设置on_delete=models.DO_NOTHING
class Book(BaseModel):
publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
# 3. null=True,on_delete=models.SET_NULL,假设有部门表和员工表,是一对多的关系,员工没有部门,可以为空
class Employee(BaseModel):
section = models.ForeignKey(to='Section',related_name='employee',db_constraint=False,null=True,on_delete=models.SET_NULL)
# 4. default=0,on_delete=models.SET_DEFAULT,一样有部门表和员工表,在部门表删除的时候,员工不会为空部门,而是自动进入一个默认的部门,即default值
class Employee(BaseModel):
section = models.ForeignKey(to='Section',related_name='employee',db_constraint=False,default=0,on_delete=models.SET_DEFAULT)
注意一点的是,只有一对一和一对多关系的表才有on_delete
字段,多对多关系的表没有这个字段,要注意.