django-rest-framework 基礎(chǔ)四 過濾、排序、分頁、異常處理
1. 過濾
在之前所寫的五個接口中,只有獲取所有需要過濾,其他接口都不需要。如在訪問的時候帶參數(shù)過濾出自己想要的數(shù)據(jù)。
http://127.0.0.1:8080/?search=活著
1.1 內(nèi)置過濾類
views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from 應(yīng)用名.models import Book # 數(shù)據(jù)庫
from 應(yīng)用名.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 應(yīng)用名.models import Book # 數(shù)據(jù)庫
from 應(yīng)用名.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']
路由還是原來的配置。
這時訪問是要過濾,關(guān)鍵字不能寫search了,寫search會把全部都打印出來,要寫具體的字段名,而且后面要過濾的內(nèi)容是精準(zhǔn)匹配
http://127.0.0.1:8000/books/?name=西游記, 如果像之前直接寫西則匹配不出來
http://127.0.0.1:8000/books/?name=活著&author=余華 # 這里面是and的關(guān)系,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 應(yīng)用名.models import Book # 數(shù)據(jù)庫
from 應(yīng)用名.serializer import BookSerializer # 序列化器
from 應(yīng)用名.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內(nèi)置的OrderingFilter
類進行排序。
views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from 應(yīng)用名.models import Book # 數(shù)據(jù)庫
from 應(yīng)用名.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 應(yīng)用名.models import Book # 數(shù)據(jù)庫
from 應(yīng)用名.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
基本分頁,按照頁碼數(shù),每頁顯示多少條
單獨創(chuàng)建一個文件專門用來分頁:page.py
# 繼承 PageNumberPagination,然后重寫四個屬性
from rest_framework.pagination import PageNumberPagination
class commPageNumberPagination(PageNumberPagination):
page_size= 3 # 默認每頁顯示的條數(shù)
page_query_param = 'page' # 查詢條件為page, 如:?page=3
page_size_query_param ='size' # 每頁顯示的條數(shù)的查詢條件 ?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 應(yīng)用名.models import Book # 數(shù)據(jù)庫
from 應(yīng)用名.serializer import BookSerializer # 序列化器
from 應(yīng)用名.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
由于設(shè)置了最多顯示5條,所以雖然設(shè)置了要顯示9條,但最多也是顯示5條
3.2 方法二:偏移分頁 LimitOffsetPagination
page.py
from rest_framework.pagination import LimitOffsetPagination
class commLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3 # 默認一頁獲取條數(shù) 3 條
limit_query_param = 'limit' # ?limit=3 獲取三條,如果不傳,就用上面的默認兩條
offset_query_param = 'offset' # ?limit=3&offset=2 從第2條開始,獲取3條 ?offset=3:從第三條開始,獲取2條
max_limit = 4 # 最大顯示條數(shù) 4 條
views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from 應(yīng)用名.models import Book # 數(shù)據(jù)庫
from 應(yīng)用名.serializer import BookSerializer # 序列化器
from 應(yīng)用名.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 方法三 游標(biāo)分頁 CursorPagination
page.py
from rest_framework.pagination import CursorPagination
class commCursorPagination(CursorPagination):
page_size = 3 # 每頁顯示2條
cursor_query_param = 'cursor' # 查詢條件 ?cursor=sdafdase
ordering = 'id' # 排序規(guī)則,使用id排序
views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from 應(yīng)用名.models import Book # 數(shù)據(jù)庫
from 應(yīng)用名.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 三種分頁總結(jié)
使用這三種分頁視圖類上,必須繼承GenericAPIView
前面兩種可以從中間位置獲取某一頁,但是游標(biāo)分頁方式只能上一頁和下一頁
前面兩種在獲取某一頁的時候,都需要從開始過濾到要取的頁面數(shù)的數(shù)據(jù)
游標(biāo)分頁方式,先排序,內(nèi)部維護了一個游標(biāo),游標(biāo)只能選擇往前走或往后走,在取某一頁的時候,不需要過濾之前的數(shù)據(jù),只能選擇上一頁和下一頁,不能指定某一頁,但是速度快,適合大數(shù)據(jù)量的分頁,在大數(shù)據(jù)量和app分頁時,下拉加載下一頁,不需要指定跳轉(zhuǎn)到第幾頁
4. 異常處理
DRF中捕獲了全局異常,在執(zhí)行三大認證,視圖類的方法時候,如果出了異常,會被全局異常捕獲。
如果自己要自己處理異常,則需要考慮:統(tǒng)一返回格式,無論是否異常,返回的格式統(tǒng)一 ,記錄日志(好排查)
異常:
{code:999,msg:服務(wù)器異常,請聯(lián)系系統(tǒng)管理員}
成功:
{code:100,msg:成功,data:[{},{}]}
4.1 自己處理異常
寫一個視圖函數(shù)
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/
自己處理異常
第一步:創(chuàng)建一個專門處理的文件,里面寫處理異常的代碼。
excepotion.py
from rest_framework.views import exception_handler # 默認沒有配置,出了異常會走它
from rest_framework.response import Response
def common_exception_handler(exc, context):
# 第一步,先執(zhí)行原來的exception_handler
# 第一種情況,返回Response對象,這表示已經(jīng)處理了異常,它只處理APIExcepiton的異常,第二種情況,返回None,表示沒有處理
res = exception_handler(exc, context)
if not res:
# 執(zhí)行這里就說明res為None,它沒有處理異常
res = Response(data={'code': 1001, 'msg': str(exc)})
return res
return res
# 注意:咱們在這里,可以記錄日志---》只要走到這,說明程序報錯了,記錄日志,以后查日志---》盡量詳細
# 出錯時間,錯誤原因,哪個視圖類出了錯,什么請求地址,什么請求方式出了錯
request = context.get('request') # 這個request是當(dāng)次請求的request對象
view = context.get('view') # 這個viewt是當(dāng)次執(zhí)行的視圖類對象
print('錯誤原因:%s,錯誤視圖類:%s,請求地址:%s,請求方式:%s' % (str(exc), str(view), request.path, request.method))
return res
### 以后再出異常,都會走這個函數(shù),后期需要記錄日志,統(tǒng)一了返回格式
第二步:把函數(shù)配置在配置文件中settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'authenticated.exceptions.common_exception_handler',# 再出異常,會執(zhí)行這個函數(shù)
}
# authenticated 為應(yīng)用名
第三步:測試
寫視圖類故意有程序錯誤:
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('程序異常,請聯(lián)系管理員')
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')
訪問發(fā)現(xiàn)這個里面就沒有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):
# 第一步,先執(zhí)行原來的exception_handler
# 第一種情況,返回Response對象,這表示已經(jīng)處理了異常,它只處理APIExcepiton的異常,第二種情況,返回None,表示沒有處理
res = exception_handler(exc, context)
if not res:
# 執(zhí)行這里就說明res為None,它沒有處理異常
res = Response(data={'code': 1001, 'msg': str(exc)})
return res
# 如果執(zhí)行到這說明res內(nèi)容,返回的是Response對象, res.data.get('detail', '請聯(lián)系管理員')表示如果detail里面有內(nèi)容,則用途detail里面的,沒有則使用'請聯(lián)系管理員'
res = Response(data={'code': 1002, 'msg': res.data.get('detail', '請聯(lián)系管理員')})
return res
沒用設(shè)置則顯示自己處理時寫的:
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 出現(xiàn)異常記錄日志
excepotion.py
from rest_framework.views import exception_handler # 默認沒有配置,出了異常會走它
from rest_framework.response import Response
def common_exception_handler(exc, context):
# 第一步,先執(zhí)行原來的exception_handler
# 第一種情況,返回Response對象,這表示已經(jīng)處理了異常,它只處理APIExcepiton的異常,第二種情況,返回None,表示沒有處理
res = exception_handler(exc, context)
if not res:
# 執(zhí)行這里就說明res為None,它沒有處理異常
res = Response(data={'code': 1001, 'msg': str(exc)})
return res
# 如果執(zhí)行到這說明res內(nèi)容,返回的是Response對象, res.data.get('detail', '請聯(lián)系管理員')表示如果detail里面有內(nèi)容,則用途detail里面的,沒有則使用'請聯(lián)系管理員'
res = Response(data={'code': 1002, 'msg': res.data.get('detail', '請聯(lián)系管理員')})
# 記錄日志
request = context.get('request') # 這個request是當(dāng)次請求的request對象
view = context.get('view') # 這個viewt是當(dāng)次執(zhí)行的視圖類對象
print('錯誤原因:%s,錯誤視圖類:%s,請求地址:%s,請求方式:%s' % (str(exc), str(view), request.path, request.method))
return res
訪問時后臺會把錯誤信息打印出來:
錯誤原因:服務(wù)器出現(xiàn)了錯誤。,錯誤視圖類:<authenticated.views.TestView object at 0x000002A296C92560>,請求地址:/test/,請求方式:GET
這里是吧錯誤信息打印出來了,正確做法是把它記錄到一個日志文件里面,具體的可以使用python的日志模塊logging
本文摘自 :https://www.cnblogs.com/