過濾、排序、分頁、異常處理

上期內容回顧

# 繼承一個父類,父類中有方法,在子類中重寫方法

# 鴨子類型:
	不需要顯示繼承一個類,只要多個類中有同樣的屬性或方法,我們把它們稱之為一種類,python,go
    
# 非鴨子類類型語言:
	如果要屬於同一類,必須顯示的繼承某個基類,這樣才屬於基類這個類型,java


# python語言建議使用鴨子類型(約定),但在實際開發中,我們經常不使用鴨子類型這種特性,出錯概率低

# 實際編碼:
	要麼認為約定必須有哪些方法(符合鴨子類型),可控性低;
	要麼強制約定有哪些方法(abc模組,使用拋異常)

# java中:
	重寫:重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 返回值和形參都不能改變
    
# java中:
	重載:是在一個類裡面,方法名字相同,而參數不同。返回類型可以相同也可以不同


# 認證
	寫一個類,繼承BaseAuthentication,重寫authenticate,在方法中校驗,如果登錄了,返回兩個值,如果沒登陸,拋出異常
	全局使用
	局部使用
	局部禁用

# 許可權
	寫一個類,繼承BasePermission,重寫has_permission方法,在方法中判斷,如果有許可權,返回True,如果沒有許可權,返回false
	全局使用
	局部使用
	局部禁用

# 頻率
	寫一個類,繼承SimpleRateThrottle
	重寫get_cache_key,返回什麼就以什麼做頻率限制
	重寫類屬性scope = 'minute_3',在配置文件中配置:'DEFAULT_THROTTLE_RATES': {'minute_3': '3/m'}
	全局使用
	局部使用
	局部禁用

今日內容概要

  • 過濾
  • 排序
  • 分頁
  • 異常處理

內容詳細

1、過濾

# 5個介面中,只有獲取所有 需要過濾,其他都不需要

# 過濾有多種:
	內置的過濾類
	第三方過濾類
	自定義過濾類

# 1、內置的過濾類
	### 第一步:導入
from rest_framework.filters import SearchFilter

	### 第二步:在視圖類中寫
# 在視圖類中
# 必須繼承GenericAPIView,才有這個類屬性
filter_backends = [SearchFilter, ]
# 需要配合一個類屬性,可以按name過濾
search_fields = ['name', 'author']

	### 第三步:搜索的時候,模糊搜索
//127.0.0.1:8000/books/?search=火
//127.0.0.1:8000/books/?search=田  # 書名或者author中帶田就能搜到
        
        
# 2、第三方過濾類
	###第一步:安裝模組
pip3 install django-filter

	### 第二步:註冊
INSTALLED_APPS = [
    'django_filters',
]

	### 第三步:導入過濾類
from django_filters.rest_framework import DjangoFilterBackend

	### 第四步:在視圖類中使用
# django_filters和rest_framework都需要去配置文件中註冊
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 必須繼承GenericAPIView,才有這個類屬性
    filter_backends = [DjangoFilterBackend, ]

    # 需要配合一個類屬性,
    filter_fields = ['name', 'author']  # 書名或者author中同時過濾

	### 第五步:查詢方式
//127.0.0.1:8000/books/?name=火影忍者
//127.0.0.1:8000/books/?name=海賊王&author=尾田  # and條件
//127.0.0.1:8000/books/?author=尾田
        
        
# 3、自定義過濾類
	### 第一步:寫一個類,繼承BaseFilterBackend 基類,重寫filter_queryset方法, 返回qs對象,是過濾後的對象
from rest_framework.filters import BaseFilterBackend

class BookNameFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        query = request.query_params.get('name')
        if query:
            queryset = queryset.filter(name__contains=query)
        return queryset
    
	### 第二步:在視圖類中使用
from .filter import BookNameFilter

class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 必須繼承GenericAPIView,才有這個類屬性
    filter_backends = [BookNameFilter, ]
    # 自定義的過濾類完成了過濾,不需要寫類屬性了
    
	### 第三步:查詢方式
//127.0.0.1:8000/books/?name=忍  # 模糊查詢 也可自己定義查詢條件
        
"""
### 源碼分析: GenericAPIView----》查詢所有,調用了list---》self.filter_queryset(self.get_queryset())----》查看GenericAPIView的filter_queryset方法:

    def filter_queryset(self, queryset):
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset
"""

image

image

image

數據準備:models.py:

# 創建一個Book表 添加兩條測試數據 遷移資料庫

from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    author = models.CharField(max_length=32)

新建:serializer.py:

from .models import Book
from rest_framework import serializers


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

新建 filter.py:

from rest_framework.filters import BaseFilterBackend


class BookNameFilter(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 .models import Book
from .serializer import BookSerializer
from rest_framework.generics import ListAPIView
from rest_framework.viewsets import ViewSetMixin, GenericViewSet
from rest_framework.filters import SearchFilter
from rest_framework.mixins import ListModelMixin


# 過濾:
### 第一種:使用內置過濾類
# class BookView(ViewSetMixin, ListAPIView):
# class BookView(GenericViewSet, ListModelMixin):  # 同上 必須繼承自動生成路由的類
#     queryset = Book.objects.all()
#     serializer_class = BookSerializer
#
#     # 必須繼承GenericAPIView,才有這個類屬性
#     filter_backends = [SearchFilter, ]
#
#     # 需要配合一個類屬性,
#     search_fields = ['name', 'author']  # 書名或者author中同時過濾


### 第二種:使用第三過濾類
from django_filters.rest_framework import DjangoFilterBackend

# django_filters和rest_framework都需要去配置文件中註冊
# class BookView(GenericViewSet, ListModelMixin):
#     queryset = Book.objects.all()
#     serializer_class = BookSerializer
#
#     # 必須繼承GenericAPIView,才有這個類屬性
#     filter_backends = [DjangoFilterBackend, ]
#
#     # 需要配合一個類屬性,
#     filter_fields = ['name', 'author']  # 書名或者author中同時過濾



### 第三種:使用自定義過濾類
from .filter import BookNameFilter

class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 必須繼承GenericAPIView,才有這個類屬性
    filter_backends = [BookNameFilter, ]
    # 自定義的過濾類完成了過濾,不需要寫類屬性了

路由修改 urls.py:

from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from app01 import views

router = SimpleRouter()
router.register('books', views.BookView, 'books')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls)),
]

image

2、排序

# 按照id排序,按照年齡排序,按照價格排序...
# 使用內置的即可
	### 第一步:導入內置排序類
from rest_framework.filters import OrderingFilter

	### 第二步:在視圖類中配置(必須繼承GenericAPIView)
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter, ]
    ordering_fields = ['price', 'id']  # 先按照price排序 如果price一樣 那麼按id繼續排序
    
	### 第三步:查詢
//127.0.0.1:8000/books/?ordering=price  # 按price正序排    
//127.0.0.1:8000/books/?ordering=-price  # 倒序排
//127.0.0.1:8000/books/?ordering=-price,-id # 先按價格倒序排,如果價格一樣,再按id倒序排
        
        
"""
### 過濾和排序可以同時用:
    因為他們本質是for循環一個個執行,所有優先使用過濾,再使用排序

    filter_backends = [SearchFilter, OrderingFilter]
    ordering_fields=['price', 'id']
    search_fields=['name', 'author']
"""

image

3、分頁

# 5個介面中,只有查詢所有,涉及到分頁

# pc端是下一個頁,下一頁的形式,如果在app,小程式中展現形式是下拉載入下一個

# 默認提供了,三種分頁方式:
	PageNumberPagination
	LimitOffsetPagination
	CursorPagination
    
    
    
# 1、PageNumberPagination---基本分頁 按照頁碼數,每頁顯示多少條
	### 第一步:寫一個類,繼承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條
    
	### 第二步:配置在視圖類上,必須繼承GenericAPIView才有這個類屬性
    pagination_class = PageNumberPagination
    
	# 第三步:查詢方式
//127.0.0.1:8000/books/?page=2  # 查詢第二頁 每頁顯示默認的三條
//127.0.0.1:8000/books/?page=2&size=4  # 查詢第二頁 每頁顯示四條
    
    
    
# 2、LimitOffsetPagination---偏移分頁
	### 第一步:寫一個類,繼承LimitOffsetPagination,重寫四個類屬性
    
    default_limit = 2  # 默認一頁獲取條數
    limit_query_param = 'limit'  # ?limit=3  獲取三條,如果不傳,就用上面的默認兩條
    offset_query_param = 'offset'  # ?limit=3&offset=2  從第2條開始,獲取3條    ?offset=3:從第三條開始,獲取默認2條
    max_limit = 5  # 最大顯示條數 5 條
    
	### 第二步:配置在視圖類上,必須繼承GenericAPIView才有這個類屬性
    pagination_class = CommonLimitOffsetPagination
    
	### 第三步:查詢方式
//127.0.0.1:8000/books/?limit=2&offset=3  # 從第三條開始獲取兩條

    
    
