DRF 過濾排序分頁異常處理

HammerZe發表於2022-04-07

DRF 過濾排序分頁異常處理

DRF-認證許可權頻率

過濾

涉及到查詢資料的介面才需要過濾功能

DRF過濾使用種類:

  1. 內建過濾類
  2. 第三方
  3. 自定義

內建過濾類

匯入from rest_framework.filters import SearchFilter

前提條件:使用內建過濾類,檢視類需要繼承GenericAPIView才能使用

步驟

  1. 檢視類內filter_backends中使用SearchFilter
  2. 類屬性search_fields指定過濾的欄位

使用連結?search=欄位,且支援模糊查詢

from rest_framework.generics import ListAPIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.filters import SearchFilter
from .models import Book
from .serializer import BookSerializer

# 只有查詢介面才需要過濾,內建過濾類的使用需要檢視類繼承GenericAPIView才能使用
class BookView(ViewSetMixin, ListAPIView):
    '''
    內建過濾類:1、filter_backends中使用SearchFilter
              2、類屬性search_fields指定過濾的欄位
              3、連結?search=欄位,且支援模糊查詢
    '''
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter,]
	# 過濾單個欄位
    search_fields = ['title',]

注意:連結過濾的欄位必須是search

# 過濾多個欄位:書名和作者名
'''
比如書名:Python  作者名:Pink,那麼過濾search=P就都會過濾出來
'''
 search_fields = ['title','author']
    
# http://127.0.0.1:8000/books/?search=H

總結

  • 內建過濾類的使用,模糊查詢會將包含過濾欄位的資料都過濾出來,前提是在search_fields列表內指定的欄位;
  • 內建過濾的特點是模糊查詢
  • 過濾欄位引數為search

image

第三方過濾

對於列表資料可能需要根據欄位進行過濾,我們可以通過新增django-fitlter擴充套件來增強支援

安裝pip install django-filter

匯入from django_filters.rest_framework import DjangoFilterBackend


在配置檔案中增加過濾後端的設定:

INSTALLED_APPS = [
    ...
    'django_filters',  # 需要註冊應用,
]

在檢視中新增filter_fields屬性,指定可以過濾的欄位

from django_filters.rest_framework import DjangoFilterBackend
# 第三方過濾類
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [DjangoFilterBackend,]
    filter_fields = ['title','author']

http://127.0.0.1:8000/books/?title=Java  # 單個欄位過濾
http://127.0.0.1:8000/books/?title=Java&author=HammerZe  # 多個欄位過濾

總結

  • 第三方過濾類在filter_backends欄位中寫,filter_fields欄位指定過濾的欄位
  • 第三方過濾類不支援模糊查詢,是精準匹配
  • 第三方過濾類的使用,檢視類也必須繼承GenericAPIView才能使用
  • 在連結內通過&來表示和的關係

image

image

自定義過濾類

步驟

  1. 寫一個類繼承BaseFilterBackend,重寫filter_queryset方法,返回queryset物件,qs物件是過濾後的
  2. 檢視類中使用,且不需要重寫類屬性去指定過濾的欄位
  3. 過濾使用,支援模糊查詢(自己定製過濾方式),通過filter方法來指定過濾規則

自定義過濾類

'''filter.py'''
from django.db.models import Q
from  rest_framework.filters import BaseFilterBackend

# 繼承BaseFilterBackend
class MyFilter(BaseFilterBackend):
    # 重寫filter_queryset方法
    def filter_queryset(self, request, queryset, view):
        # 獲取過濾引數
        qs_title = request.query_params.get('title')
        qs_author = request.query_params.get('author')
        # title__contains:精確大小寫查詢,SQL中-->like BINARY
        # 利用Q查詢構造或關係
        if qs_title:
            queryset = queryset.filter(title__contains=qs_title)
        elif qs_author:
            queryset = queryset.filter(author__contains=qs_author)
        elif qs_title or qs_author:
            queryset = queryset.filter(Q(title__contains=qs_title)|Q(author__contains=qs_author))
        return queryset

檢視

# 自定製過濾類
from .filter import MyFilter
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [MyFilter,]

    # 這裡不需要寫類屬性指定欄位了,因為自定義過濾類,過濾欄位了

原始碼分析

我們知道過濾的前提條件是檢視繼承了GenericAPIView才能使用,那麼在GenericAPIView中的執行流程是什麼?

