内容概要

  • 路由系统

  • 认证组件

  • 权限组件

  • 频率组件

  • 过滤/排序

  • 分页组件

路由系统

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')

image-20230207213551289

权限组件

# 即便登录成功了,有些接口,还是不能访问,因为没有权限

# 登录后,有的接口有权限访问,有的没有权限访问

# 查询单个和查询所有,都要登录才能访问----》全局认证
	-查询单个需要超级管理员才能访问
    -查询所有,所有登录用户都能访问
    
    
# 权限是一个字段,在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 = [] 

image-20230207215255111

频率组件

# 控制某个接口访问频率(次数)


# 查询所有接口,同一个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',
}