DRF
DRF(Django REST framework)框架是建立在Django框架基础之上,本质上它就是Django的一个App,通过DRF能够快速设计符合RESTful规范的接口,并且它还提供了一些功能。
RESTful规范
RESTful规范锁一套接口的协议,用于程序与程序见数据交换的一套默认规则
RESTful规范规定了以下几点:
- https代替http,保证数据传输时安全。
- 在url中一般要体现api标识
- 在接口中要体现版本
- restful也称为面向资源编程,一般资源都使用名词
- 筛选条件要添加在url中
- 根据method不同做不同操作
- 返回状态码
- 返回值
- 增加数据时返回新增的数据
- 单条查询返回字典
- 多条查询返回列表
- 更新返回更新后的完整数据
- 删除返回空
- 操作异常时,要返回错误信息
- 返回下一个请求的接口
示例:
http://www.takanashi.com/api/v1/user/?page=1
安装
pip3 install djangorestframework
使用
drf本质是一个app,所以如果想要在Django项目中使用它就需要在settings配置文件中进行注册
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework'
]
使用drf时推荐在视图中使用CBV处理请求
# urls.py
from app1 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/book/$', views.Book.as_view()),
url(r'^api/book/(?P<pk>\d+)/$', views.Book.as_view()),
]
视图中根据请求不同做不同的处理,返回json格式的数据
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
# Create your views here.
from app1 import models
from django.forms.models import model_to_dict
class Book(APIView):
def get(self,request,*args,**kwargs):
# 查询
pk = kwargs.get("pk")
if pk:
book = models.Book.objects.filter(pk=pk).first()
return Response(model_to_dict(book))
books = models.Book.objects.all().values()
return Response(books)
def post(self,request,*args,**kwargs):
# 增加
book = models.Book.objects.create(**request.data)
return Response(model_to_dict(book))# model_to_dict将一个对象转换为字典
def put(self,request,*args,**kwargs):
# 修改
pk = kwargs.get("pk")
models.Book.objects.filter(id=pk).update(**request.data)
return Response("ok")
def delete(self,request,*args,**kwargs):
# 删除
pk = kwargs.get("pk")
models.Book.objects.get(pk=pk).delete()
return Response("ok")
序列化
drf中的serializers为我们提供了数据校验、序列化的功能
基于APIView实现
from rest_framework.views import APIView
from rest_framework.response import Response
from app1 import models
# 导入serializers
from rest_framework import serializers
class BookSerializers(serializers.ModelSerializer):
# 自定义字段:
# 方式一:source指定显示内容,required指定校验时忽略
title_1 = serializers.CharField(source="title",required=False)
# 方式二:这种写法需要写一个以get_开头的钩子函数,这个函数返回上面就显示什么
state_1 = serializers.SerializerMethodField()
# 如果想要返回orm中使用了choices的字段只需要这样写,不需要加括号,内部会检查是否可执行,如果可执行会自动加括号
state_2 = serializers.CharField(source="get_status_display",required=False)
class Meta:
# 指定表
model = models.Book
# 指定字段,全部字段、排除指定字段、选择指定字段
# fields = "__all__"
# exclude = ["id"]
fields = ["id","title","title_1","title_2","state_1","state_2"]
# 钩子函数,obj就是当前对象
def get_title_2(self,obj):
return obj.title
def get_state_1(self, obj):
return obj.get_status_display()
class Book(APIView):
def get(self,request,*args,**kwargs):
pk = kwargs.get("pk")
if pk:
book = models.Book.objects.filter(pk=pk).first()
# 实例化序列化对象,instance指定数据,many默认等于False,表示查询结果为一条数据
ser = BookSerializers(instance=book,many=False)
return Response(ser.data)
books = models.Book.objects.all().values()
# many=True表示查询结果有多条数据
ser = BookSerializers(instance=books,many=True)
return Response(ser.data)
def post(self,request,*args,**kwargs):
# data指定保存的数据
ser = BookSerializers(data=request.data)
# 校验数据
if ser.is_valid():
# 保存
ser.save()
# 返回保存的数据
return Response(ser.data)
# 返回错误信息
return Response(ser.errors)
def put(self,request,*args,**kwargs):
# 全部修改
pk = kwargs.get('pk')
book = models.Book.objects.filter(id=pk).first()
# instance指定修改哪条数据,data指定钥修改为的数据
ser = BookSerializers(instance=book, data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
return Response(ser.errors)
def patch(self, request, *args, **kwargs):
# 局部修改
pk = kwargs.get('pk')
book = models.Book.objects.filter(id=pk).first()
# partial=True指定此次修改为局部修改
ser = BookSerializers(instance=book,data=request.data,partial=True)
if ser.is_valid():
ser.save()
return Response(ser.data)
return Response(ser.errors)
基于ListAPIView实现
from app1 import models
from rest_framework import serializers
from rest_framework.generics import GenericAPIView,ListAPIView
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
class Book(ListAPIView):
queryset = models.Book.objects.all()
# 序列化
serializer_class = BookSerializers
筛选
基于APIView筛选
from app1 import models
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.generics import GenericAPIView,ListAPIView
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
class Book(APIView):
def get(self,request,*args,**kwargs):
pk = kwargs.get("pk")
if pk:
book = models.Book.objects.filter(pk=pk).first()
ser = BookSerializers(instance=book,many=False)
return Response(ser.data)
condition = {}
# 获取条件
title = request.query_params.get('title')
if title:
condition['title'] = title
books = models.Book.objects.filter(**condition).order_by('pk')
ser = BookSerializers(instance=books,many=True)
return Response(ser.data)
基于ListAPIView筛选
from app1 import models
from rest_framework import serializers
from rest_framework.generics import GenericAPIView,ListAPIView
# 导入BaseFilterBackend
from rest_framework.filters import BaseFilterBackend
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
# 自定义分页类
class MyFilterBackend(BaseFilterBackend):
# 必须实现filter_queryset方法
def filter_queryset(self, request, queryset, view):
val = request.query_params.get('title')
return queryset.filter(title=val)
class Book(ListAPIView):
queryset = models.Book.objects.all()
# 序列化
serializer_class = BookSerializers
# 分页
filter_backends = [MyFilterBackend,]
分页
基于APIView分页
方式一:
通过PageNumberPagination类进行分页,访问URL使用以下方式:http://api.example.org/accounts/?page=4
在settings配置中写入以下配置
REST_FRAMEWORK = {
# 默认显示数
"PAGE_SIZE":2,
}
from rest_framework.views import APIView
from rest_framework.response import Response
from app1 import models
from rest_framework import serializers
# 导入PageNumberPagination
from rest_framework.pagination import PageNumberPagination
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
class Book(APIView):
def get(self,request,*args,**kwargs):
queryset = models.Book.objects.all()
# 实例化分页类
page_obj = PageNumberPagination()
# 调用paginate_queryset方法对数据进行分页
result = page_obj.paginate_queryset(queryset,request,self)
print(result) # 结果为列表
# 对分页结果进行序列化
ser = BookSerializers(instance=result,many=True)
# 只返回数据
return Response(ser.data)
# 返回包含数据、总数据个数、上一页url、下一页url,也可以通过重写get_paginated_response方法自定义显示内容
return page_obj.get_paginated_response(ser.data)
方式二:
通过LimitOffsetPagination类进行分页,访问URL使用以下方式:http://127.0.0.1:8000/api/book/?limit=2&offset=2,limit指定显示几条数据,offset指定从第几条数据开始
from rest_framework.views import APIView
from rest_framework.response import Response
from app1 import models
from rest_framework import serializers
# 导入LimitOffsetPagination
from rest_framework.pagination import LimitOffsetPagination
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
class Book(APIView):
def get(self,request,*args,**kwargs):
queryset = models.Book.objects.all()
page_obj = LimitOffsetPagination()
result = page_obj.paginate_queryset(queryset,request,self)
ser = BookSerializers(instance=result,many=True)
return Response(ser.data)
基于ListAPIView分页
在settings配置中写入以下配置
REST_FRAMEWORK = {
# 默认显示数
"PAGE_SIZE":2,
# 指定分页类
"DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination"
}
from app1 import models
from rest_framework import serializers
from rest_framework.generics import GenericAPIView,ListAPIView
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
class Book(ListAPIView):
queryset = models.Book.objects.all()
# 序列化
serializer_class = BookSerializers
版本
局部使用
urls.py
urlpatterns = [
url(r'^api/(?P<version>\w+)/book/$', views.Book.as_view()),
]
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
# 导入
from rest_framework.versioning import URLPathVersioning
class Book(APIView):
versioning_class = URLPathVersioning
def get(self,reqeust,*args,**kwargs):
print(reqeust.version)
return Response("ok")
全局配置
想要在所有视图中都应用版本,可以在settings中进行配置
REST_FRAMEWORK = {
# 配置版本
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
# 配置允许版本
"ALLOWED_VERSIONS": ['v1', 'v2'],
# 配置url传参时的名称,默认为version
'VERSION_PARAM': 'version'
}
认证
局部使用
from app1 import models
from rest_framework.views import APIView
from rest_framework.response import Response
# 导入BaseAuthentication
from rest_framework.authentication import BaseAuthentication
class TokenAuthentication(BaseAuthentication):
# 必须实现authenticate
def authenticate(self, request):
# 获取url中的token
token = request.query_params.get("token")
book_object = models.Book.objects.filter(token=token).first()
if book_object:
return (book_object,token) # 认证成功,不再进行认证
return None # 认证成功,执行下一个认证类
return Exception() # 认证失败,返回给用户
class Book(APIView):
authentication_classes = [TokenAuthentication]
def get(self,reqeust,*args,**kwargs):
print(reqeust.user) # book_object对象
print(reqeust.auth) # token
return Response("ok")
全局配置
settings中配置后所有视图都进行认证
REST_FRAMEWORK = {
# 自定义Authentication的路径
'DEFAULT_AUTHENTICATION_CLASSES' : ["authon.auth.TokenAuthentication",]
}
权限
根据认证组件的结果判断是否有权限访问
局部使用
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import BasePermission
from rest_framework import exceptions
class MyPermission(BasePermission):
def has_permission(self, request, view):
if request.user:
# 返回True表示通过,进行下一个权限认证类,
return True
# 返回False表示认证未通过由源码内抛出异常
return False
# 也可以直接手动抛出异常
# return exceptions.PermissionDenied({"code":"1001","error":"错误信息"})
# RetrieveAPIView触发
def has_object_permission(self, request, view, obj):
return True
class Book(APIView):
permission_classes = [MyPermission,]
def get(self,reqeust,*args,**kwargs):
return Response("ok")
全局配置
REST_FRAMEWORK = {
# 自定义Permission的路径
'DEFAULT_PERMISSION_CLASSES':["authon.auth.MyPermission"]
}
使用全局配置后如果对某一视图不想应用该功能可以通过指定空列表的方式使其不生效
class Book(APIView):
permission_classes = []
def get(self,reqeust,*args,**kwargs):
return Response("ok")
频率限制
def的频率限制本质是在内部维护了一个字典,字典以throttle_anon_用户ip做key值是一个列表,当用户请求到来时获取当前时间,然后用当前时间减去60得到一个时间戳,将列表中小于这个时间戳的记录删除,然后获取列表中元素的个数,判断是否可以进行访问,如果可以进行访问则将当前时间插入到列表第一位。
局部使用
在settings中配置
REST_FRAMEWORK = {
# 配置访问次数限制 3/m 每分钟3次
'DEFAULT_THROTTLE_RATES':{"anon":'3/m'},
}
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.throttling import AnonRateThrottle,BaseThrottle
class Book(APIView):
throttle_classes = [AnonRateThrottle, ]
def get(self,reqeust,*args,**kwargs):
return Response("ok")
全局使用
在settings中配置
REST_FRAMEWORK = {
# 配置访问次数限制 3/m 每分钟3次
'DEFAULT_THROTTLE_RATES':{"anon":'3/m'},
# 配置AnonRateThrottle路径
'DEFAULT_THROTTLE_CLASSES':["rest_framework.throttling.AnonRateThrottle"]
}
DRF提供的APIView
from rest_framework.generics import ListAPIView,CreateAPIView,RetrieveAPIView,UpdateAPIView,DestroyAPIView
from rest_framework import serializers
from app1 import models
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
class Book(ListAPIView,CreateAPIView):
# 列表数据查询、增加数据
queryset = models.Book.objects.all()
serializer_class = BookSerializers
class BookDetailView(RetrieveAPIView,UpdateAPIView,DestroyAPIView):
# 单条数据查询、修改数据、删除数据
queryset = models.Book.objects.all()
serializer_class = BookSerializers
DRF源码分析
1.启动项目:
views.Book.as_view()
该条代码会执行APIView中的as_view
方法,这个方法内部首先会执行父类(View
)的as_view
方法,父类方法返回一个view函数,当其请求到来时执行这个函数。
2.请求到来:
请求到来首先执行之前得到的view函数,在函数内部调用了APIView函数的dispath
函数,在这个函数内部执行initialze_request
函数对request进行封装:
- 将原本的request封装为
_reqeust
, - 读取视图函数中的认证列表
authentication_classes
并将认证列表封装。
执行完这些后接着返回dispath
函数执行的initial
,在这个函数中首先会做版本的处理:
- 调用
determine_version
读取视图中的版本类versioning_class
- 执行
versioning_class
指定的类的determine_version
函数进行版本处理:- 获取url中的版本号
- 调用
is_allowed_version
函数校验版本号合法性
版本处理结束后会进行认证:
- 调用
perform_authentication
执行reqeust.user
- Reuest类的
user
会调用_authenticator
函数进行认证- 循环认证列表执行每个认证类的
authenticator
函数 - 在
authenticator
函数中做认证处理 - 得到认证结果将
authenticator
函数返回的第一个值封装到self.user
中,将第二个值封装到self.auth
中
- 循环认证列表执行每个认证类的
认证处理结束后进行权限处理:
- 调用
check_throttles
函数 - 函数内部通过get_permissions函数读取视图中的权限列表
permission_classes
- 循环权限列表执行列表中每个类的
has_permissions
函数进行权限认证
权限处理结束后进行频率限制:
- 调用
check_throttles
函数 - 函数内部通过get_throttles函数读取视图中的频率限制列表
throttles_classes
- 循环频率限制列表执行列表中每个类的allow_request函数
- 在
allow_request
进行频率限制:- 获得历史请求记录列表
- 将列表中失效的记录删除
- 通过列表长度判断是否可以访问
- 可以访问:将当前时间加到记录列表的第一位‘’
3.执行视图函数:
当上面所有步骤都结束后在dispath
函数中会通过反射执行视图中对应请求的函数,“以ListAPIView的get请求举例”:
- 执行ListAPIView中的
get
函数,函数内调用ListModeMixin的list
函数 - 在list函数中获得视图中的
queryset
- 执行
filter_quertset
对queryset进行筛选:- 读取视图中的
filter_backends
,得到筛选类列表 - 循环筛选类列表,执行每个类的
filter_queryset
进行筛选
- 读取视图中的
- 执行
paginate_queryset
对queryset进行分页:- 读取配置文件中的分页类
- 执行分页类的
paginate_queryset
函数 - 在函数中读取配置文件中的默认显示数和url中的页码
- 通过显示数和页码对
quertset
进行分页
4.将结果封装到response
中进行返回