1、呼叫了GenericAPIView中的filter_queryset方法
2、filter_queryset方法原始碼:
    def filter_queryset(self, queryset):
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset
'''
1.backend是通過遍歷該類的filter_backends列表的得到的,也就是我們指定的過濾類列表,那麼backend就是我們的過濾類
2.通過例項化得到物件來呼叫了類內的filter_queryset返回了過濾後的物件
'''

排序

REST framework提供了OrderingFilter過濾器來幫助我們快速指明資料按照指定欄位進行排序。

匯入from rest_framework.filters import OrderingFilter

步驟

  1. 檢視類中配置,且檢視類必須繼承GenericAPIView
  2. 通過ordering_fields指定要排序的欄位
  3. 排序過濾,-號代表倒序,且必須使用ordering指定排序欄位
'''內建過濾和排序混用'''
from rest_framework.filters import OrderingFilter
from rest_framework.filters import SearchFilter
class BookView(ViewSetMixin, ListAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter,OrderingFilter]
    # 先過濾後排序減少消耗
    search_fields = ['title']
    ordering_fields = ['id','price']

# 排序
http://127.0.0.1:8000/books/?ordering=price   # 價格升序
http://127.0.0.1:8000/books/?ordering=-price  #  價格降序
http://127.0.0.1:8000/books/?ordering=price,id # 價格id升序
http://127.0.0.1:8000/books/?ordering=price,-id # 價格升序id降序
        ····

image

注意

過濾可以和排序同時使用,但是先執行過濾再執行排序,提升了程式碼的效率(先過濾後排序),因為如果先排序,那麼資料庫的數量龐大的話,直接操作了整個資料庫,消耗資源,過濾完成後排序只是針對一小部分資料

分頁

分頁只在查詢所有介面中使用

匯入from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination

分頁有三種分頁方式,如下:

PageNumberPagination,基本分頁

步驟

  1. 自定義類,繼承PageNumberPagination,重寫四個類屬性

    • page_size:設定每頁預設顯示的條數

    • page_query_param:url中的查詢條件,books/?page=2表示第二頁

    • page_size_query_param:每頁顯示多少條的查詢條件,books/?page=2&size=5,表示查詢第二頁,顯示5條

    • max_page_size:設定每頁最多顯示條數,不管查多少條,最大顯示該值限制的條數

  2. 配置在檢視類中,通過pagination_class指定,必須繼承GenericAPIView才有

    pagination_class = PageNumberPagination
    

分頁

from rest_framework.pagination import PageNumberPagination

class BookPagination(PageNumberPagination):
    page_size = 2 # 預設每頁顯示2條
    page_query_param = 'page'  # 查詢條件,eg:page=3
    page_size_query_param = 'size' # 查詢條件引數size=5顯示五條
    max_page_size = 10   # 每頁最大顯示條數

檢視

from rest_framework.filters import OrderingFilter
from rest_framework.filters import SearchFilter
from .page import BookPagination
class BookView(ViewSetMixin, ListAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter,OrderingFilter]
    search_fields = ['title','author']
    ordering_fields = ['id','price']
    pagination_class = BookPagination

# http://127.0.0.1:8000/books/?page=2&size=5

注意pagination_class指定分頁類不需要使用列表

image

LimitOffsetPagination,偏移分頁

步驟

  1. 自定義類,繼承LimitOffsetPagination,重寫四個類屬性
    • default_limit:預設每頁獲取的條數
    • limit_query_param:每頁顯示多少條的查詢條件,比如?limit=3,表示獲取三條,如果不寫預設使用default_limit設定的條數
    • offset_query_param:表示偏移量引數,比如?offset=3表示從第三條開始往後獲取預設的條數
    • max_limit:設定最大顯示條數
  2. 檢視類內配置,pagination_class引數指定,必須繼承GenericAPIView才有

分頁

class MyLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 2  # 預設每頁顯示2條
    limit_query_param = 'limit'  # ?limit=3,查詢出3條
    offset_query_param = 'offset'  # 偏移量,?offset=1,從第一條後開始
    max_limit = 5  # 最大顯示5條

檢視

from rest_framework.filters import OrderingFilter
from rest_framework.filters import SearchFilter
from .page import MyLimitOffsetPagination
class BookView(ViewSetMixin, ListAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter,OrderingFilter]
    # 先過濾後排序減少消耗
    search_fields = ['title','author']
    ordering_fields = ['id','price']
    pagination_class = MyLimitOffsetPagination

# http://127.0.0.1:8000/books/?limit=2&offset=4

image

CursorPagination,遊標分頁

步驟

  1. 自定義類,繼承CursorPagination,重寫三個類屬性
    • page_size:每頁顯示的條數
    • cursor_query_param:查詢條件
    • ordering:排序規則,指定排序欄位
  2. 檢視類內配置,pagination_class引數指定,必須繼承GenericAPIView才有

分頁

class MyCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 2
    ordering = 'id'

檢視

from rest_framework.filters import SearchFilter
from .page import MyCursorPagination
class BookView(ViewSetMixin, ListAPIView):
	queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [SearchFilter]
    search_fields = ['title','author']
    pagination_class = MyCursorPagination
    
'''
注意:因為分頁內指定了排序規則,那麼檢視內如果再指定了排序規則就會報錯
'''

總結

  • 分頁類內指定了排序,檢視內不要寫排序規則,不然報錯

image

分頁總結

  1. 前兩種分頁都可以從中間位置獲取一頁,而最後一個分頁類只能上一頁或下一頁
  2. 前兩種在獲取某一頁的時候,都需要從開始過濾到要取的頁面數的資料,本質是SQL中的limit··,查詢出要跳過的頁數顯示要查的資料,相比第三種慢一點
  3. 第三種方式,本質是先排序,內部維護了一個遊標,遊標只能選擇往前或者往後,在獲取到一頁的資料時,不需要過濾之前的資料,相比前兩種速度較快,適合大資料量的分頁

異常

REST framework提供了異常處理,我們可以自定義異常處理函式,不論正常還是異常,通過定製,我們可以返回我們想要返回的樣子

步驟

  1. 自定義函式
  2. 在配置檔案中配置函式

注意

如果沒有配置自己處理異常的規則,會執行預設的,如下:

from rest_framework import settings

from rest_framework.views import exception_handler

預設配置流程怎麼走?

# 1、 APIView原始碼
# dispatch方法原始碼
except Exception as exc:
     response = self.handle_exception(exc)
# handle_exception方法原始碼
response = exception_handler(exc, context)
# 2、views種的exception_handler方法
def exception_handler(exc, context):
    ···
# 3、 預設配置檔案
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',

自定義異常

原始碼exception_handler方法有兩種情況,if判斷第一種情況是處理了APIException物件的異常返回Reponse物件,第二種情況是處理了其他異常返回了None,這裡我們針對這兩種情況的異常進行定製處理

  • exc:錯誤原因
  • context:字典,包含了當前請求物件鶴檢視類物件

自定義異常處理方法

from rest_framework.views import exception_handler
from rest_framework.response import Response
def myexception_handler(exc, context):
    # 先執行原來的exception_handler幫助我們處理
    res = exception_handler(exc, context)
    if res:
        # res有值代表處理過了APIException物件的異常了,返回的資料再定製
        res = Response(data={'code': 998, 'msg': res.data.get('detail', '伺服器異常,請聯絡系統管理員')})
        # res = Response(data={'code': 998, 'msg': '伺服器異常,請聯絡系統管理員'})
        # res.data.get從響應中獲取原來的處理詳細資訊
    else:
        res = Response(data={'code': 999, 'msg': str(exc)})
        print(exc) # list index out of range
	
   '''模擬日誌處理'''
 	request = context.get('request') # 當次請求的request物件
    view = context.get('view')  # 當次執行的檢視類物件
    print('錯誤原因:%s,錯誤檢視類:%s,請求地址:%s,請求方式:%s' % (str(exc), str(view), request.path, request.method))
    '''結果:
    錯誤原因:list index out of range,錯誤檢視類:<app01.views.TestView object at 0x000001C3B1C7CA58>,請求地址:/test/,請求方式:GET
    '''    
    return res

檢視

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

        # 1、 其他報錯
        l = [1,2,3]
        print(l[100])

        # 2、APIException異常
        # raise APIException('APIException errors!')

        return Response('successfuly!')

配置檔案


REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exception.myexception_handler' # 再出異常,會執行自己定義的函式
}

image

REST framework定義的異常

  • APIException 所有異常的父類
  • ParseError 解析錯誤
  • AuthenticationFailed 認證失敗
  • NotAuthenticated 尚未認證
  • PermissionDenied 許可權決絕
  • NotFound 未找到
  • MethodNotAllowed 請求方式不支援
  • NotAcceptable 要獲取的資料格式不支援
  • Throttled 超過限流次數
  • ValidationError 校驗失敗

相關文章