• django-rest-framework 基础四 过滤、排序、分页、异常处理


    django-rest-framework 基础四 过滤、排序、分页、异常处理

    1. 过滤

    在之前所写的五个接口中,只有获取所有需要过滤,其他接口都不需要。如在访问的时候带参数过滤出自己想要的数据。

    http://127.0.0.1:8080/?search=活着
    

    1.1 内置过滤类

    views.py

    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    
    from 应用名.models import Book  # 数据库
    from 应用名.serializer import BookSerializer # 序列化器
    
    from rest_framework.filters import SearchFilter
    
    
    class BookView(GenericViewSet, ListModelMixin):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        filter_backends = [SearchFilter,]
        # 过滤name
        # search_fields = ['name']
    	# 过滤namt或author
        search_fields = ['name','author']
    

    路由urls.py

    from django.contrib import admin
    from django.urls import path,include
    from authenticated import views
    from rest_framework import routers
    
    router = routers.SimpleRouter()
    router.register('books', views.BookView,"books")
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', include(router.urls))
    ]
    
    

    访问(模糊匹配):

    http://127.0.0.1:8000/books/?search=西游记
    http://127.0.0.1:8000/books/?search=华
    

    image-20220406224215678

    image-20220406224259442

    1.2 第三方过滤类

    使用第三方过滤类,

    第一步先安装django-filter

    pip install django-filter
    

    第二步在配置里注册settings.py

    INSTALLED_APPS = [
    	...
        'rest_framework',
        'django_filters',
    ]
    

    第三步在视图类中使用views.py

    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    
    from 应用名.models import Book  # 数据库
    from 应用名.serializer import BookSerializer # 序列化器
    
    from django_filters.rest_framework import DjangoFilterBackend
    class BookView(GenericViewSet, ListModelMixin):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        filter_backends = [DjangoFilterBackend,]
    
        filter_fields = ['name','author']
    

    路由还是原来的配置。

    这时访问是要过滤,关键字不能写search了,写search会把全部都打印出来,要写具体的字段名,而且后面要过滤的内容是精准匹配

    http://127.0.0.1:8000/books/?name=西游记, 如果像之前直接写西则匹配不出来
    

    image-20220406225631132

    http://127.0.0.1:8000/books/?name=活着&author=余华  # 这里面是and的关系,name是活着并且author是余华的
    

    image-20220406225714022

    1.3 自定义过滤类

    单独写一个类继承BaseFilterBackend 基类,重写filter_queryset方法,返回queryset对象

    filter.py

    from rest_framework.filters import BaseFilterBackend
    
    class BookFilter(BaseFilterBackend):
        def filter_queryset(self, request, queryset, view):
            query = request.query_params.get('name')
            if query:
                queryset =  queryset.filter(name__contains=query)
            return queryset
    
    

    views.py

    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    
    from 应用名.models import Book  # 数据库
    from 应用名.serializer import BookSerializer # 序列化器
    
    
    from 应用名.filter import BookFilter
    
    
    class BookView(GenericViewSet, ListModelMixin):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        # filter_backends = [DjangoFilterBackend,]
        filter_backends = [BookFilter,]
    
    

    由于只写了name字段,所以只能匹配name

    http://127.0.0.1:8000/books/?name=西  # 模糊匹配 ,自己定义的
    

    image-20220407000028763

    image-20220407000129979

    2. 排序

    可以使用DRF内置的OrderingFilter类进行排序。

    views.py

    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    
    from 应用名.models import Book  # 数据库
    from 应用名.serializer import BookSerializer # 序列化器
    
    
    
    from rest_framework.filters import OrderingFilter
    class BookView(GenericViewSet, ListModelMixin):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        filter_backends = [OrderingFilter,]
    
        ordering_fields=['price']  # 按价格排序
    

    访问:

    http://127.0.0.1:8000/books/?ordering=price  # 正序
    

    image-20220407232111823

    http://127.0.0.1:8000/books/?ordering=-price  # 倒序, 使用减号(-)为倒序
    

    image-20220407232224688

    可以把过滤和排序放在一块

    views.py

    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    
    from 应用名.models import Book  # 数据库
    from 应用名.serializer import BookSerializer # 序列化器
    
    from rest_framework.filters import SearchFilter
    
    
    from rest_framework.filters import OrderingFilter
    
    class BookView(GenericViewSet, ListModelMixin):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        filter_backends = [SearchFilter,OrderingFilter,]
    	# 排序
        ordering_fields=['price', 'id']
        # 过滤namt或author
        search_fields = ['name','author']
    

    image-20220407233105436

    image-20220407233125935

    3. 分页

    接口中也只有查询所有用到了分页。

    默认的三种分页方法

    3.1 方法一:基本分页PageNumberPagination

    基本分页,按照页码数,每页显示多少条

    单独创建一个文件专门用来分页:page.py

    # 继承 PageNumberPagination,然后重写四个属性
    from rest_framework.pagination import PageNumberPagination
    class  commPageNumberPagination(PageNumberPagination):
        page_size= 3 # 默认每页显示的条数
        page_query_param = 'page' # 查询条件为page, 如:?page=3
        page_size_query_param ='size' # 每页显示的条数的查询条件 ?page=3&size=9 查询第三页,第三页显示9条
        max_page_size = 5 # 每页最多显示几条, ?page=3&size=9,最终还是显示5条
    
    

    views.py

    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    
    from 应用名.models import Book  # 数据库
    from 应用名.serializer import BookSerializer # 序列化器
    
    from 应用名.page import commPageNumberPagination
    class BookView(GenericViewSet, ListModelMixin):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        pagination_class = commPageNumberPagination
    
    

    路由不变,默认访问:

    http://127.0.0.1:8000/books/

    可看到默认显示3条,

    image-20220408011717608

    访问第一页,每页显示9条。

    http://127.0.0.1:8000/books/?page=1&size=9

    由于设置了最多显示5条,所以虽然设置了要显示9条,但最多也是显示5条

    image-20220408011829555

    3.2 方法二:偏移分页 LimitOffsetPagination

    page.py

    from rest_framework.pagination import LimitOffsetPagination
    
    class commLimitOffsetPagination(LimitOffsetPagination):
        default_limit = 3  # 默认一页获取条数 3  条
        limit_query_param = 'limit'  # ?limit=3  获取三条,如果不传,就用上面的默认两条
        offset_query_param = 'offset'  #  ?limit=3&offset=2  从第2条开始,获取3条    ?offset=3:从第三条开始,获取2条
        max_limit = 4 # 最大显示条数 4 条
    
    

    views.py

    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    
    from 应用名.models import Book  # 数据库
    from 应用名.serializer import BookSerializer # 序列化器
    
    from 应用名.page import commLimitOffsetPagination
    
    class BookView(GenericViewSet, ListModelMixin):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        
        pagination_class = commLimitOffsetPagination
    
    
    
    

    访问:

    http://127.0.0.1:8000/books/

    image-20220408012638592

    从第二条开始,每页显示三条

    image-20220408012830050

    3.3 方法三 游标分页 CursorPagination

    page.py

    from rest_framework.pagination import CursorPagination
    
    class commCursorPagination(CursorPagination):
        page_size = 3  # 每页显示2条
        cursor_query_param = 'cursor'   # 查询条件  ?cursor=sdafdase
        ordering = 'id' # 排序规则,使用id排序
    
    

    views.py

    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin
    
    from 应用名.models import Book  # 数据库
    from 应用名.serializer import BookSerializer # 序列化器
    from authenticated.page import commCursorPagination
    
    class BookView(GenericViewSet, ListModelMixin):
    
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        pagination_class = commCursorPagination
    

    访问:

    http://127.0.0.1:8000/books/

    image-20220408013435703

    3.4 三种分页总结

    使用这三种分页视图类上,必须继承GenericAPIView
    
    前面两种可以从中间位置获取某一页,但是游标分页方式只能上一页和下一页
    
    前面两种在获取某一页的时候,都需要从开始过滤到要取的页面数的数据
    
    游标分页方式,先排序,内部维护了一个游标,游标只能选择往前走或往后走,在取某一页的时候,不需要过滤之前的数据,只能选择上一页和下一页,不能指定某一页,但是速度快,适合大数据量的分页,在大数据量和app分页时,下拉加载下一页,不需要指定跳转到第几页
    
    
    

    4. 异常处理

    DRF中捕获了全局异常,在执行三大认证,视图类的方法时候,如果出了异常,会被全局异常捕获。

    如果自己要自己处理异常,则需要考虑:统一返回格式,无论是否异常,返回的格式统一 ,记录日志(好排查)

    异常:
    {code:999,msg:服务器异常,请联系系统管理员}
    成功:
    {code:100,msg:成功,data:[{},{}]}
      
    

    4.1 自己处理异常

    写一个视图函数

    views.py

    from rest_framework.views import APIView
    from rest_framework.response import Response
    class TestView(APIView):
        def get(self,request):
            # 第一:程序出错
            l=[1,2,3]
            print(l[99])
    
            return Response('ok')
    
    

    配置路由

    urls.py

    from django.contrib import admin
    from django.urls import path,include
    from authenticated import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('test/', views.TestView.as_view()),
    ]
    
    

    使用自带的异常处理时:

    http://127.0.0.1:8000/test/

    image-20220408015711905

    自己处理异常

    第一步:创建一个专门处理的文件,里面写处理异常的代码。

    excepotion.py

    from rest_framework.views import exception_handler  # 默认没有配置,出了异常会走它
    from rest_framework.response import Response
    
    def common_exception_handler(exc, context):
        # 第一步,先执行原来的exception_handler
        # 第一种情况,返回Response对象,这表示已经处理了异常,它只处理APIExcepiton的异常,第二种情况,返回None,表示没有处理
        res = exception_handler(exc, context)
        if not res:
            # 执行这里就说明res为None,它没有处理异常
            res = Response(data={'code': 1001, 'msg': str(exc)})
            return res
        return res
    
        # 注意:咱们在这里,可以记录日志---》只要走到这,说明程序报错了,记录日志,以后查日志---》尽量详细
        # 出错时间,错误原因,哪个视图类出了错,什么请求地址,什么请求方式出了错
        request = context.get('request')  # 这个request是当次请求的request对象
        view = context.get('view')  # 这个viewt是当次执行的视图类对象
        print('错误原因:%s,错误视图类:%s,请求地址:%s,请求方式:%s' % (str(exc), str(view), request.path, request.method))
        return res
      
      
    
        
    ### 以后再出异常,都会走这个函数,后期需要记录日志,统一了返回格式
    

    第二步:把函数配置在配置文件中settings.py

    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER':  'authenticated.exceptions.common_exception_handler',# 再出异常,会执行这个函数
    }
    # authenticated 为应用名
    

    第三步:测试

    写视图类故意有程序错误:

    views.py

    from rest_framework.views import APIView
    from rest_framework.response import Response
    class TestView(APIView):
        def get(self,request):
            # 第一:程序出错
            l=[1,2,3]
            print(l[99])
    
            return Response('ok')
    

    访问:http://127.0.0.1:8000/test/

    image-20220408021018176

    写视图类主动拋异常:

    from rest_framework.views import APIView
    from rest_framework.response import Response
    class TestView(APIView):
        def get(self,request):
    		raise Exception('程序异常,请联系管理员')
    
            return Response('ok')
    

    image-20220408021337600

    写视图类主动拋APIException异常

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.exceptions import APIException
    class TestView(APIView):
        def get(self,request):
    		raise APIException('APIException异常')
    
            return Response('ok')
    

    访问发现这个里面就没有code,因为这是一个APIException异常,DRF会捕捉到。

    image-20220408021509552

    如果APIException也想自己处理:

    excepotion.py

    from rest_framework.views import exception_handler  # 默认没有配置,出了异常会走它
    from rest_framework.response import Response
    
    def common_exception_handler(exc, context):
        # 第一步,先执行原来的exception_handler
        # 第一种情况,返回Response对象,这表示已经处理了异常,它只处理APIExcepiton的异常,第二种情况,返回None,表示没有处理
        res = exception_handler(exc, context)
        if not res:
            # 执行这里就说明res为None,它没有处理异常
            res = Response(data={'code': 1001, 'msg': str(exc)})
            return res
            # 如果执行到这说明res内容,返回的是Response对象, res.data.get('detail', '请联系管理员')表示如果detail里面有内容,则用途detail里面的,没有则使用'请联系管理员'
        res = Response(data={'code': 1002, 'msg': res.data.get('detail', '请联系管理员')})
        return res
    

    image-20220408022515944

    没用设置则显示自己处理时写的:

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.exceptions import APIException
    class TestView(APIView):
        def get(self,request):
    		raise APIException()
    
            return Response('ok')
    

    image-20220408022552389

    4.2 出现异常记录日志

    excepotion.py

    from rest_framework.views import exception_handler  # 默认没有配置,出了异常会走它
    from rest_framework.response import Response
    
    
    def common_exception_handler(exc, context):
        # 第一步,先执行原来的exception_handler
        # 第一种情况,返回Response对象,这表示已经处理了异常,它只处理APIExcepiton的异常,第二种情况,返回None,表示没有处理
        res = exception_handler(exc, context)
        if not res:
            # 执行这里就说明res为None,它没有处理异常
            res = Response(data={'code': 1001, 'msg': str(exc)})
            return res
        
        # 如果执行到这说明res内容,返回的是Response对象, res.data.get('detail', '请联系管理员')表示如果detail里面有内容,则用途detail里面的,没有则使用'请联系管理员'
        res = Response(data={'code': 1002, 'msg': res.data.get('detail', '请联系管理员')})
        
        # 记录日志
        request = context.get('request')  # 这个request是当次请求的request对象
        view = context.get('view')  # 这个viewt是当次执行的视图类对象
        print('错误原因:%s,错误视图类:%s,请求地址:%s,请求方式:%s' % (str(exc), str(view), request.path, request.method))
        return res
    
    

    访问时后台会把错误信息打印出来:

    错误原因:服务器出现了错误。,错误视图类:<authenticated.views.TestView object at 0x000002A296C92560>,请求地址:/test/,请求方式:GET
    

    这里是吧错误信息打印出来了,正确做法是把它记录到一个日志文件里面,具体的可以使用python的日志模块logging

  • 相关阅读:
    Redis的内存淘汰策略分析
    玩转微服务-GateWay
    win7系统的两种硬盘格式mbr和gpt怎么选择?
    SpringSecurity - 启动流程分析(六)
    ESP8266 做简单的仪器
    jupyter notebook内核启动报错:ImportError: DLL load failed while importing _device
    大厂FPGA的面试题
    JAVA Stream流
    docker 部署 mysql8.0.30
    智慧城市新篇章:数字孪生的力量与未来
  • 原文地址:https://www.cnblogs.com/hans-python/p/16251283.html