DRF之過濾排序分頁異常處理

Yang` 發表於 2021-07-11

一、過濾

對於列表資料要通過欄位來進行過濾,就需要新增 django-filter 模組

使用方法:

# 1、註冊,在app中註冊 settings.py
INSTALLED_APPS = [
    'django_filters',
]

# 2、settings.py 配置
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
    )
}

# 3、在檢視中新增filter_fields屬性,指定過濾的欄位
class BooksView(ModelViewSet):
    queryset = Books.objects.all()
    serializer_class = BooksSerializer

    filter_fields = ('title',)	# 配置可以按照哪個欄位來過濾
    
# http://127.0.0.1:8000/books/?title=紅樓夢

二、排序

排序可以使用 Rest Framework 提供的 OrderingFilter 來快速指明資料按指定欄位進行排序

使用方法:

# 1、首先檢視中設定filter_backends=[OrderingFilter]
# 2、然後再檢視中新增 ordering_fields 屬性,指定排序欄位

from rest_framework.filters import OrderingFilter
class BooksView(ModelViewSet):
    queryset = Books.objects.all()
    serializer_class = BooksSerializer

    filter_backends = [OrderingFilter]	# 第一步
    ordering_fields = ['price','id']	# 第二步
    
# http://127.0.0.1:8000/books/?ordering=id	通過ordering檢視有無指明排序的欄位,並通過欄位來排序
# -id 表示針對id欄位進行倒序排序
# id  表示針對id欄位進行升序排序

三、分頁

首先 Rest Framework 提供了三種分頁方式,並且

  • PageNumberPagination
  • LimitOffsetPagination
  • CursorPagination

使用方式一:可以直接繼承使用,但是配置引數需要在settings.py裡面配置

使用方式二:通過子類繼承父類分頁器使用,直接在子類中修改父類的引數(推薦)

PageNumberPagination

子類中的屬性:

  • page_size:每頁數目
  • page_query_param:前端傳送的頁數關鍵字,預設“page”
  • page_size_query_param:前端傳送每頁數目關鍵字名,預設None
  • max_page_size:前端最多能設定的每頁數量

如何使用:

from rest_framework.generics import ListAPIView
from rest_framework.pagination import PageNumberPagination
from app01.models import Books
from app01.ser import BooksSerializer

# 第一步:
class BookPageNumberPagination(PageNumberPagination):
    page_size = 3         # 每頁條數
    page_query_param = 'page'   # 查詢第幾頁的key
    page_size_query_param = 'size'  # 每一頁顯示的條數
    max_page_size = 5   # 每頁最大條數


class BookView(ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BooksSerializer
    
    # 第二步:分頁配置
    pagination_class = BookPageNumberPagination
    
    
# url:http://127.0.0.1:8000/books/?page=1&size=5	查詢第1頁,一共顯示五條資料

LimitOffsetPagination

子類中的屬性:

  • default_limit:預設限制,預設值與PAGE_SIZE設定一致
  • limit_query_param:limit引數名,預設’limit’
  • offset_query_param:offset引數名,預設’offset’
  • max_limit:最大limit限制,預設None

如何使用:

from rest_framework.generics import ListAPIView
from rest_framework.pagination import LimitOffsetPagination
from app01.models import Books
from app01.ser import BooksSerializer

# 第一步:
class BookLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 3           # 每頁條數
    limit_query_param = 'limit'     # 往後拿幾條
    offset_query_param = 'offset'   # 從第幾條往後拿幾條的標杆
    max_limit = 5       # 每頁最大拿幾條


class BookView(ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BooksSerializer
    
    # 第二步:分頁配置
    pagination_class = BookLimitOffsetPagination
    
    
# url:http://127.0.0.1:8000/books/?limit=3&offset=4 	從第三條開始往後拿4條資料

CursorPagination

子類中的屬性:

  • cursor_query_param:預設查詢欄位,不需要修改
  • page_size:每頁數目
  • ordering:按什麼排序,需要指定

如何使用:

CursorPagination的查詢速度快,但是卻不能定位到第幾頁這樣查,要麼往前查,要麼往後查。

from rest_framework.generics import ListAPIView
from rest_framework.pagination import CursorPagination
from app01.models import Books
from app01.ser import BooksSerializer

# 第一步:
class BookCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'   # 每頁查詢的key
    page_size = 3       # 每頁顯示條數
    ordering = 'id'    # 排序欄位


class BookView(ListAPIView):
    queryset = Books.objects.all()
    serializer_class = BooksSerializer
    
    # 第二步:分頁配置
    pagination_class = BookCursorPagination
    
    
# url:http://127.0.0.1:8000/books/?cursor=cD0z

繼承APIView使用方法

像方式一,方式二這樣使用的話,就要檢視類是繼承 ListAPIView 然後直接配置就可以了

但是如果檢視類繼承的是 GenericAPIView 或者 APIView 的話,就需要用另一種方式了:

使用方法:

# 1、定義一個分頁器
class BookPageNumberPagination(PageNumberPagination):
    page_size = 3         # 每頁條數
    page_query_param = 'page'   # 查詢第幾頁的key
    page_size_query_param = 'size'  # 每一頁顯示的條數
    max_page_size = 5   # 每頁最大條數


class BookView(APIView):

    def get(self, request, *args, **kwargs):
        book_list = Books.objects.all()

        # 2、例項化得到一個分頁器物件
        page_obj = BookPageNumberPagination()
        
        # 3、呼叫paginate_queryset返回每一頁的資料
        book_list = page_obj.paginate_queryset(book_list, request, view=self)

        # 4、獲取上一頁下一頁連結
        next_url = page_obj.get_next_link()
        previous_url = page_obj.get_previous_link()

        # 5、序列化
        book_ser = BooksSerializer(book_list, many=True)
        
        # 6、加入響應資訊中
        data = {'next_url': next_url, 'previous_url': previous_url, 'data': book_ser.data}
        return Response(data=data)

四、異常處理

異常處理主要是用來統一介面返回

原始碼分析

異常處理在APIView的api_settings已經配置好了

'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',

def exception_handler(exc, context):
    # 先判斷是不是404
    if isinstance(exc, Http404):	# exc是異常資訊的異常物件
        exc = exceptions.NotFound()
    # 然後判斷是不是許可權的問題
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()		# 比如許可權問題會返回一個字典
        
	# 在判斷你丟擲的是不是API的異常——>認證許可權這些都是繼承了API的異常
    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        if isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            data = {'detail': exc.detail}

        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)
    # 只要是上面這些異常都做了處理
    

    return None		# 意味有些異常它沒有處理,就會交給Django自己處理

如何處理

因為dir有些異常它不做處理,Django處理的又不符合我們的標準,所以就要寫一個統一的異常類來替換掉它,把所有情況都處理,只要前端出異常看到的都是固定的東西。

如何寫:

重寫一個類,和它基本上差不多,配置的時候在 settings.py 裡面全域性配置

#  app01_auth.py
from rest_framework.views import exception_handler
from rest_framework.response import Response

def app01_exception_handler(exc, context):
    response = exception_handler(exc, context)  # 原來的drf處理的我們也需要
    # 兩種情況,一個是None,drf沒有處理,Django處理了,但是處理的不符合需求
    # response物件,drf處理了我們需要的


    if not response:
        # 如果沒處理,那麼我們自己處理
        return Response(data={'status': 400, 'error': str(exc)}, status=400)
    else:
        # drf 處理了,那麼取出它處理的資訊,重新處理一下
        return Response(data={'status': 400, 'error': response.data.get('detail')}, status=400)

然後再全域性配置一下:settings.py

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.app_auth.app01_exception_handler',
}

五、封裝Response物件

class APIResponse(Response):
    def __init__(self,code=100,msg='成功',data=None,status=None,headers=None,**kwargs):
        
        dic = {'code': code, 'msg': msg}
        
        if  data:
            dic = {'code': code, 'msg': msg,'data':data}
            
        dic.update(kwargs)
        
        super().__init__(data=dic, status=status,headers=headers)
        
        
# 使用
return APIResponse(data={"name":'xiaoyang'},token='dsafsdfa',aa='dsafdsfdee')
return APIResponse(data={"name":'xiaoyang'})
return APIResponse(code='101',msg='錯誤',data={"name":'xiaoyang'},token='dsafsdfa',aa='dsafdsfdee',header={})