# 3、CursorPagination---游標分頁
	### 第一步:寫一個類,繼承CursorPagination,重寫四個類屬性

    page_size = 2  # 每頁顯示2條
    cursor_query_param = 'cursor'  # 查詢條件  ?cursor=sdafdase
    ordering = 'id'  # 排序規則,使用id排序
    
	### 第二步:配置在視圖類上,必須繼承GenericAPIView才有這個類屬性
    pagination_class = CommonCursorPagination
    
	### 第三步:查詢方式
//127.0.0.1:8000/books/?cursor=cD02

image

image

image

新建 page.py:

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination


class CommonPageNumberPagination(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條
    
    
class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 2  # 默認一頁獲取條數
    limit_query_param = 'limit'  # ?limit=3  獲取三條,如果不傳,就用上面的默認兩條
    offset_query_param = 'offset'  # ?limit=3&offset=2  從第2條開始,獲取3條    ?offset=3:從第三條開始,獲取默認2條
    max_limit = 5  # 最大顯示條數 5 條
    
    
class CommonCursorPagination(CursorPagination):
    page_size = 2  # 每頁顯示2條
    cursor_query_param = 'cursor'  # 查詢條件  ?cursor=sdafdase
    ordering = 'id'  # 排序規則,使用id排序

視圖類 views.py中:

"""
分頁
"""

# 分頁種類一:PageNumberPagination  基本分頁  用的最多
from .page import CommonPageNumberPagination as PageNumberPagination
from .page import CommonLimitOffsetPagination, CommonCursorPagination

# 分頁一 二:
# class BookView(GenericViewSet, ListModelMixin):
#     queryset = Book.objects.all()
#     serializer_class = BookSerializer
#
#     filter_backends = [OrderingFilter, SearchFilter]
#     ordering_fields = ['price', 'id']  # 排序
#     search_fields = ['name', 'author']  # 過濾

    # 不要放在列表中了,分頁只能按一種方式,不能按多種方式
    # pagination_class = PageNumberPagination

    # pagination_class = CommonLimitOffsetPagination


# 分頁三:
"""
跟上面兩種的區別:上面兩種,可以從中間位置獲取某一頁,Cursor方式只能上一頁和下一頁

上面這兩種在獲取某一頁的時候,都需要從開始過濾到要取的頁面數的數據

下面這種方式,先排序,內部維護了一個游標,游標只能選擇往前走或往後走,在取某一頁的時候,不需要過濾之前的數據
這種分頁方式特殊,只能選擇上一頁和下一頁,不能指定某一頁,但是速度快,適合大數據量的分頁
    適用於:大數據量和app分頁---》下拉載入下一頁,不需要指定跳轉到第幾頁
"""
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    filter_backends = [SearchFilter]
    search_fields = ['name', 'author']  # 過濾

    pagination_class = CommonCursorPagination  # 驗證此類分頁時 視圖類中不可以帶有排序功能 要注釋掉

4、異常處理

# 之前讀APIViwe源碼的時候,捕獲了全局異常
	在執行三大認證,視圖類的方法時候,如果出了異常,會被全局異常捕獲

# 統一返回格式,無論是否異常,返回的格式統一  ,記錄日誌(好排查)
	{code:999,msg:伺服器異常,請聯繫系統管理員}
	{code:100,msg:成功,data:[{},{}]}
    
    
### 第一步:寫一個函數 新建exception.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 res:  # exception_handler 已經處理了,暫時先不處理了
        # 998:APIExcepiton
        # res=Response(data={'code':998,'msg':'伺服器異常,請聯繫系統管理員'})
        res = Response(data={'code': 998, 'msg': res.data.get('detail', '伺服器異常,請聯繫系統管理員')})
    else:
        # 999:出了APIExcepiton外的異常
        # res=Response(data={'code':999,'msg':'伺服器異常,請聯繫系統管理員'})
        res = Response(data={'code': 999, 'msg': str(exc)})

    # 注意:咱們在這裡,可以記錄日誌---》只要走到這,說明程式報錯了,記錄日誌,以後查日誌---》盡量詳細
    # 出錯時間,錯誤原因,哪個視圖類出了錯,什麼請求地址,什麼請求方式出了錯
    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



### 第二步:把函數配置在配置文件中:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exception.common_exception_handler'  # 再出異常,會執行這個函數
}

視圖類 views.py:

### 全局異常,繼承APIView及其子類就可以
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):
        ## 第一:程式出錯
        # l=[1,2,3]
        # print(l[99])

        ## 第二:主動拋異常
        raise Exception('我錯了')

        # 第三:主動拋 APIException的異常
        # raise APIException('APIException我錯了')

        return Response('ok')

配置測試路由 urls.py:

    path('test/', views.TestView.as_view()),

image