最近复习一下DRF框架(高度封装,快速开发),结合开发经验和理解。特写笔记,以供自己学习回顾和朋友们参考学习。
学习本框架首先要搞清楚,什么是序列化及反序列化,因为序列化是前后端分离框架的核心!
将模型对象转换为json数据 称之为 序列化 后端向前端传数据 (后传前,序列化)
将json转换为数据对象 称之为 反序列化前端向后端传数据(前传后,反序列)
举个例子
# 查询项目下的节点
class SelectNode(GenericAPIView):
"""
查询项目节点列表
"""
def post(self, request, *args, **kwargs):
serializer = SelectNodeSerializers(data=request.data) # json转对象 反序列化
verify_token(request)
if serializer.is_valid():
# 对象转json 序列化
nodes = serialize("json", Node.objects.filter(node_project_name=serializer.data['node_project_name']))
node = json.loads(nodes)
response_data = {'code': ['200'], 'data': node}
return HttpResponse(json.dumps(response_data))
return Response(serializer.errors)
用来访问主表属性时可以访问其从表(一对多,多的那个表)的信息 以及级联删除
例子:
进行一对多关联的返回 :一个书籍下有多个人物
目的:查询书籍及书籍里的人物
主表 书籍表model
class BookInfo(models.Model):
title = models.CharField(max_length=50)
pub_date = models.DateField()
readcount = models.IntegerField(default=0)
comment = models.IntegerField(default=0)
is_delete = models.BooleanField(default=False)
class Meta:
db_table = 'books' # 指明数据库表名
verbose_name = '图书' # 在admin站点中显示的名称
verbose_name_plural = verbose_name # 显示的复数名称
def __str__(self):
"""定义每个数据对象的显示信息"""
return self.title
副表 人物表
# 定义英雄模型类
class HeroInfo(models.Model):
GENDER_CHOICES = (
(0, 'male'),
(1, 'female')
)
hname = models.CharField(max_length=20, verbose_name='名称')
hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
hcomment = models.CharField(max_length=200, null=True, verbose_name='描述信息')
hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE,verbose_name='图书') # '外键'
class Meta:
db_table = 'tb_heros'
verbose_name = '英雄'
verbose_name_plural = verbose_name
def __str__(self):
return self.hname
hbook字段的BookInfo属性表明主表模型类 此外 on_delete属性必须标明,用来进行级联删除,即删除某book其下关联的英雄全部删除
# 自定义序列化器
class BookSerialzier(serializers.Serializer):
# 序列化返回字段
title = serializers.CharField(max_length=60)
readcount = serializers.IntegerField()
pub_date = serializers.DateField()
# 返回关联的英雄id
# heroinfo_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
# 返回关联英雄的str方法值
heroinfo_set = serializers.StringRelatedField(read_only=True, many=True)
注意返回子表的字段属性名一定要是子表model类小写加_set
View代码
# 附带查询从表数据
class Books(View):
def get(self, request):
# 1 查询所有对象
books = BookInfo.objects.all()
ser = BookSerialzier(books, many=True)
return JsonResponse(ser.data, safe=False)
# 级联删除
class DeleteBook(View):
def get(self,request):
books = BookInfo.objects.get(title='西游记')
books.delete()
return JsonResponse("删除成功")
PrimaryKeyRelatedField 返回关联对象的id
StringRelatedField 返回关联模型类的str方法值
单独定义序列化器
例子:
class HeroInfoSerialzier(serializers.Serializer):
# 英雄序列化器
hname = serializers.CharField()
hcomment = serializer.CharField()
hook = serializers.StringRelatedField()
# 返回值加入英雄所在书籍(返回字段名需和自己model中定义的外键字段名相同)
h_book = serializers.PrimaryKeyRelatedField(read_only=True,)
class BookSerialzier(serializers.Serializer)
# 书籍序列化器
# 序列化字段
btitle = serializers.CharField(max_length=20,min_lenth=5)
bread = serializers.Integerfield(max_value=100)
# 返回关联英雄的model中str方法定义的返回值
# heroinfo_set = serializers.StringRelatedField(read_only=True,many=True)
heroinfo_set = HeroInfoSerialzier(many = True)
**
如果字段属性中添加read_only 那么表明此字段只参加序列化返回,不参加序列化验证,即此字段是用来读取的 不存
如果字段属性中添加write_only 那么表明此字段值不参加序列化返回,参加序列化验证,即此字段是拿来存的不是用来读的
ser.validated_data 为序列化器验证后的字段数据
选项参数的验证
class BookSerialzier(serializers.Serializer):
# 序列化返回字段 (length 字符串长度 单个汉字长度为1)
title = serializers.CharField(min_length=4, max_length=60)
read_count = serializers.IntegerField(max_value=100, min_value=2)
pub_date = serializers.DateField(required=False)
# 当指定默认值 或者 allow_null时 前端可以不用传此值
comment = serializers.IntegerField(default=10)
# require =False 可以前端可以不传此值 默认为True
view
def post(self, request):
data = request.body.decode()
data_dic = json.loads(data)
# 此处必须指定data参数
ser = BookSerialzier(data=data_dic)
# 加了raise_exception=True的话 就自动抛出异常 不在response了
ser.is_valid(raise_exception=True)
if not ser.is_valid():
# 返回验证的错误结果
return JsonResponse(ser.errors)
# 如果is valid 验证错误的话 就不能保存数据
# ser.save()
return JsonResponse(ser.data)
序列化器中单一字段验证 和多个字段的验证,其中单一字段验证需validate_字段名。
第一步 在序列化器中定义create方法
validated_data 接受验证后的字段数据
def create(self,validated_data):
# 保存数据 第一种方式
BookInfo.objects.create(btitle=validated_data["btitle"])
# 保存数据 第二种方式 **拆包处理 例子在下面
BookInfo.objects.create(**validated_data)
return book
如需同时保存多个数据,需使用事务
def create(self,validated_data):
try:
# 设置回滚点
with atomic():
# 保存数据 第二种方式 **拆包处理 例子在下面
BookInfo.objects.create(**validated_data)
xxxxxxx略
except:
# 回滚到回滚点
return book
第二步 在views中保存数据
# 类继承APIview的情况
def post(self,request):
# 1 获取前端数据
data_dict = request.data
# 2 验证数据
ser = BookSerialzier(data=data_dict)
# 3 保存数据 此处调用的是序列化器中的create方法
ser.save()
# 4 返回结果 此处的data来自于序列化器中的create方法 返回的book对象json数据
return JsonResponse(ser.data)
第一步序列化器中定义update方法
# instance 是序列化器初始化时传来的数据对象
def update(self,instance,validated_data):
# 更新数据
instance.btitle=validated_data["btitle"]
instance.bread = validated_data["bread"]
instance.save()
return instance
第二步视图中定义put方法
def put(self,request,pk)
# 1获取前端数据
data= request.body.decode()
data_dict = json.loads(data)
# 验证数据
try:
book = BookInfo.objects.get(id=pk)
except :
return JsonResponse({'error':"错误信息"},status=400)
# 此处的book就是instance值
ser = BookSerialzier(book,data=data_dict)
# 更新数据
ser.save()
# 4 返回结果
return JsonResponse(ser.data)
save() 方法源码 对instance进行判断 有instance则使用update方法 ,没有则使用create方法
2 ModelSerializer比Serializer多了create 与 update方法。
即继承了ModelSerializer的序列化器不用再写update和create方法 在view中直接可以使用save方法
第三 实现了对字段唯一值的验证
注意 fields 与 exclude 不能同时存在
且field = 'all’为 验证全部字段
虽然根据模型类生成了字段及其验证方式,但我们也可另外指定验证规则
如果模型类字段属性的长度没有任何规定 则会默认最大长度
例
可以通过显示指明字段的方式 进行限制字段长度
也可通过extra_kwargs={ }来进行字段属性的修改
read_only_fields=(“id”,“xx”,“xx”) 指明只读字段 即仅用于序列化输出的字段
**
增加模型类中没有的字段
注意:此处field若不等于"all" 则需在field中指明增加的字段
APIview 继承Django的View类 增加的功能: 权限(判断用户有没有操作权限) ,认证(认证是否为注册用户), 限流(反爬虫)
GenericAPIView 继承自APIView 增加的功能:
1分页
2过滤排序
3指定查询集
例子 books =Book.objects.all()
查询集特性:1惰性执行 (生成查询集的时候不进行数据库查询操作,当对查询集进行具体的操作才进行数据库查询操作) 2缓存 查询过一次后不再从数据库中查询 ,而是在已查询完的数据中操作
queryset = (查询集) Book.objects.all()
指定序列化器 serializer_class = 序列化器
即可使用data = request.data 获取data
常用属性
使用rest_framework.request
from rest_framework.request import Request
使用APIview之前使用的是JsonResponse
使用APIview后可以直接使用rest_framework的Response类
from rest_framework.request import Response
此时 获取查询字符串可以用request.query_parms
示例代码
class Test(APIView):
authentication_classes = []
permission_classes = []
# serializer_class = EditcompanySerializer
def get(self, request):
# 1 注意此处get后只能加单‘’
id = request.query_params.get('id')
print(request.query_params.get('id'))
# print(id)
if id:
books = BookInfo.objects.get(id=id)
ser = BookSerialzier(books)
else:
books = BookInfo.objects.all()
ser = BookSerialzier(books, many=True)
return Response(ser.data)
# 新建保存数据 测试需使用form-data表单传输数据
def post(self, request):
# 输出 request中传入的查询字符串
print(request.query_params)
ser = TestSerializer(data=request.data)
ser.is_valid()
ser.save()
return Response(ser.data)
# 更新数据
def put(self, request):
# 1获取前端数据
id = request.GET.get("id")
# request.data 是表单里的数据
data = request.data
# id = s.data["id"]
# 2验证数据
try:
c = ContactUs.objects.get(id=id)
except:
return Response("没取到数据")
ser = TestSerializer(c, data=data)
ser.is_valid()
# 3更新数据
ser.save()
return Response(ser.data)
继承自APIview
from rest_framework.generics import GenericAPIView
指定序列化器
指定查询集
例子:
class Test(GenericAPIView):
authentication_classes = []
permission_classes = []
# 指定当前类视图使用的序列化器
serializer_class = TestSerializer
# 指定当前类视图使用的查询集数据
queryset = ContactUs.objects.all()
def get(self, request):
# 获取查询集中的所有数据
contactus = self.get_queryset()
# 使用指定序列化器,获取序列化器对象(即上面的TestSerializer)
ser = self.get_serializer(contactus, many=True)
return Response(ser.data)
# 保存数据 测试需使用form-data表单传输数据
def post(self, request):
data = request.data
ser = self.get_serializer(data=data)
ser.is_valid()
ser.save()
return Response(ser.data)
# 更新数据
def put(self, request, pk):
# 1获取前端数据
# request.data 是表单里的数据
data = request.data
# id = s.data["id"]
# 2验证数据
try:
# 使用pk值从查询集中获取指定的单一数据对象()
c = self.get_object()
except:
return Response("没取到数据")
ser = TestSerializer(c, data=data)
ser.is_valid()
# 3更新数据
ser.save()
return Response(ser.data)
业务逻辑没有变化 代码量也没有简化多少 为什么要单独创建一个GenericApiview类呢?
因为要和拓展类配合使用,从而简化代码
# 拓展类 此时函数的业务逻辑交给拓展类来执行
class Test(GenericAPIView, CreateModelMixin, ListModelMixin):
authentication_classes = []
permission_classes = []
# 指定当前类视图使用的序列化器
serializer_class = TestSerializer
# 指定当前类视图使用的查询集数据
queryset = ContactUs.objects.all()
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
# 拓展类 此时函数的业务逻辑交给拓展类来执行
class Test_Update(GenericAPIView, UpdateModelMixin, DestroyModelMixin):
authentication_classes = []
permission_classes = []
# 指定当前类视图使用的序列化器
serializer_class = TestSerializer
# 指定当前类视图使用的查询集数据
queryset = ContactUs.objects.all()
# 更新数据
def put(self, request, pk):
return self.update(request, pk)
def delete(self, request, pk):
return self.destroy(request, pk)
url文件是
urlpatterns = [
url(r'^test/', Test.as_view()),
url(r'^test_update/(?P\d+)' , Test_Update.as_view()),
]
serializer:
from rest_framework.exceptions import ValidationError
from engineer.models import *
from One.models import *
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = ContactUs
fields = "__all__"
def validate(self, attrs):
if attrs["name"] == '44526':
raise ValidationError(detail={'code': 998, "message": "您输入的name值不对"})
return attrs
这里暂时没有实验成功 显示pk值接收不到。。后来发现是postman原因 重启后解决 绝了
postman测试ip
自己修改后的代码:无论是执行更改,删除等操作成功或者失败都要返回东西 否则报错 是框架源码规定
# 拓展类 此时函数的业务逻辑交给拓展类来执行
class Test_Update(GenericAPIView, UpdateModelMixin, DestroyModelMixin):
authentication_classes = []
permission_classes = []
# 指定当前类视图使用的序列化器
serializer_class = TestSerializer
# 指定当前类视图使用的查询集数据
queryset = ContactUs.objects.all()
# 更新数据
def put(self, request, pk):
try:
self.update(request, pk)
return Response("更新数据成功")
except:
return Response("数据不存在")
# return self.update(request, pk)
def delete(self, request, pk):
try:
self.destroy(request, pk)
return Response("数据已删除")
except:
return Response("数据未找到")
例如ListCreateAPIView继承的是GenericAPIView ListAPIview CreateAPIView
所以上文中的Test类可以改写为
查询所有数据 增加新数据
# 拓展类 此时函数的业务逻辑交给拓展类来执行
class Test(ListCreateAPIView):
authentication_classes = []
permission_classes = []
# 指定当前类视图使用的序列化器
serializer_class = TestSerializer
# 指定当前类视图使用的查询集数据
queryset = ContactUs.objects.all()
另一个拓展类
查询单一数据 更新数据 删除数据
# 拓展类 此时函数的业务逻辑交给拓展类来执行
class Test(RetrieveUpdateDestroyAPIView):
authentication_classes = []
permission_classes = []
# 指定当前类视图使用的序列化器
serializer_class = TestSerializer
# 指定当前类视图使用的查询集数据
queryset = ContactUs.objects.all()
和ApiVIew的区别:自已定义方法名,post get 等 ,那也得对应系统已经定义的list,create等方法,如需对应别的下文有方法展示
这时候相应的url也要发生改变
因为Viewset继承于ApiView
所以写法以及用法和ApiView相似 不需要指定查询集数据和序列化器
例子:
这五个方法 可以用上文中的方法来匹配
如果是自己定义的别的方法名,匹配方法如下:
和GenericApiView相同需要指定 查询集数据和序列化器
例子:
modelviewset 继承于Genericapiview
modelset中已经实现了增删改查功能
自动生成路由前提:必须使用视图集
在url中 先导入包SimpleRouter
from rest_framework.routers import SimpleRouter
register 的三个参数分别是:url名,view文件名.类名,自己指定的命名
此时启动项目会看到:
如果定义了超过规定的五个方法名之外 自动生成路由要借助action方法
methods规定请求方法 detail=True生成pk值,detail=False 不生成pk值
根据实际需求来选择:
例如单一需求如注册等,就使用拓展类,需要增删改查就使用视图集,节省空间、资源,且涉及到redis也不能使用视图集,因为存储是在mysql中,和redis无关
Default Router可以对首页进行匹配 simpleRouter可以
Default Router继承自SimpleRouter
self.action就是view方法名
使用:可以使用get_serializer()方法 指定多个序列化器使用(概率较小)