django-rest-framework 基礎四 過濾、排序、分頁、異常處理

Hans_Wang發表於2022-05-11

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

相關文章