1、关于REST
1.1、什么是REST
Resource:资源,即数据(前面说过网络的核心)。比如 books;
Representational:某种表现形式,比如用JSON,XML,JPEG等;
State Transfer:状态变化。通过HTTP动词GET、PUT、POST等来实现。
以前的网页都是前后端融合到一体的。但近年来随着移动互联网的发展,各种类型的客户端层出不穷,导致需要很多种接口才能满足需求。这样不利于开发工作。故前后端分离模式应运而生。
而RESTful 风格的API提供了一套统一的接口来满足Web,IOS,Android的需求,非常适合于前后端分离的项目。
1.2、RESTful规范
RESTful风格的API的好处:
看Url就知道要什么;
看http method就知道干什么;
看http status code就知道结果如何。
1.3、django原生接口
下面使用django原生形式实现前后端分离的RESTful架构的API。
正常情况下,如果使用django去写一个图书管理的后端,需要很多接口。包括查询所有图书的接口,查询指定图书的接口,新增图书接口,修改图书的接口,删除图书的接口。这里用CBV类视图来实现。
根据路由后面有没有带pk/id可将这些接口分为两类,即不带pk的列表视图和带pk的详情视图。
列表视图内有get查询所有图书的接口和post新增单一图书的接口。如下:
1 class BookListView(View): 2 """列表视图""" 3 4 def get(self,request): 5 """查询所有图书接口""" 6 # 1、查询所有图书模型 7 books = Book.objects.all() 8 # 2、遍历查询集,取出里面的每个书籍模型对象,转化为字典 9 book_list = [] 10 for book in books: 11 book_dict = { 12 \'nid\':book.nid, 13 \'name\':book.name, 14 \'price\':book.price, 15 \'author\':book.author 16 } 17 book_list.append(book_dict) 18 # 3、响应 19 return JsonResponse(book_list,json_dumps_params={\'ensure_ascii\':False},safe=False) # 将字典序列化为json 20 # JsonResponse类的data参数默认为字典,safe参数默认为true,如果不是字典则应该设置safe=False 21 # 注意:json_dumps_params参数默认为None,此时如果响应的字典中包含中文,则在客户端显示的是Unicode字符集的16进制码位,如\u8c; 22 # 如果想要显示中文,则应将ensure_ascii的默认参数True改为False。 23 24 def post(self,request): 25 """新增图书接口""" 26 # 要先在settings.py中关闭CSRF验证 27 # 1、获取json格式的请求数据的字节流,由utf-8编码的16进制表示 如\x6c 28 json_bytes = request.body 29 # print(request.body) 30 # for k,v in request.environ.items(): # 打印请求头信息 31 # print(k,v) 32 # 2、将字节流转化为json格式的字符串 33 json_str = json_bytes.decode() 34 # 3、将字符串转化为json格式的字典 35 json_dict = json.loads(json_str) 36 # 4、新增模型数据 37 book = Book( 38 name=json_dict[\'name\'], 39 price=json_dict[\'price\'], 40 author=json_dict[\'author\'] 41 ) 42 # 5、保存模型 43 book.save() 44 # 6、响应字典 45 jd = { 46 \'nid\':book.nid, 47 \'name\':book.name, 48 \'price\':book.price, 49 \'author\':book.author 50 } 51 return JsonResponse(jd,json_dumps_params={\'ensure_ascii\':False},status=201)
详情视图
1 class BookDetailView(View): 2 """详情视图""" 3 def get(self,request,pk): 4 """查询指定的图书接口""" 5 # 1、获取指定pk的模型对象; 6 try: 7 book = Book.objects.get(nid=pk) # id超出范围的异常处理 8 except Book.DoesNotExist: 9 print(\'查询数据不存在\') 10 return HttpResponse("查询数据不存在", status=404) 11 12 # 2、将模型对象转换为字典; 13 book_dict = { 14 \'nid\':book.nid, 15 \'name\':book.name, 16 \'price\':book.price, 17 \'author\':book.author 18 } 19 # 3、响应字典给前端 20 return JsonResponse(book_dict,json_dumps_params={"ensure_ascii":False}) 21 22 def put(self,request,pk): 23 """修改指定的图书接口""" 24 # 1、获取指定pk的模型对象 25 try: 26 book = Book.objects.get(nid=pk) # id超出范围的异常处理 27 except Book.DoesNotExist: 28 return HttpResponse("要修改的数据不存在", status=404) 29 # 2、获取并将请求的数据转换为json字典 30 book_dict = json.loads(request.body.decode()) 31 # 3 、修改模型对象并保存 32 book.name = book_dict[\'name\'] 33 book.price = book_dict[\'price\'] 34 book.author = book_dict[\'author\'] 35 book.save() 36 # 4、将修改后的模型对象转换为字典 37 book_modified_dict = { 38 \'nid\': book.nid, 39 \'name\': book.name, 40 \'price\': book.price, 41 \'author\': book.author 42 } 43 # 5、响应字典 44 return JsonResponse(book_modified_dict,json_dumps_params={\'ensure_ascii\':False}) 45 46 def delete(self,request,pk): 47 try: 48 book = Book.objects.get(nid=pk) 49 except Book.DoesNotExist: 50 return HttpResponse(\'要删除的数据不存在\',status=404) 51 book.delete() 52 return HttpResponse(status=204) # 请求执行成功,但没有数据返回
呵呵
2、DRF实现接口
2.1、DRF初登场
用drf来实现上面的图书接口,最简单版本只需要10行代码:
1、在urls.py中注册路由:
2、新建ser.py,添加序列化器:
3、在views.py中写入类视图:
浏览器访问:
为何如此简洁的代码就能实现接口功能呢?这就因为dfr内部对数据之间的转换进行了一系列的封装,这就涉及到序列化和反序列化。
序列化和反序列化:
当需要给前端响应模型数据时,需要将模型数据序列化成前端需要的格式。
当需要将用户发送的数据存储到数据库时,就要将数据如字典、json、xml等反序列化成Django中的数据库模型类对象再保存。
这就是在开发RESTful API时,在视图中要做的最核心的事情。
序列化和反序列化都是通过序列化器这个类来实现的。详情见下面的实例部分的serializer.py。
2.2、DRF实例-手机列表
2.2.1、模型models.py
from django.db import models import django # Create your models here. # demo of DRF class CellPhone(models.Model): id = models.AutoField(primary_key=True,verbose_name=\'手机ID\') name = models.CharField(max_length=32,verbose_name=\'手机名\') price = models.DecimalField(max_digits=7,decimal_places=2,verbose_name=\'手机价格\') company = models.CharField(max_length=32,verbose_name=\'生产公司\') class Meta: db_table = \'cellphone\' def __str__(self): return self.name
2.2.2、视图view.py
from django.shortcuts import render # Create your views here. from django.http import JsonResponse, HttpResponse, HttpRequest, Http404 from .models import CellPhone from django.views import View import json # import django # import os # os.environ.setdefault("DJANGO_SETTINGS_MODULE","django01.settings") # django.setup() \'\'\' # 使用django的原生框架来写rest风格的API接口 class CellphoneListView(View): """列表视图,不用传参""" def get(self,request): """获取全部接口""" # 1、获取查询集 cps = CellPhone.objects.all() # 2、将查询集中的每个模型对象转字典嵌套到列表中 cps_list = [] for cp in cps: cp_dict = { \'id\':cp.id, \'name\':cp.name, \'price\':cp.price, \'company\':cp.company } cps_list.append(cp_dict) return JsonResponse(cps_list,safe=False) def post(self,request): """新增一个接口""" # 1、将请求中的字节流转字符串转json字典 cp_dict = json.loads(request.body.decode()) # 2、新增模型 cp_model = CellPhone.objects.create(**cp_dict) # 3、将模型转为响应的字典 cp_dict_return = { \'id\':cp_model.id, \'name\':cp_model.name, \'price\':cp_model.price, # 传入价格3999,则返回的也是3999,不是3999.00 \'company\':cp_model.company } return JsonResponse(cp_dict_return) class CellphoneDetailView(View): """详情视图,需要传参""" def get(self,request,pk): """获取一个指定接口""" # 1、获取指定pk的模型对象 # 对输入的id做一个简单的验证 print(type(pk)) print(pk) try: cp_model = CellPhone.objects.get(id=pk) print(cp_model) except CellPhone.DoesNotExist: return HttpResponse("查询数据不存在",status=404) # 2、将模型对象转字典 cp_dict_return = { \'id\': cp_model.id, \'name\': cp_model.name, \'price\': cp_model.price, # 传入价格3999,则返回的也是3999,不是3999.00 \'company\': cp_model.company } # 3、响应字典给前端 return JsonResponse(cp_dict_return) def put(self,request,pk): """修改一个指定的接口""" # 1、获取模型对象 try: cp_model = CellPhone.objects.get(id=pk) except CellPhone.DoesNotExist: return HttpResponse("要修改的数据不存在",status=404) # 2、请求数据转字典 cp_dict = json.loads(request.body.decode()) # 3、字典转模型保存到数据库 cp_model.name = cp_dict[\'name\'] cp_model.price = cp_dict[\'price\'] cp_model.company = cp_dict[\'company\'] cp_model.save() # 4、将修改后的模型转字典响应给前端 cp_dict_return = { \'id\': cp_model.id, \'name\': cp_model.name, \'price\': cp_model.price, \'company\': cp_model.company } return JsonResponse(cp_dict_return) def delete(self,request,pk): """删除指定接口""" try: cp_model = CellPhone.objects.get(id=pk) except CellPhone.DoesNotExist: return HttpResponse("要删除的数据不存在",status=404) cp_model.delete() return HttpResponse("删除成功",status=204) \'\'\' # --------------------------------------------------------------------------------------- # 使用DRF来写rest风格的API接口 from .serializer import CellPhoneSerializer class CellphoneListView(View): """列表视图,不用传参""" def get(self,request): """获取全部接口""" # 1、获取查询集 cps = CellPhone.objects.all() print(cps) # <QuerySet [<CellPhone: P30pro>, <CellPhone: mate30 pro>, <CellPhone: 小米11>]> # 2、直接对查询集进行序列化,并且应当将many置为True,因为查询集合包含多条数据 ser = CellPhoneSerializer(instance=cps, many=True) print(ser) print(ser.data) # 由多个有序字典组成的列表 return JsonResponse(ser.data,safe=False) # 此处返回的是列表嵌套字典,而不是字典,应置safe为False # return JsonResponse({\'name\':\'wangyi\',\'age\':10}) # 此处返回列表嵌套字典,应置safe为False def post(self,request): """新增一个接口""" \'\'\' # 使用序列化器后的写法 # 1、获取新增的json数据并将其转换为字典 cp_new_data = json.loads(request.body.decode()) # 2、保存到数据库模型 # create方法能创建新的模型对象并保存到数据库,然后返回新增的模型对对象 cp = CellPhone.objects.create(**cp_new_data) # 3、对返回的模型对象进行序列化输出 ser = CellPhoneSerializer(cp) return JsonResponse(ser.data) \'\'\' # nmb ser = CellPhoneSerializer(data=json.loads(request.body.decode())) # 校验数据 # 如果序列化器中id字段有read_only=True,此处新增数据就无需传id if ser.is_valid(): # ser_model = CellPhone.objects.create(**ser.validated_data) # 校验之后才有validated_data字典 # ser = CellPhoneSerializer(ser_model) # 对新增的模型序列化 # 对以上两步优化:数据库操作放在序列化器中进行,在这直接调用序列化器的save方法 ser.save() # obj = ser.save() # 上面ser.save()执行后返回的是新增的模型类对象obj,可对obj进行序列化输出,或者直接对一开始的反序列化对象ser进行响应ser.data return JsonResponse(ser.data) else: return JsonResponse(ser.errors) # 校验失败后抛出错误信息 # 下面是APIView中的高级写法,需要在序列化器里重写create方法 # # 1、直接对请求数据进行反序列化,给data传参,注意要指明data,因为默认第一个参数是instance # ser = CellPhoneSerializer(data=request.data) # 无实例instance # print(ser) # print(request.body) # 字节流类型 # # 2、校验数据 # if ser.is_valid(): # ser.save() # 校验通过,调用序列化器里面的save()方法保存到数据库 # return JsonResponse(ser.data) # 返回新增的数据,json格式 # else: # return JsonResponse(ser.errors) # 校验未通过则返回错误信息 class CellphoneDetailView(View): """详情视图,需要传参""" # 对pk值校验,抽离出方法 def get_object(self,pk): try: return CellPhone.objects.get(id=pk) except CellPhone.DoesNotExist: raise Http404 # return HttpResponse("要修改的数据不存在", status=404) def get(self, request, pk): """获取一个指定接口""" # # 1、获取指定pk的模型对象 # # 对输入的id做一个简单的验证 # print(type(pk)) # print(pk) # try: # cp_model = CellPhone.objects.get(id=pk) # print(cp_model) # except CellPhone.DoesNotExist: # return HttpResponse("查询数据不存在",status=404) # # 2、将模型对象转字典 # cp_dict_return = { # \'id\': cp_model.id, # \'name\': cp_model.name, # \'price\': cp_model.price, # 传入价格3999,则返回的也是3999,不是3999.00 # \'company\': cp_model.company # } # # 3、响应字典给前端 # return JsonResponse(cp_dict_return) cp = self.get_object(pk) ser = CellPhoneSerializer(instance=cp) # 也可省略instance= return JsonResponse(ser.data) def put(self,request,pk): """修改一个指定的接口""" # 1、获取模型对象 cp_model = self.get_object(pk) # 2、请求数据转字典 cp_dict = json.loads(request.body.decode()) # 3、通过反序列化实行校验 ser_r = CellPhoneSerializer(instance=cp_model,data=cp_dict) try: ser_r.is_valid(raise_exception=True) except Exception : # print(ser_r.errors) # print(ser_r.errors[\'name\']) return JsonResponse(ser_r.errors) # 上面修改手机信息的时候不能修改手机name,否则因为唯一性校验失败 # 怎么在修改的时候能够修改手机名呢?? # 4、字典转模型保存到数据库 # cp_model.name = ser_r.validated_data[\'name\'] # cp_model.price = ser_r.validated_data[\'price\'] # cp_model.company = ser_r.validated_data[\'company\'] # cp_model.save() # 通过给序列化对象同时传参instance和data后,就可以直接调用序列化对象的save()方法保存到数据库 # 需要在我们定义的序列化器中重写update()方法供save去调用 ser_r.save() # 5、将修改后的模型转字典响应给前端 # cp_dict_return = { # \'id\': cp_model.id, # \'name\': cp_model.name, # \'price\': cp_model.price, # \'company\': cp_model.company # } # 5、序列化输出 # ser = CellPhoneSerializer(cp_model) return JsonResponse(ser_r.data) def delete(self,request,pk): """删除指定接口""" cp_model = self.get_object(pk) cp_model.delete() return HttpResponse("删除成功",status=204)
2.2.3、序列化器serializer.py
from rest_framework import serializers from rest_framework.validators import UniqueValidator from rest_framework.exceptions import ValidationError from app02_serializer.models import CellPhone # 自定义校验,放在validators列表里使用 def made_in_china(company): if company in ["三星","苹果"]: raise ValidationError("只支持中国手机厂商") class CellPhoneSerializer(serializers.Serializer): id = serializers.IntegerField(label=\'ID\',read_only=True) name = serializers.CharField(max_length=32,label=\'手机名\', validators=[UniqueValidator(CellPhone.objects.all(),message="手机名称不能重复")]) price = serializers.DecimalField(max_digits=7,decimal_places=2,label=\'手机价格\') company = serializers.CharField(max_length=32,label=\'生产公司\',validators=[made_in_china]) # 定义的属性与模型类中的字段名要一一对应。少了的话会影响序列化输出和反序列化输入的内容。 # label即为模型字段中的verbose_name # read_only=True表示序列化输出的时候有该字段,反序列化输入的时候不用传,write_only=True则刚好相反 # 如果id的read_only=True,则修改的时候无法指定nid字段,自动新增,无法修改旧的 # 可通过参数validators以列表的形式添加多个验证器 # 也可以通过在序列化器内部以 validate_字段名 的形式来添加校验,这个校验顺序排在最后 def validate_price(self,value): """局部钩子函数,单字段校验""" if float(value) <= 1000: raise ValidationError("手机价格过低") else: return value # 注意这里一定要返回 def validate(self, attrs): """全局钩子函数,多字段校验""" if attrs["price"] >= 10000 and attrs["company"] is "小米": raise ValidationError("该款小米手机价格太高了") else: return attrs def update(self, instance, validated_data): """重写update方法,instance是数据库模型对象""" instance.name = validated_data.get("name") instance.price = validated_data.get("price") instance.company = validated_data.get("company") instance.save() # 这是django的ORM提供的save return instance # 将数据库的新增放在序列化器中实现,需要重写serializers.py中的BaseSerializer类的create() # CellPhoneSerializer继承自Serializer,而Serializer继承自BaseSerializer,所以这里才能实现重写 def create(self, validated_data): """新增时需要重写create""" # 下面的create()调用的是query.py里的为ORM所提供的create接口 return CellPhone.objects.create(**validated_data)
2、继承自ModelSerializer:
# 使用ModelSerializer进一步优化 # 1、ModerSerializer能根据模型自动生成序列化器中的字段; # 2、ModelSerializer能自动重写create和update方法。 # 3、对于某些特殊需求,如前端传进来的数据中有一个是我不想要新增的,这时就要自己重写create方法,在validated_data中删除这个数据,实现个性化处理数据来满足业务需求。 # 4、若果定义的序列化器没有数据库模型与之对应,就用Serializer。 # 5、建议前期都用Serializer class CellPhoneModelSerializer(serializers.ModelSerializer): # 覆盖重写name字段 # trim_whitespace=True 去除项目名前后的空格 name = serializers.CharField(max_length=32, label=\'手机名\', trim_whitespace=True, validators=[UniqueValidator(CellPhone.objects.all(), message="手机名称不能重复")], error_messages={\'max_length\':\'手机名长度不能超过32个字节哦!\'}) class Meta: model = CellPhone fields = "__all__" # 指定序列化的字段域,all表示所有 # fields = [\'id\', \'name\'] # 指定序列化的字段域 # exclude = [\'name\', \'price\'] # 指定不参加序列化的字段 extra_kwargs = { \'price\':{\'default\':\'2999\',\'write_only\':True}, \'company\':{\'error_messages\':\'公司名长度不超过32字节哦!\'}} # 指定额外参数 # 个性化校验部分跟上面一样 def validate_price(self, value): """局部钩子函数,单字段校验""" if float(value) <= 1000: raise ValidationError("手机价格过低") else: return value # 注意这里一定要返回 def validate(self, attrs): """全局钩子函数,多字段校验""" if attrs["price"] >= 10000 and attrs["company"] is "小米": raise ValidationError("该款小米手机价格太高了") else: return attrs
2.2.4、 Request和Response
Request
REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了HttpRequest类的Request类的对象。
REST framework 提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典对象保存到Request对象中。
Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。
无论前端发送的哪种格式的数据,都可以以统一的方式读取数据。
常用属性
1、data
request.data 返回解析之后的请求体数据。类似于Django中标准的request.POST和 request.FILES属性,但提供如下特性:
-
包含了解析之后的文件和非文件数据;
-
包含了对POST、PUT、PATCH请求方式解析后的数据;
-
利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据;
2、query_params
request.query_params与Django标准的request.GET相同,只是更换了更正确的名称而已。
Response
REST framework提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染)成符合前端需求的类型。
REST framework提供了Renderer 渲染器,用来根据请求头中的Accept(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,也可以通过配置来修改默认响应格式。
默认方式:rest_framework/settings.py
可以看出drf默认渲染器类里有两个渲染器:一个json渲染器,一个是可浏览的API渲染器。
当用postman去访问接口时,会调用json渲染器响应json格式的数据,而当我们用浏览器去访问接口时会返回可浏览的API渲染器。
如果需要指定渲染器,可以在自己项目的settings.py中去重写这段代码。格式人家都交代好了:
如果只想返回json,只需注释掉可浏览API渲染器:
则浏览器访问:
之前是这样的:
也可以在视图类中通过添加render_class属性指定渲染器:
from rest_framework.renderers import JSONRenderer
renderer_classes = [JSONRenderer, ]
构造方式:
Response(data, status=None, template_name=None, headers=None, content_type=None)
data数据不是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer渲染器处理data。
data不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer序列化器序列化处理后(转为了Python字典类型)再传递给data参数。
参数说明:
- data: 为响应准备的序列化处理后的数据;
- status: 状态码,默认200;
- template_name: 模板名称,如果使用HTMLRenderer 时需指明;
- headers: 用于存放响应头信息的字典;
- content_type: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。
2.2.5、APIView
APIView是 framework提供的所有视图的基类,继承自Django的View父类。
APIView与View的不同之处在于:
-
传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;
-
视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;
-
任何APIException异常都会被捕获到,并且处理成合适的响应信息;
-
在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
支持定义的属性:
- authentication_classes 列表或元组,身份认证类
- permissoin_classes 列表或元组,权限检查类
- throttle_classes 列表或元组,流量控制类
继承APIView后的视图:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .models import CellPhone from .serializer import CellPhoneModelSerializer class CellPhoneListAPIView(APIView): """列表视图""" def get(self,request): """查询所有""" cp = CellPhone.objects.all() ser = CellPhoneModelSerializer(instance=cp, many=True) return Response(ser.data) def post(self, request): """新增""" data = request.data ser = CellPhoneModelSerializer(data=data) ser.is_valid(raise_exception=True) ser.save() return Response(ser.data,status=status.HTTP_201_CREATED) class CellPhoneDetailAPIView(APIView): """详情查询""" def get(self,request,pk): """单一查询""" try: cp = CellPhone.objects.get(id=pk) except CellPhone.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) ser = CellPhoneModelSerializer(instance=cp) return Response(data=ser.data) def put(self,request,pk): """修改单一""" try: cp = CellPhone.objects.get(id=pk) except CellPhone.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) ser = CellPhoneModelSerializer(instance=cp, data=request.data) ser.is_valid(raise_exception=True) ser.save() return Response(data=ser.data) def delete(self,request,pk): """删除单一""" try: cp = CellPhone.objects.get(id=pk) except CellPhone.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) cp.delete() return Response(status=status.HTTP_204_NO_CONTENT)
APIView中的代码还是不够简练,没有将模型和序列化器抽离出来,假如需要更改这两者,则要改很多地方。并且当响应数据体量过大时,也没有分页展示功能等,而APIView的子类GenericAPIView就加入了很多功能。
2.2.6、GenericAPIView
继承自APIView,主要增加了操作序列化器和数据库查询的方法,为Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。
这个类使用时必须要定义两个属性:queryset数据查询集和serializer_class序列化器
views.py :
from rest_framework.response import Response from rest_framework import status from .models import CellPhone from .serializer import CellPhoneModelSerializer from rest_framework.generics import GenericAPIView # GenericAPIView class CellPhoneListGeneric(GenericAPIView): """列表视图""" # 指定查询集 queryset = CellPhone.objects.all() # 指定序列化器类 serializer_class = CellPhoneModelSerializer def get(self,request): """查询所有""" # 获取查询集 instance = self.get_queryset() # 通过父类方法返回实例属性queryset # 获取序列化器对象 ser = self.get_serializer(instance, many=True) # 通过调用封装后的方法来返回序列化器对象,主要给mixin扩展类使用 return Response(ser.data) def post(self,request): """新增""" ser = self.get_serializer(data=request.data) ser.is_valid(raise_exception=True) ser.save() return Response(ser.data, status=status.HTTP_201_CREATED) class CellPhoneDetailGeneric(GenericAPIView): """详情视图""" queryset = CellPhone.objects.all() serializer_class = CellPhoneModelSerializer def get(self,request,pk): """单一查询""" # 获取模型对象 instance = self.get_object() # 无需传pk,内部已封装,拿不到自动抛404 ser = self.get_serializer(instance) return Response(ser.data) def put(self,request,pk): """修改单一""" instance = self.get_object() ser = self.get_serializer(instance,request.data) return Response(ser.data) def delete(self,request,pk): """删除单一""" instance = self.get_object() instance.delete() return Response(status=status.HTTP_204_NO_CONTENT)
注意:
1、get_object()会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问。
2、get_serializer()方法会向序列化器对象的context属性以字典的形式添加三个数据对象request、format和view。这三个数据对象可以在定义序列化器时使用。
request:当前视图的请求对象
view:当前请求的类视图对象
format:当前请求期望返回的数据格式
2.2.7、mixins扩展类
虽说到目前为止,这五个接口的代码已经很简洁了,但还是存在重复性的代码,还可以更加简洁,利用drf提供的mixins扩展类,一行代码就可以搞定。
mixins有五个扩展类:
1、查询所有:ListModeMixin
2、新增单一:CreateModelMixin
3、查询单一:RetrieveModelMixin
4、修改单一:UpdateModelMixin
5、删除单一: DestroyModelMixin
我们要做的就是继承这些扩展类并调用类里面的方法即可。
from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin class CellPhoneListGeneric(ListModelMixin,CreateModelMixin,GenericAPIView): """列表视图""" queryset = CellPhone.objects.all() serializer_class = CellPhoneModelSerializer def get(self,request): """查询所有""" return self.list(request) def post(self,request): """新增""" return self.create(request) class CellPhoneDetailGeneric(RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin,GenericAPIView): """详情视图""" queryset = CellPhone.objects.all() serializer_class = CellPhoneModelSerializer def get(self,request,pk): """查询单一""" return self.retrieve(request,pk) def put(self,request,pk): """修改单一""" return self.update(request,pk) def delete(self,request,pk): """删除单一""" return self.destroy(request,pk)
从mixins.py 里的五大类的具体代码可以看出,他帮我们之前做的工作都做了一遍,还更加详细:
1 """ 2 Basic building blocks for generic class based views. 3 4 We don\'t bind behaviour to http method handlers yet, 5 which allows mixin classes to be composed in interesting ways. 6 """ 7 from rest_framework import status 8 from rest_framework.response import Response 9 from rest_framework.settings import api_settings 10 11 12 class CreateModelMixin: 13 """ 14 Create a model instance. 15 """ 16 def create(self, request, *args, **kwargs): 17 serializer = self.get_serializer(data=request.data) 18 serializer.is_valid(raise_exception=True) 19 self.perform_create(serializer) 20 headers = self.get_success_headers(serializer.data) 21 return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) 22 23 def perform_create(self, serializer): 24 serializer.save() 25 26 def get_success_headers(self, data): 27 try: 28 return {\'Location\': str(data[api_settings.URL_FIELD_NAME])} 29 except (TypeError, KeyError): 30 return {} 31 32 33 class ListModelMixin: 34 """ 35 List a queryset. 36 """ 37 def list(self, request, *args, **kwargs): 38 queryset = self.filter_queryset(self.get_queryset()) 39 40 page = self.paginate_queryset(queryset) 41 if page is not None: 42 serializer = self.get_serializer(page, many=True) 43 return self.get_paginated_response(serializer.data) 44 45 serializer = self.get_serializer(queryset, many=True) 46 return Response(serializer.data) 47 48 49 class RetrieveModelMixin: 50 """ 51 Retrieve a model instance. 52 """ 53 def retrieve(self, request, *args, **kwargs): 54 instance = self.get_object() 55 serializer = self.get_serializer(instance) 56 return Response(serializer.data) 57 58 59 class UpdateModelMixin: 60 """ 61 Update a model instance. 62 """ 63 def update(self, request, *args, **kwargs): 64 partial = kwargs.pop(\'partial\', False) 65 instance = self.get_object() 66 serializer = self.get_serializer(instance, data=request.data, partial=partial) 67 serializer.is_valid(raise_exception=True) 68 self.perform_update(serializer) 69 70 if getattr(instance, \'_prefetched_objects_cache\', None): 71 # If \'prefetch_related\' has been applied to a queryset, we need to 72 # forcibly invalidate the prefetch cache on the instance. 73 instance._prefetched_objects_cache = {} 74 75 return Response(serializer.data) 76 77 def perform_update(self, serializer): 78 serializer.save() 79 80 def partial_update(self, request, *args, **kwargs): 81 kwargs[\'partial\'] = True 82 return self.update(request, *args, **kwargs) 83 84 85 class DestroyModelMixin: 86 """ 87 Destroy a model instance. 88 """ 89 def destroy(self, request, *args, **kwargs): 90 instance = self.get_object() 91 self.perform_destroy(instance) 92 return Response(status=status.HTTP_204_NO_CONTENT) 93 94 def perform_destroy(self, instance): 95 instance.delete()
大大解放了双手,可歌可泣。
使用mixins扩展后还是不够简洁,各种方法还得自己写,其实还可以进一步简化,把定义方法的过程也交给其他扩展类如ListAPIView。
from rest_framework.generics import GenericAPIView,ListAPIView,CreateAPIView,ListCreateAPIView
from rest_framework.generics import RetrieveAPIView,UpdateAPIView,DestroyAPIView,RetrieveUpdateDestroyAPIView
这些扩展类继承自GenericAPIView和mixins扩展类:
里面帮我们实现了相应的方法:
继承ListAPIView就不用再写 查询所有 的方法,继承CreateAPIView就不用写 新增 的方法,而ListCreateAPIView又继承自前面两者,所以所有方法都不用写:
只用六行代码即可搞定列表视图和详情视图里面的五个操作:查询所有、新增、查询单一、修改、删除!
2.3 视图集
2.3.1 Viewset
从上面的视图代码可以看出,两个视图类中有重复的代码,而且这两个类不能合并,因为里面都有get()方法。但万能的DRF为我们解决了这个问题。
通过视图集ViewSet可以实现列表查询和单一查询共存在一个视图类里面。
from rest_framework.viewsets import ViewSet
class CellPhoneViewSet(ViewSet): def list(self,request): """查询所有""" instance = CellPhone.objects.all() ser = CellPhoneModelSerializer(instance,many=True) return Response(ser.data) def retrieve(self,request,pk): """查询单一""" try: instance = CellPhone.objects.get(id=pk) except CellPhone.DoesNotExist: return Response(status.HTTP_404_NOT_FOUND) ser = CellPhoneModelSerializer(instance) return Response(ser.data)
使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中:
- list() 提供一组数据
- retrieve() 提供单个数据
- create() 创建数据
- update() 保存数据
- destory() 删除数据
ViewSet视图集类不再实现get()、post()等方法,而是实现动作 action 如 list() 、create() 等。
视图集只在使用as_view()方法的时候,才会将action动作与具体请求方式对应上。
ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典(如{\'get\':\'list\'})的映射处理工作。在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。
设置路由:
2.3.2 GenericViewSet
使用ViewSet通常并不方便,因为list、retrieve、create、update、destory等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView,所以还需要继承GenericAPIView。
GenericViewSet就帮助我们完成了这样的继承工作,继承自GenericAPIView与ViewSetMixin,在实现了调用as_view()时传入字典(如{\'get\':\'list\'})的映射处理工作的同时,还提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用。
from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin
如只实现两个查询方法就要继承三个类:
class CellPhoneViewSet(ListModelMixin,RetrieveModelMixin,GenericViewSet):
queryset = CellPhone.objects.all()
serializer_class = CellPhoneModelSerializer
也是比较麻烦。如果有个类xxx继承自上面这些类,而我们只要继承自这个xxx类就好了。
2.3.3 ModelViewSet
ModelViewSet的存在就解决了以上痛点:
使用后的视图代码:只要三行
路由部分:
这样就回到了最初2.1中十行代码搞定最基本的DRF接口了。
权限
权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。
在执行视图的dispatch()方法前,会先进行视图访问权限的判断。
在通过get_object()获取具体对象时,会进行对象访问权限的判断。
有时我们希望只有特定用户才能访问视图,如登录后的用户,怎么实现?
在项目主urls.py添加路由api,自动添加登陆退出功能
然后在设置settings.py里添加权限设置,默认为AllowAny,改为认证用户,这样就只有登录用户才能访问视图获得数据了。
登录的用户名和密码可以通过命令行: python manage.py createsuperuser 来设置。
上面是全局设置,也可以通过另一种方法,在视图中添加permission_classes属性来进行权限设置:
其中第二个MyPermission权限是我个人自定义的,如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部:
1、has_permission(self, request, view)
是否可以访问视图, view表示当前视图对象,如果没有设置的话默认的是True,如果设置False则表示所有的用户都不能访问该视图。
2、has_object_permission(self, request, view, obj)
是否可以访问数据对象, view表示当前视图, obj为数据对象,控制视图能够访问添加了权限控制类的数据对象。
上面的权限表示用户没有权限访问id为1~5的数据对象。
3、部分源码分析
3.1、 as_view()
1 def dispatch(self, request, *args, **kwargs):
2 """
3 `.dispatch()` is pretty much the same as Django\'s regular dispatch,
4 but with extra hooks for startup, finalize, and exception handling.
5 """
6 self.args = args
7 self.kwargs = kwargs
8 request = self.initialize_request(request, *args, **kwargs)
9 self.request = request
10 self.headers = self.default_response_headers # deprecate?
11
12 try:
13 self.initial(request, *args, **kwargs)
14
15 # Get the appropriate handler method
16 if request.method.lower() in self.http_method_names:
17 handler = getattr(self, request.method.lower(),
18 self.http_method_not_allowed)
19 else:
20 handler = self.http_method_not_allowed
21
22 response = handler(request, *args, **kwargs)
23
24 except Exception as exc:
25 response = self.handle_exception(exc)
26
27 self.response = self.finalize_response(request, response, *args, **kwargs)
28 return self.response
request = self.initialize_request(request, *args, **kwargs) 这句代码对原来的request进行了初始化封装,加入了很多东西如请求解析内容。使得现在的request不再是HttpRequest的对象,而是rest_framework/request.py模块里的Request类的对象。
在Request类中,原本请求的request对象变成了类的一个受保护属性_request(不能通过from xx import xx的方式导入)。
补充:类中的私有属性__y是不能被类的对象和子类直接引用的,因为在python中,是通过改名的方式实现属性的私有的,python会在内部会将类A的私有属性__y改名为_A__y。所以使用对象a.__y是
找不到该属性的,而使用a._A__y是可以操作__y属性的。 这种语言特性叫做名称改写。
此时如果在类视图中print(request.method)会有什么结果?答案是能够正常获取到了请求的方法。为什么封装后request也能获得请求的方法?因为类Request对__getattr__方法进行了重写,当我们获取请求方法找不到时就会调用这个内建方法,而此时又重写了,在重写后的__getattr__里,又通过反射来获取到了self._request的属性,也就是原生request的method属性。
参考:https://www.cnblogs.com/wangyi0419/p/12592492.html
补充反射:
问题:这里的except中的 return self.__getattribute__(attr)我没看懂什么意思,self不是不能直接调用__getattrbute__()吗?
data看起来像个属性,但其实是一个被@property装饰器装饰过的方法。他能返回多种格式的请求数据。
补充一下装饰器@property的作用:
第一:@property是为了让人方便的像调用属性一样去调用方法,不用带();
第二:与所定义的属性配合使用,这样可以防止属性被修改。让外部直接调用给定的方法名,而不知道该方法返回的真正的属性名。从而达到了隐藏属性名的作用,让用户进行使用的时候不能随便更改。
python默认的成员函数和成员变量都是公开的。可以通过加双下划线将属性变为私有,不过私有不绝对,也是可以通过_类名__属性名的方式访问和修改的。
3.2、 关于save()
在一个简单点注册接口中,只有post()方法需要实现,视图继承自CreateAPIView。
CreateAPIView实现post(),返回中调用CreateModelMixin中的create()。
调用perform_create(),调用序列化器中的save()
然而我的序列化器类没有save(),在祖宗类BaseSerializer中有save(),这里的save()返回的实例,内部调用create()
create()是抽象类,需要重写。
我在注册序列化器中重写了create(),调用了create_user():
在规范化邮箱用户名给密码加密后又调用了save()类:
在AbstractBaseUser类中的save()又调用了父类的save():
父类是django原生的Model: