drf之三大认证、过滤、排序、分页组件
本文所介绍的组件,都有着很相似的配置方式,继承组件类,类体中配置参数,视图类中配置参数添加对应的组件类或者全局配置,我们就可以方便的使用drf提供的组件了。这些组件也足够常用。
认证组件
对于接口而言,有些接口应该是需要登录认证后才能访问,我们需要先写一个登录的功能:
登录接口
登录功能我们首先要简单的准备用户模型表:
- 用户表存储用户信息,这里就只用用户和密码两个字段了
- token表相当于我们自己建立的session表,在服务端存一个随机字符串,用于两侧比对验证登录。
登录的路由和视图类一并配好,因为login功能不是五个常用接口之一,所以需要加action装饰器,书写路由时则按/api/v1/user/login的 post 请求。
login接口的逻辑也是需要自己重写的:
- 验证用户名和密码,查看是否有这个用户
- 有相应用户的话拿到token表中比对,如果有的话更新token值即可,如果没有则新建一条
#### 表模型
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
class UserToken(models.Model): # 跟User是一对一
token = models.CharField(max_length=32)
user = models.OneToOneField(to='User', on_delete=models.CASCADE, null=True)
# user :反向,表名小写,所有有user字段
### 路由
router.register('user', views.UserView, 'user') # /api/v1/user/login post 请求
# 视图类
#### 登录接口 自动生成路由+由于登录功能,不用序列化,继承ViewSet
from .models import User, UserToken
import uuid
class UserView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
if user:
# 用户存在,登录成功
# 生成一个随机字符串--uuid
token = str(uuid.uuid4()) # 生成一个永不重复的随机字符串
# 在userToken表中存储一下:1 从来没有登录过,插入一条, 2 登录过,修改记录
UserToken.objects.update_or_create(user=user, defaults={'token': token})
return Response({'code': '100', 'msg': '登录成功', 'token': token})
else:
return Response({'code': '101', 'msg': '用户名或密码错误'})
update_or_create的用法,按照defaults外的字段判断是更新还是新增,defaults的内容一定会更新到表中。
认证功能实现
需求:查询所有登不登录都可以,查询单个需要登录
认证功能流程:
-
继承BaseAuthentication类,产生认证子类
-
在子类中必须覆写authenticate(self, request)方法
-
方法中必须固定的返回(用户数据对象,token值)
这里的用户数据对象会保存在request.user中
-
如果认证不成功则需要抛出指定的异常
rest_framework.exceptions.AuthenticationFailed
-
# 认证类
from rest_framework import authentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed
class CommonAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
if not token:
raise AuthenticationFailed('没有传token')
user_token = UserToken.objects.filter(token=token).first()
if not user_token:
raise AuthenticationFailed('你还未登录,无法使用接口权限')
return user_token.user, token
-
将认证子类注册到局部或全局
-
局部注册--在继承自GenericAPIView的视图类内部配置参数
from .authentication import CommonAuthentication class BookDetailView(GenericViewSet, mixins.RetrieveModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer # 还可以配置多个认证类,从左到右执行 authentication_classes = [CommonAuthentication]
-
全局注册
# 项目的settings.py中 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'app01.authentication.CommonAuthentication' ], }
-
局部禁用--在继承自GenericAPIView的视图类内部的认证类配置为空列表
class UserView(GenericViewSet): authentication_classes = []
-
权限组件
需求:用户为超级管理员则可以新增,修改等,普通用户只能查看单个,没有登录同上
这就涉及了权限问题,认证后也不一定拥有相应的权限,drf也提供了权限组件。
数据准备
在用户模型表增加一个权限字段,这里就设置为用户类型好了:
user_type = models.IntegerField(choices=((1, '超级管理员'), (2, '普通用户')), default=2)
迁移好后,那么在后续判断权限时就可以通过这个字段来判断。
这里用到了choices列举此字段的所有可能,存储和正常取出都是1,2数字,但是通过
get_字段_display
可以拿到超级管理员、普通用户数据。
权限功能实现
权限功能流程:
-
继承BasePermission类,产生子类
-
在类中改写has_permission方法
在得到有权限的结论时返回True,没有则返回False,可以使用self.message更改无权限的提示信息
class CommonPermission(BasePermission): def has_permission(self, request, view): if request.user.user_type == 1: return True self.message = f'你是{request.user.get_user_type_display()},你的权限不够' return False
-
将权限类校验注册到局部或全局生效。
# 在视图类中局部配置 class BookDetailView(GenericViewSet, mixins.RetrieveModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer permission_classes = [CommonPermission] # 需要登录认证后再进行权限认证 # 全局配置 REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'app01.permission.CommonPermission', ], }
频率组件
用于控制某接口的访问频率,如查询所有对于同一个ip,限制它只能一分钟访问5次。
限制频率功能的简单实现
-
继承SimpleRateThrottle类写一个频率类
-
重写get_cache_key方法,返回什么,就以什么做限制(可以是ip或者用户id)
-
配置一个类属性:scope='book_5_m'
from rest_framework.throttling import SimpleRateThrottle class CommonThrottle(SimpleRateThrottle): scope = 'book_5_m' def get_cache_key(self, request, view): return request.META.get('REMOTE_ADDR')
-
在配置文件中配置
'DEFAULT_THROTTLE_RATES': {'book_5_m': '5/m',},
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'book_5_m': '5/m', }, }
-
局部使用和全局使用
-
局部使用--直接在视图类中添加throttle_classes列表参数,将频率类放入。
class BookView(GenericViewSet, mixins.ListModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer authentication_classes = [] permission_classes = [CommonPermission] throttle_classes = [CommonThrottle]
-
全局使用--配置settings
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'app01.throttle.CommonThrottle', ], }
三大认证执行顺序:
每一种认证都按局部先生效,再是项目的settings.REST_FRAMEWORK,最后是drf的settings.DEFAULTS
三大认证按authentication--》permission--》throttle的顺序执行。
-
过滤排序组件
在restful规范中,要求请求地址携带过滤条件
而5大接口中,只有查询所有需要携带过滤条件。
内置过滤类SearchFilter的使用
SearchFilter是固定用法,会模糊匹配所有字段。
from rest_framework.filters import SearchFilter
class BookView(GenericViewSet, mixins.ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = SearchFilter
http://127.0.0.1:8001/api/v1/book/?search=红
会模糊搜索所有序列化字段,包括name、price、publisher。
使用第三方djagno-filter实现过滤
首先需要安装djagno-filter,它的规则是完整的匹配字段的内容,而且搜索携带的数据必须按照:?字段=内容&字段=内容
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name','price'] # 定制了可以通过name和price字段进行完整匹配查询
自定制过滤类实现过滤
这种能够自定义通过什么样的字段进行匹配查询
定义过滤类,继承BaseFilterBackend,重新filter_queryset方法
然后将过滤类,配置到视图函数中,还是通过filter_backends = []的方式
# 过滤类
from rest_framework.filters import BaseFilterBackend
from django.db.models import Q
class CommonFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
q_obj = Q()
# 遍历查询参数,将参数加到q条件中,进行filter查询
for field, condition in request.query_params.items():
if field in view.filter_fields: # 支持视图类中对查询字段进行编辑
q_obj.children.append((field,condition))
return queryset.filter(q_obj)
# 视图类
class BookView(GenericViewSet, mixins.ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = []
throttle_classes = [CommonThrottle]
filter_backends = [CommonFilter]
filter_fields = ['price__contains', 'name__contains']
排序类
排序使用内置的即可,它也属于过滤类的一种,配置在filter_backends列表中即可。
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [OrderingFilter]
ordering_fields = ['price'] # 支持排序的字段
配置好后前端可以通过以下方式进行查询:
http://127.0.0.1:8001/api/v1/book/?ordering=price # 正常按价格排序
http://127.0.0.1:8001/api/v1/book/?ordering=-price # 倒序排序
http://127.0.0.1:8001/api/v1/book/?ordering=-id,price # 先按一个字段排序,如果有相同的再按另外一个字段排序
分页类
分页只有查询所有接口需要。drf内置了三个分页器,对应三种分页方式。
继承PageNumberPagination
这种最贴合人对分页的习惯,但是效率并不算高,它只需要设置以下参数:
from rest_framework.pagination import PageNumberPagination
class CommonPageNumberPagination(PageNumberPagination):
page_size = 2 # 每页显示2条
page_query_param = 'page' # 前端分页键的名字,?page=10 查询第十页
page_size_query_param = 'size' # ?page=10&size=5 查询第10页,每页显示5条
max_page_size = 5 # 每页最大显示5条,size的值大于这个值无效,只取5
而在前端拿到了下面的数据:
{
"count": 3,
"next": "http://127.0.0.1:8001/api/v1/book/?page=2",
"previous": null,
"results": [...]
}
分别对应总数据条数,下页的数据网址,上页的数据网址,数据结果。
前端的网址可以通过携带?page=页数
来拿到指定页的数据。
继承LimitOffsetPagination
这种则是让前端通过limit每页条数和offset偏移条数两个参数进行分页,即开始的条数和此页有几条数据的方式取。
网址方式:http://127.0.0.1:8001/api/v1/book/?offset=2&limit3
class CommonLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3 # 每页显示2条
limit_query_param = 'limit' # limit=3 取3条
offset_query_param = 'offset' # offset=1 从第一个位置开始,取limit条
max_limit = 5
继承CursorPagination
这种分页方式只支持上下翻页,但是面对大量数据,其效率会有很大的提升。
class CommonCursorPagination(CursorPagination):
cursor_query_param = 'cursor' # 查询参数
page_size = 2 # 每页多少条
ordering = 'id' # 排序字段
标签:之三,request,认证,filter,token,user,组件,class,drf
From: https://www.cnblogs.com/Leethon-lizhilog/p/17099892.html