目录
1、权限组件内部处理机制:按照列表的顺序逐一执行 has_permission 方法,如果返回True,则继续执行后续的权限类;如果返回None或False,则抛出权限异常并停止后续权限类的执行。
5、3权限补充(has_object_permission)
6、4不要太死板,后期一定要根据自己的需求来看源码的执行流程,重写里面的方法
1、快速上手
1、安装
pip install djangorestframework==3.12.4
2、配置,在settings.py中添加配置
INSTALLED_APPS = [
...
# 注册rest_framework(drf)
'rest_framework',
]
# drf相关配置以后编写在这里
REST_FRAMEWORK = {
}
3、URL和视图
1、URL
# urls.py
from django.urls import path
from app01 import views
urlpatterns = [
path('users/', views.UserView.as_view()),
]
2、视图
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
def get(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
drf中重写了 as_view
和dispatch
方法,其实就是在原来django的功能基础上添加了一些功能,例如:
-
as_view
,免除了csrf 验证,一般前后端分离不会使用csrf token认证(后期会使用jwt认证)。 -
dispatch
,内部添加了 版本处理、认证、权限、访问频率限制等诸多功能(后期逐一讲解)
2、请求数据的封装
在使用drf框架开发时,视图中的request对象与原来的有些不同,例如:
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views import View
from rest_framework.request import Request
class UserView(APIView):
def get(self, request, *args, **kwargs):
# 通过对象的嵌套直接找到原request,读取相关值
request._request.method
request._request.GET
request._request.POST
request._request.body
# 举例:
content-type: url-form-encoded
v1=123&v2=456&v3=999
django一旦读取到这个请求头之后,就会按照 {"v1":123,"v2":456,"v3":999}
content-type: application/json
{"v1":123,"v2":456}
request._request.POST
request._request.body
# 直接读取新request对象中的值,一般此处会对原始的数据进行一些处理,方便开发者在视图中使用。
request.query_params # 内部本质上就是 request._request.GET
request.data # 内部读取请求体中的数据,并进行处理,例如:请求者发来JSON格式,他的内部会对json字符串进行反序列化。
# 通过 __getattr__ 去访问 request._request 中的值
request.method
详细:
# 通过对象的嵌套直接找到原request,读取相关值
request._request.method
request._request.GET
request._request.POST
request._request.body
# 直接读取新request对象中的值,一般此处会对原始的数据进行一些处理,方便开发者在视图中使用。
request.query_params # 内部本质上就是 request._request.GET
request.data # 内部读取请求体中的数据,并进行处理,例如:请求者发来JSON格式,他的内部会对json字符串进行反序列化。
# 通过 __getattr__ 去访问 request._request 中的值
request.method
3、版本控制(versioning_class)
在restful规范中要去,后端的API中需要体现版本。
drf框架中支持5种版本的设置。
3、1URL的GET参数传递(*)
视图:
from rest_framework.versioning import QueryParameterVersioning
class UserView2(ModelViewSet):
versioning_class = QueryParameterVersioning
def post(self,request):
print(request.version)
全局配置,setting.py
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": "v", #接受传递版本的参数,v=v2
"DEFAULT_VERSION": "v1", #默认版本
"ALLOWED_VERSIONS": ["v1", "v2", "v3"], #只有版本允许,才能获取版本,默认版本为允许版本
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.QueryParameterVersioning"
#设置全局配置
}
3、2URL路径传递(*)
视图:
from rest_framework.versioning import URLPathVersioning
class UserView2(ModelViewSet):
versioning_class = URLPathVersioning
def post(self,request):
print(request.version)
全局配置,setting.py
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": "v", #接受传递版本的参数,v=v2
"DEFAULT_VERSION": "v1", #默认版本
"ALLOWED_VERSIONS": ["v1", "v2", "v3"], #只有版本允许,才能获取版本,默认版本为允许版本
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning"
#设置全局配置
}
注意:可以通过request.version获取到版本
4、认证(authentication_classes)
在开发后端的API时,不同的功能会有不同的限制,例如:
-
无需认证,就可以访问并获取数据。
-
需认证,用户需先登录,后续发送请求需携带登录时发放的凭证(后期会讲jwt)
4、1快速使用
视图:
class UserView2(ModelViewSet):
authentication_classes = ["MyAuth1", "MyAuth2"]
全局配置,settings.py
REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": lambda: None, #都没有通过认证时,request.user=的值
"UNAUTHENTICATED_TOKEN": lambda: None, #都没有通过认证时,request.auth=的值
"DEFAULT_AUTHENTICATION_CLASSES":["xxxx.xxxx.xx.类名","xxxx.xxxx.xx.类名",]
}
执行规程:
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
# 必须认证成功之后才能访问
class TokenAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if not token:
raise AuthenticationFailed({"code": return_code.AUTH_FAILED, "error": "认证失败"})
user_object = models.UserInfo.objects.filter(token=token).first()
if not user_object:
raise AuthenticationFailed({"code": return_code.AUTH_FAILED, "error": "认证失败"})
if datetime.datetime.now() > user_object.token_expiry_date:
raise AuthenticationFailed({"code": return_code.AUTH_OVERDUE, "error": "认证过期"})
return user_object, token
def authenticate_header(self, request):
return 'Bearer realm="API"'
在视图类中设置类变量 authentication_classes
的值为 认证类 TokenAuthentication,表示此视图在执行内部功能之前需要先经过 认证。
认证类的内部就是去执行:authenticate
方法,根据返回值来表示认证结果
-
抛出异常AuthenticationFailed,表示认证失败。内部还会执行
authenticate_header
将返回值设置给响应头WWW-Authenticate
-
返回含有两个元素的元组,表示认证成功,并且会将元素的第1个元素赋值给
request.user
、第2个值赋值给request.auth
。
第1个值,一般是用户对象。
第2个值,一般是token
-
返回None,表示继续调用 后续的认证类 进行认证(上述案例未涉及)
4、2 关于 ”返回None“
在视图类的 authentication_classes
中定义认证类时,传入的是一个列表,支持定义多个认证类。
当出现多个认证类时,drf内部会按照列表的顺序,逐一执行认证类的 authenticate
方法,如果 返回元组 或 抛出异常 则会终止后续认证类的执行;如果返回None,则意味着继续执行后续的认证类。
如果所有的认证类authenticate
都返回了None,则默认 request.user="AnonymousUser" 和 request.auth=None,也可以通过修改配置文件来修改默认值。
修改默认值:
REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": lambda: None,
"UNAUTHENTICATED_TOKEN": lambda: None,
}
当某个API,已认证 和 未认证 的用户都可以方法时,比如:
-
已认证用户,访问API返回该用户的视频播放记录列表。
-
未认证用户,访问API返回最新的的视频列表。
注意:不同于之前的案例,之前案例是:必须认证成功后才能访问,而此案例则是已认证和未认证均可访问。
4、3必须认证成功之后才能访问
即:认证不通过,直接报异常
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
# 必须认证成功之后才能访问
class TokenAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if not token:
raise AuthenticationFailed({"code": return_code.AUTH_FAILED, "error": "认证失败"})
user_object = models.UserInfo.objects.filter(token=token).first()
if not user_object:
raise AuthenticationFailed({"code": return_code.AUTH_FAILED, "error": "认证失败"})
if datetime.datetime.now() > user_object.token_expiry_date:
raise AuthenticationFailed({"code": return_code.AUTH_OVERDUE, "error": "认证过期"})
return user_object, token
def authenticate_header(self, request):
return 'Bearer realm="API"'
4、4未认证用户也能访问
即:
登录,可以访问 request.user
不登录,也可以访问 request.user=None
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
# 登录,可以访问 request.user
# 不登录,也可以访问 request.user=None
class UserAnonTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if not token:
return None
user_object = models.UserInfo.objects.filter(token=token).first()
if not user_object:
return None
if datetime.datetime.now() > user_object.token_expiry_date:
return None
return user_object, token
def authenticate_header(self, request):
return 'Bearer realm="API"'
4、5关于多个认证类
一般情况下,编写一个认证类足矣。
当项目中可能存在多种认证方式时,就可以写多个认证类。例如,项目认证支持:
-
在请求中传递token进行验证。
-
请求携带cookie进行验证。
-
请求携带jwt进行验证(后期讲)。
-
请求携带的加密的数据,需用特定算法解密(一般为app开发的接口都是有加密算法)
注意:此示例后续在视图中读取的 request.user
的值为None时,表示未认证成功;不为None时,则表示认证成功。
全局配置
在每个视图类的类变量 authentication_classes
中可以定义,其实在配置文件中也可以进行全局配置,例如:
REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": lambda: None,
"UNAUTHENTICATED_TOKEN": lambda: None,
"DEFAULT_AUTHENTICATION_CLASSES":["xxxx.xxxx.xx.类名","xxxx.xxxx.xx.类名",]
}
5、权限(permission_classes)
认证,根据用户携带的 token/其他 获取当前用户信息。
权限,读取认证中获取的用户信息,判断当前用户是否有权限访问,例如:普通用户、管理员、超级用户,不同用户具有不同的权限。
5、1快速使用
定义:权限类
class PermissionA(BasePermission):
message = {"code": 1003, 'data': "无权访问"}
def has_permission(self, request, view):
if request.user.role == 2:
return True
return False
# 暂时先这么写
def has_object_permission(self, request, view, obj):
return True
注意:has_permission针对的是某个用户有没有权限
has_object_permission针对的是用户对于某一条数据有没有权限
视图:
class OrderView(APIView):
permission_classes = [PermissionA,]
def get(self, request, *args, **kwargs):
print(request.user)
return Response({"code": 0, "data": {"user": None, 'list': [1, 2, 3]}})
5、2关于多个权限类
当开发过程中需要用户同时具备多个权限(缺一不可)时,可以用多个权限类来实现。
1、权限组件内部处理机制:按照列表的顺序逐一执行 has_permission
方法,如果返回True,则继续执行后续的权限类;如果返回None或False,则抛出权限异常并停止后续权限类的执行。
调用GenericAPIView中的self.get_object
方法时,会按照 permission_classes
中权限组件的顺序,依次执行他们的 has_object_permission
方法。
self.get_object
其实就根据用户传入的 pk,搜索并获取某个对象的过程。
2、全局配置
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES":["xxxx.xxxx.xx.类名","xxxx.xxxx.xx.类名",]
}
5、3权限补充(has_object_permission
)
在之前定义权限类时,类中可以定义两个方法:has_permission
和 has_object_permission
-
has_permission
,在请求进入视图之前就会执行。 -
has_object_permission
,当视图中调用self.get_object
时就会被调用(删除、更新、查看某个对象时都会调用),一般用于检查对某个对象是否具有权限进行操作。
class PermissionA(BasePermission):
message = {"code": 1003, 'data': "无权访问"}
def has_permission(self, request, view):
exists = request.user.roles.filter(title="员工").exists()
if exists:
return True
return False
def has_object_permission(self, request, view, obj):
return True
所以,让我们在编写视图类时,如果是直接获取间接继承了 GenericAPIView,同时内部调用 get_object
方法,这样在权限中通过 has_object_permission
就可以进行权限的处理。
6、限流
限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。
-
对于匿名用户,使用用户IP作为唯一标识。
-
对于登录用户,使用用户ID或名称作为唯一标识。
6、1快速使用
6、1、1配置redis
pip3 install django-redis
# settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "qwe123",
}
}
}
6、1、2视图展示
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import exceptions
from rest_framework import status
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import cache as default_cache
class ThrottledException(exceptions.APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_code = 'throttled'
#自定义限流类
class MyRateThrottle(SimpleRateThrottle):
cache = default_cache # 访问记录存放在django的缓存中(需设置缓存)
scope = "user" # 构造缓存中的key
cache_format = 'throttle_%(scope)s_%(ident)s'
# 设置访问频率,例如:1分钟允许访问10次
# 其他:'s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day'
THROTTLE_RATES = {"user": "10/m"}
def get_cache_key(self, request, view):
if request.user:
ident = request.user.pk # 用户ID
else:
ident = self.get_ident(request) # 获取请求用户IP(去request中找请求头)
# throttle_u # throttle_user_11.11.11.11ser_2
return self.cache_format % {'scope': self.scope, 'ident': ident}
def throttle_failure(self):
wait = self.wait()
detail = {
"code": 1005,
"data": "访问频率限制",
'detail': "需等待{}s才能访问".format(int(wait))
}
raise ThrottledException(detail)
#视图
class OrderView(APIView):
throttle_classes = [MyRateThrottle, ]
def get(self, request):
return Response({"code": 0, "data": "数据..."})
6、1、3注意事项(使用限流必须要做的)
一定要重写get_cache_key方法,它返回的是存储在redis的key
def get_cache_key(self, request, view):
if request.user:
ident = request.user.pk # 用户ID
else:
ident = self.get_ident(request) # 获取请求用户IP(去request中找请求头)
# throttle_u # throttle_user_11.11.11.11ser_2
return self.cache_format % {'scope': self.scope, 'ident': ident}
6、2多个限流类
本质,每个限流的类中都有一个 allow_request
方法,此方法内部可以有三种情况:
-
返回True,表示当前限流类允许访问,继续执行后续的限流类。
-
返回False,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
-
抛出异常,表示当前限流类不允许访问,后续限流类不再执行。
6、3全局配置
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES":["xxx.xxx.xx.限流类", ],
"DEFAULT_THROTTLE_RATES": {
"user": "10/m",
"xx":"100/h"
}
}
6、4不要太死板,后期一定要根据自己的需求来看源码的执行流程,重写里面的方法
1、根据请求方式判断是否会限流
class NewsView(DigListModelMixin, DigCreateModelMixin, GenericViewSet):
# 自定义的类变量
throttle_objects = [NewsCreateRateThrottle(), ]
def get_throttles(self):
if self.request.method == "POST":
return self.throttle_objects
return []
根据请求方式不同决定是否要调用限流类:一般get请求不会限制访问次数,post会限制次数
2、throttle_success才往列表里面添加数据
视图:
class NewsView(DigListModelMixin, DigCreateModelMixin, GenericViewSet):
filter_backends = [SelfFilterBackend, DjangoFilterBackend]
filterset_class = NewsFilterSet
# 未删除 & 属于当前用户创建的新闻资讯
queryset = models.News.objects.filter(deleted=False).order_by('-id')
serializer_class = NewsSerializer
# 自定义的类变量
throttle_objects = [NewsCreateRateThrottle(), ]
def perform_create(self, serializer):
# 1.创建新闻资讯
# 2.自己对自己的内容做推荐
# - 推荐数量+1
# - 推荐记录 用户&资讯
serializer.save(user=self.request.user)
# 数据库中已增加成功,调用限流的那个done方法
for throttle in self.get_throttles():
throttle.done()
def get_throttles(self):
if self.request.method == "POST":
return self.throttle_objects
return []
限流类:throttle_success改为done ,等数据添加完,执行done,可以实现数据添加成功时(将诶口访问成功),将数据加入到redis的限流时间戳
def throttle_success(self):
"""
Inserts the current request's timestamp along with the key
into the cache.
"""
#self.history.insert(0, self.now)
#self.cache.set(self.key, self.history, self.duration)
return True
def done(self):
"""
Inserts the current request's timestamp along with the key
into the cache.
"""
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
return True
在请求完成时,数据添加成功,在调用done方法,把该次请求的时间添加到redis的缓存中
3、重写throttle_failure(自定义返回异常)
不满足限流条件直接触发异常:
# views.py
from rest_framework import exceptions
from rest_framework import status
class ThrottledException(exceptions.APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_code = 'throttled'
#自定义限流类
class MyRateThrottle(SimpleRateThrottle):
cache = default_cache # 访问记录存放在django的缓存中(需设置缓存)
scope = "user" # 构造缓存中的key
cache_format = 'throttle_%(scope)s_%(ident)s'
def throttle_failure(self):
wait = self.wait()
detail = {
"code": 1005,
"data": "访问频率限制",
'detail': "需等待{}s才能访问".format(int(wait))
}
raise ThrottledException(detail)
注意:需要自定义异常的类,默认的exception.threttld只会接受一个wait()数字
7、Serializer校验和序列化(*)
连接如下:
8、视图
8.1 APIView
-
View,django
-
APIView,drf,在请求到来时,新增了:免除csrf、请求封装、版本、认证、权限、限流的功能。
APIView
是drf中 “顶层” 的视图类,在他的内部主要实现drf基础的组件的使用,例如:版本、认证、权限、限流等。
8.2 GenericAPIView
GenericAPIView
继承APIView,在APIView的基础上又增加了一些功能。例如:get_queryset
、get_object、
get_serializer、filter_backends、pagination_class等。
直接体现为:组合搜索、分页
实际在开发中一般不会直接继承它,他更多的是担任 中间人
的角色,为子类提供公共功能。
注意:最大的意义,将数据库查询、序列化类提取到类变量中,后期再提供公共的get/post/put/delete等方法,让开发者只定义类变量,自动实现增删改查。
8.3 GenericViewSet
GenericViewSet
类中没有定义任何代码,他就是继承 ViewSetMixin
和 GenericAPIView
,也就说他的功能就是将继承的两个类的功能继承到一起。
-
GenericAPIView
,将数据库查询、序列化类的定义提取到类变量中,便于后期处理。 -
ViewSetMixin
,将 get/post/put/delete 等方法映射到 list、create、retrieve、update、partial_update、destroy方法中,让视图不再需要两个类。
注意:开发中一般也很少直接去继承他,因为他也属于是 中间人
类,在原来 GenericAPIView
基础上又增加了一个映射而已。
8.4 五大类
在drf的为我们提供好了5个用于做 增、删、改(含局部修改)、查列表、查单个数据的5个类(需结合 GenericViewSet
使用)。
# urls.py
from django.urls import path, re_path, include
from app01 import views
urlpatterns = [
path('api/users/', views.UserView.as_view({"get":"list","post":"create"})),
path('api/users/<int:pk>/', views.UserView.as_view({"get":"retrieve","put":"update","patch":"partial_update","delete":"destroy"})),
]
# views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import (
ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin,
DestroyModelMixin, ListModelMixin
)
class UserView(CreateModelMixin,RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin,ListModelMixin,GenericViewSet):
# 认证、权限、限流等
queryset = models.UserInfo.objects.filter(status=True)
serializer_class = 序列化类
在这个5个类中已帮我们写好了 list
、create
、retrieve
、update
、partial_update
、destory
方法,我们只需要在根据写 类变量:queryset、serializer_class即可。
在开发过程中使用 五大类
或 ModelViewSet
是比较常见的,并且如果他们内部的某些功能不够用,还可以进行重新某些方法进行扩展。
如果一个视图具备所有的功能
from rest_framework.viewsets import ModelViewSet
问题:drf中提供了这么多视图,以后那个用的比较多?
-
接口与数据库操作无关,直接继承APIView
-
接口背后需要对数据库进行操作,一般:
ModelViewSet
或CreateModelMixin、ListModelMixin...
- 利用钩子自定义功能。 - 重写某个写方法,实现更加完善的功能。
-
根据自己公司的习惯,自定义 :
ModelViewSet
或CreateModelMixin、ListModelMixin...
9、条件搜索和分页
连接:
10、路由
针对具备所有功能的视图:引入router
from rest_framework import routers
from app01 import views
router = routers.SimpleRouter()
router.register(r'api/users', views.UserView)
urlpatterns = [
# 其他URL
# path('xxxx/', xxxx.as_view()),
]
urlpatterns += router.urls
也可以利用include,给URL加前缀:
from django.urls import path, include
from rest_framework import routers
from app01 import views
router = routers.SimpleRouter()
router.register(r'users', views.UserView)
urlpatterns = [
path('api/', include((router.urls, 'app_name'), namespace='instance_name')),
# 其他URL
# path('forgot-password/', ForgotPasswordFormView.as_view()),
]