過濾、排序、分頁、異常處理
上期內容回顧
# 繼承一個父類,父類中有方法,在子類中重寫方法
# 鴨子類型:
不需要顯示繼承一個類,只要多個類中有同樣的屬性或方法,我們把它們稱之為一種類,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
"""
數據準備: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)),
]
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']
"""
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
新建 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()),