drf认证/权限/频率/分页-过滤-排序
内容概要
-
路由系统
-
认证组件
-
权限组件
-
频率组件
-
过滤/排序
-
分页组件
路由系统
1.自动生成路由
drf 由于继承ViewSetMixin类,路由写法变了
-原生+drf,以后的路由写法,可能会有如下情况(三种情况)
1.-path('books/', views.BookView.as_view()
2.-path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'}))
3.-自动生成
# drf提供了两个路由类,继承ModelViewSet后,路由可以自动生成
# 使用步骤:
# 第一步:导入路由类
from rest_framework.routers import SimpleRouter,DefaultRouter
# 第二步,实例化得到对象(两个类,一般使用SimpleRouter)
router = SimpleRouter()
# 第三步:注册:router.register('books', views.BookView, 'books')
# 第四步:在urlpatterns中注册,两种方式
1.-urlpatterns += router.urls
2.-include:path('/api/v1/', include(router.urls)) 方式多一些
# 底层实现:自动生成路由就
-本质是自动做映射,能够自动成的前提是,视图类中要有 5个方法的某几个或多个
get--->list
get---->retrieve
put---->update
post---->create
delete---->destory
-ModelViewSet,ReadOnlyModelViewSet可以自动生成
-9个试图子类+配合ViewSetMixin 才可以自动生成
-GenericAPIView+5个试图扩展类+配合ViewSetMixin 才能自动生成
2.action装饰器
# action 装饰器是写在视图类的方法上,可以自动生成路由
# 使用步骤
1.写在视图类方法上
class SendView(ViewSet)
# methods指定请求方法,可以传多个
# detail:只能传True和False
-False,不带id的路径:send/send_sms/
-True,带id的路径:send/2/send_sms/
#url_path:生成send后路径的名字,默认以方法名命名
# url_name:别名,反向解析使用,了解即可
@action(methods=['POST'], detail=False)
def send_sms(self, request):
# 以后看到的drf路由写法
后期,都是自动生成,一般不在urlpatterns 加入路由了
# action 写在视图类的方法上,可以自动生成路由
# 使用步骤
- 1 写在视图类方法上
class SendView(ViewSet):
# methods指定请求方法,可以传多个
# detail:只能传True和False
-False,不带id的路径:send/send_sms/
-True,带id的路径:send/2/send_sms/
# url_path:生成send后路径的名字,默认以方法名命名
# url_name:别名,反向解析使用,了解即可
@action(methods=['POST'], detail=False)
def send_sms(self, request):
# 以后看到的drf路由写法
后期,都是自动生成,一般不在urlpatterns 加入路由了
# 补充:
-1 不同请求方式可以使用不同序列化类
-2 不同action使用不同序列化类
class SendView(GenericViewSet):
queryset = None
serializer_class = '序列化类'
def get_serializer(self, *args, **kwargs):
if self.action=='lqz':
return '某个序列化类'
else:
return '另一个序列化列'
@action(methods=['GET'], detail=True)
def send_sms(self, request,pk):
print(pk)
# 手机号,从哪去,假设get请求,携带了参数
phone = request.query_params.get('phone')
print('发送成功,%s' % phone)
return Response({'code': 100, 'msg': '发送成功'})
@action(methods=['GET'], detail=True)
def lqz(self,request): # get
# 序列化类
pass
@action(methods=['GET'], detail=True)
def login(self,request): # get
# 序列化类
pass
认证组件
1.认证组件使用步骤
# 1 写一个认证类,继承BaseAuthentication
# 2 重写authenticate方法,在该方法在中实现登录认证:token在哪带的?如果认证它是登录了
# 3 如果认证成功,返回两个值【返回None或两个值】
# 4 认证不通过,抛异常AuthenticationFailed
# 5 局部使用和全局使用
-局部:只在某个视图类中使用(当前视图类管理的的所有接口)
class BookDetailView(ViewSetMixin, RetrieveAPIView):
authentication_classes = [LoginAuth]
-全局:全局所有接口都生效(登录接口不要)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.authenticate.LoginAuth']
}
-局部禁用:
class BookDetailView(ViewSetMixin,RetrieveAPIView):
authentication_clsses = []
# 视图类
# 查询所有
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 查询单个
class BookDetailView(ViewSetMixin, RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# authentication_classes = [LoginAuth] # 需要写一个认证类,需要咱们写
# 认证组件 单独的authenticate.py文件编写
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# 在这里实现认证,如果是登录的,继续往后走返回两个值,如果不是抛异常
# 请求中是否携带token,判断是否登录,放在地址栏中
token = request.query_params('token', None)
if token: # 前端传入token了,去表中查,如果能查到,登录了,返回两个值[固定的:当前登录用户,token]
user_token_obj = models.UserToken.objects.filter(token=token).first()
if user_token_obj:
return user_token_obj.user, token # 返回两个值[固定的:当前登录用户,token]
# 没有登录抛异常
raise AuthenticationFailed('token验证失败')
raise AuthenticationFailed('没有token')
# 路由代码
router.register('books', views.BookView, 'books')
router.register('books', views.BookDetailView, 'books')
权限组件
# 即便登录成功了,有些接口,还是不能访问,因为没有权限
# 登录后,有的接口有权限访问,有的没有权限访问
# 查询单个和查询所有,都要登录才能访问----》全局认证
-查询单个需要超级管理员才能访问
-查询所有,所有登录用户都能访问
# 权限是一个字段,在User表中,加入user_type字段
1.权限使用
# 1 写一个权限类,继承BasePermission
# 2 重写has_permission方法,在该方法在中实现权限认证,在这方法中,request.user就是当前登录用户
# 3 如果有权限,返回True
# 4 没有权限,返回False,定制返回的中文: self.message='中文'
# 5 局部使用和全局使用
-局部:只在某个视图类中使用【当前视图类管理的所有接口】
class BookDetailView(ViewSetMixin, RetrieveAPIView):
permission_classes = [CommonPermission]
-全局:全局所有接口都生效
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'app01.permissions.CommonPermission',
],
}
-局部禁用:
class BookDetailView(ViewSetMixin, RetrieveAPIView):
permission_classes = []
频率组件
# 控制某个接口访问频率(次数)
# 查询所有接口,同一个ip一分钟只能访问5次
1.使用步骤
# 1 写一个频率类,继承SimpleRateThrottle
# 2 重写get_cache_key方法,返回什么,就以什么做限制----》ip,用户id做限制
# 3 配置一个类属性:scope = 'book_5_m'
# 4 在配置文件中配置
'DEFAULT_THROTTLE_RATES': {
'book_5_m': '5/m',
},
# 5 局部使用和全局使用
-局部:只在某个视图类中使用【当前视图类管理的所有接口】
class BookDetailView(ViewSetMixin, RetrieveAPIView):
throttle_classes = [CommonThrottle]
-全局:全局所有接口都生效
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.CommonThrottle'],
}
-局部禁用:
class BookDetailView(ViewSetMixin, RetrieveAPIView):
throttle_classes = []
过滤排序
restful规范中,要求了,请求地址中带过滤条件
5个接口中,只有一个接口需要有过滤和排序,就是'查询所有接口'
举例: 查询 所有图书接口,查询以 红 开头的所有图书
内置过滤类的使用【继承GenericAPIView】
# 内置过滤类,必须继承GenericAPIView及其子类,才能使用这种方法---》配置过滤类的方式
from rest_framework.filters import SearchFilter, OrderingFilter
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# SearchFilter内置的,固定用法,模糊匹配
# 就有过滤功能了,指定按哪个字段过滤
filter_backends = [SearchFilter]
# search_fields = ['name'] # 可以按名字模糊匹配
search_fields = ['name','price'] # 可以按名字模糊匹配或价格模糊匹配
自定义过滤类实现过滤
# 查询价格大于100的所有图书
http://127.0.0.1:8000/api/v1/books/?price_gt=100
#第一步; 定义一个过滤类,继承BaseFilterBackend,重写filter_queryset方法
class CommonFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# 在里面实现过滤,返回qs对象,就是过滤后的数据
price_gt = request.query_params.get('price_gt', None)
if price_gt:
qs = queryset.filter(price__gt=int(price_gt))
return qs
else:
return queryset
# 第二步:配置在视图类上
from app01 import filter
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [filter.CommonFilter] # 可以定制多个,从左往右,依次执行
使用第三方djagno-filter实现过滤
# 安装:django-filter
# 下载了django-filter需要重新安装以下django2.22版本 他会自动升级django至4.x版本
from django_filters.rest_framework import DjangoFilterBackend
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = []
authentication_classes = []
throttle_classes = []
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name','price'] # 支持完整匹配 name=聊斋11&price=933
# 支持的查询方式
http://127.0.0.1:8000/api/v1/books/?price=939
排序使用
# 内置的就够了
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [OrderingFilter]
ordering_fields = ['price']
# filter_backends = [OrderingFilter,filter.CommonFilter] # 可以定制多个,从左往右,依次执行
# ordering_fields = ['price']
# 支持的查询方法:
http://127.0.0.1:8000/api/v1/books/?ordering=price
http://127.0.0.1:8000/api/v1/books/?ordering=-price
http://127.0.0.1:8000/api/v1/books/?ordering=-id,price
分页使用
# 分页,只有查询所有接口,才有分页
# drf内置了三个分页器,对应三种分页方式
#内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
自定义一个分页类
# 分页使用,自定义一个分页类(三种)
class CommonPageNumberPagination(PageNumberPagination):
page_size = 2 # 每页显示2条
page_query_param = 'page' # page=10 查询第10页的数据,每页显示2条
page_size_query_param = 'size' # page=10&size=5 查询第10页,每页显示5条
max_page_size = 5 # 每页最大显示10条
# LimitOffset 类似偏移
class CommonLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3 # 每页显示2条
limit_query_param = 'limit' # limit=3 取3条
offset_query_param = 'offset' # offset=1 从第一个位置开始,取limit条
max_limit = 5
# offset=3&limit=2 0 1 2 3 4 5
# app 用下面
class CommonCursorPagination(CursorPagination):
cursor_query_param = 'cursor' # 查询参数
page_size = 2 # 每页多少条
ordering = 'id' # 排序字段
# 配置在视图类上即可
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = []
authentication_classes = []
throttle_classes = []
# 之前的东西一样用 ,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
# pagination_class = PageNumberPagination
#基本分页方式(基本是这种,网页端):http://127.0.0.1:8000/api/v1/books/?page=2&size=3
# pagination_class = LimitOffsetPagination
# 偏移分页 http://127.0.0.1:8000/api/v1/books/?limit=4&offset=1
# 从第一条开始,取4条
pagination_class = CommonCursorPagination
# 游标分页,只能下一页,上一页,不能跳到中间,但它的效率最高,大数据量分页,使用这种较好
基于APIView编写分页
# 分页功能,只有查询所有才有
class BookView(ViewSetMixin, APIView):
def list(self, request):
books = Book.objects.all()
# 使用步骤
# 1 实例化得到一个分页类的对象
paginator = CommonLimitOffsetPagination()
# 2 调用分页类对象的paginate_queryset方法来完成分页,返回的page是 要序列化的数据,分页好的
page = paginator.paginate_queryset(books, request, self)
if page is not None:
serializer = BookSerializer(instance=page, many=True)
# 3 返回数据,调用paginator的get_paginated_response方法
# return paginator.get_paginated_response(serializer.data)
return Response({
'total': paginator.count,
'next': paginator.get_next_link(),
'previous': paginator.get_previous_link(),
'results': serializer.data
})
异常处理
# APIView--->dispatch--->三大认证,视图类的方法,如果出了一场,会被一场捕获,捕获后统一处理
# drf 内置了一个函数,只要上面过程出了异常,就会执行这个函数,这个函数只处理的drf的异常
-主动抛的非drf异常
-程序出错了
都不会被处理
我们的目标,无论主动抛还是程序运行出错,都同意返回规定格式--》能记录日志
公司里一般返回 {code:999,'msg':'系统错误,请联系系统管理员'}
# 写一个函数,内部处理异常,在配置文件中配置一下即可
def common_exception_handler(exc, context):
# exc 错误对象
# context:上下文,有view:当前出错的视图类的对象,args和kwargs视图类方法分组出来的参数,request:当次请求的request对象
# 只要走到这里,就要记录日志 ,只有错了,才会执行这个函数
# 记录日志尽量详细
print('时间,登录用户id,用户ip,请求方式,请求地址,执行的视图类,错误原因')
res = exception_handler(exc, context)
if res: # 有值,说明返回了Response 对象,没有值说明返回None
# 如果是Response 对象说明是drf的异常,已经被处理了,如果是None表明没有处理,就是非drf的异常
res = Response(data={'code': 888, 'msg': res.data.get('detail', '请联系系统管理员')})
else:
# res = Response(data={'code': 999, 'msg': str(exc)})
# 记录日志
res = Response(data={'code': 999, 'msg': '系统错误,请联系系统管理员'})
return res
# 在配置文件中配置
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
}
分类:
Django框架 / Drf框架
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律