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=華
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=西遊記, 如果像之前直接寫西則匹配不出來
http://127.0.0.1:8000/books/?name=活著&author=餘華 # 這裡面是and的關係,name是活著並且author是餘華的
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=西 # 模糊匹配 ,自己定義的
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 # 正序
http://127.0.0.1:8000/books/?ordering=-price # 倒序, 使用減號(-)為倒序
可以把過濾和排序放在一塊
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']
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條,
訪問第一頁,每頁顯示9條。
http://127.0.0.1:8000/books/?page=1&size=9
由於設定了最多顯示5條,所以雖然設定了要顯示9條,但最多也是顯示5條
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/
從第二條開始,每頁顯示三條
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/
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/
自己處理異常
第一步:建立一個專門處理的檔案,裡面寫處理異常的程式碼。
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/
寫檢視類主動拋異常:
from rest_framework.views import APIView
from rest_framework.response import Response
class TestView(APIView):
def get(self,request):
raise Exception('程式異常,請聯絡管理員')
return Response('ok')
寫檢視類主動拋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會捕捉到。
如果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
沒用設定則顯示自己處理時寫的:
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')
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