drf的過濾排序分頁異常處理
阿新 • • 發佈:2020-07-28
一. 過濾元件#
1. 步驟#
Copy1. 安裝:pip3 install django-filter
2. 註冊: settings.py中註冊
INSTALLED_APPS = [
...
'django_filters', # 需要註冊應用,
]
3. 全域性配置 或者 區域性配置
全域性配置: 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
區域性配置:
指定所有欄位: filter_fields = '__all__'
指定固定欄位: filter_fields = ['name', ...] # 提示: 可以元組, 也可以是列表
2. 程式碼例項#
Copyfrom rest_framework.response import Response
from rest_framework.generics import ListAPIView
class TextView6(ListAPIView):
# 區域性將全域性可能配置的認證+許可權+頻率禁用
authentication_classes = []
permission_classes = []
throttle_classes = []
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
# filter_fields = ('name', 'price')
filter_fields = '__all__' # 宣告所有的欄位
# url位址列中輸入:
http://127.0.0.1:8000/books2/?name=egon
3. 總結#
Copy安裝 -> 註冊 -> 全域性 or 區域性
4. 注意#
Copydjango-filter的安裝可能會出現django版本最低要求問題, 如果下載最新版本的django-filter
如果使用的是django 1.11版本會自動升級到3.x.
二. 排序元件#
1. 全域性配置 區域性配置#
Copy# 全域性配置
# 排序
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS' : ('rest_framework.filters.OrderingFilter')
}
# 過濾 和 排序
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.OrderingFilter')
}
# 區域性配置
# 排序
from rest_framework.filters import OrderingFilter
filter_backends = [OrderingFilter] # 注意: 如果這樣就會覆蓋全域性配置配置的過濾
# 過濾 和 排序
from rest_framework.filters import OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
filter_backends = [OrderingFilter, DjangoFilterBackend]
filter_fields = '__all__'
2. 程式碼例項#
Copyclass TextView7(ListAPIView):
# 區域性將全域性可能配置的認證+許可權+頻率禁用
authentication_classes = []
permission_classes = []
throttle_classes = []
# 區域性配置排序元件.
# 注意: 如果要過濾和排序, 需要注意的是如果全域性配置了過濾, 需要在宣告排序的基礎之上再什麼過濾. 因為filter_backends的區域性指定會覆蓋過濾的配置.
# filter_backends = [OrderingFilter, DjangoFilterBackend, ] # 提示: 2者之間沒有順序
filter_backends = [DjangoFilterBackend, OrderingFilter]
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
filter_fields = ['name', 'price'] # 可以用列表, 也可以用元組
3. 總結#
Copy# 過濾匯入
from django_filters.rest_framework import DjangoFilterBackend
# 排序匯入
from rest_framework.filters import OrderingFilter
# 注意問題
它們2個全域性配置都是共用一個配置路徑, 如果區域性指定了就會將全域性配置的對應項所有的覆蓋
三. 分頁元件#
1. 分頁的三種方式#
Copyfrom rest_framework.pagination import PageNumberPagination
# 第一種分頁方式: 通過指定page獲取頁數, 通過size獲取每頁顯示的條目
class CoustomPageNumberPagination(PageNumberPagination):
"""
url位址列目支援的查詢格式:
http://api.example.org/accounts/?page=4
http://api.example.org/accounts/?page=4&page_size=100
"""
page_size = 3 # 頁面大小. 表示顯示每頁資料條數. 可配置全域性('PAGE_SIZE': None)
page_query_param = 'page' # 頁面查詢引數. 表示查詢第幾頁的key. (可自定義名稱)
page_size_query_param = 'size' # 頁面大小查詢引數. 表示每一頁顯示的條數 (可自定義名稱)
max_page_size = 5 # 最大頁面大小. 表示每頁最大顯示條數
from rest_framework.pagination import LimitOffsetPagination
# 第二種分頁方式: 通過指定offset找到指定位置, 通過limit往後獲取條目數
class CustomLimitOffsetPagination(LimitOffsetPagination):
"""
url位址列目支援的查詢格式:
http://api.example.org/accounts/?limit=100
http://api.example.org/accounts/?offset=400&limit=100
"""
default_limit = 3 # 每頁條數. 可配置全域性('PAGE_SIZE': None,)
limit_query_param = 'limit' # 從offset標杆的位置, 往後獲取的條目數 (可自定義名稱)
offset_query_param = 'offset' # 標杆. 可以理解為旗幟, 插在這裡, 後去旗幟後面的內容條目 (可自定義名稱)
max_limit = 5 # 每頁顯示的最大條目數. 預設為None, 不做任何限制.
from rest_framework.pagination import CursorPagination
# 第三種分頁方式: 游標分頁
class CustomCursorPagination(CursorPagination):
"""
提示: 這種方式實現的方式很複雜, 因此帶來的好處就是查詢效率極高, 不過也有它的缺陷, 就是通過這種方式製作的分頁只有上一頁下一頁,
如果資料庫資料非常大那麼使用它將會是非常好的選擇.
url位址列目支援的查詢格式:
直接瀏覽器輸入訪問即可, 因為不能指定位置.
"""
cursor_query_param = 'cursor' # 每一頁查詢的key (可自定義名稱)
page_size = 3 # 每頁顯示的條數. 可配置全域性('PAGE_SIZE': None,)
ordering = '-id' # 排序欄位. 如果不指定預設使用'-created'進行分頁, 如果你的資料庫中沒有這個欄位, 那麼就會丟擲異常
from rest_framework.generics import ListAPIView
from . import ser
from . import models
class BookAPIView(ListAPIView):
queryset = models.Book.objects.all()
serializer_class = ser.BookModelSerializer
"""
APIView -> api_settings -> DEFAULTS -> 51行
# Generic view behavior
'DEFAULT_PAGINATION_CLASS': None,
"""
# pagination_class = CoustomPageNumberPagination
# pagination_class = CustomLimitOffsetPagination
pagination_class = CustomCursorPagination
# 總結
from rest_framework.pagination import PageNumberPagination
通過page分頁, 通過site獲取分頁條目數
from rest_framework.pagination import LimitOffsetPagination
通過offset找到分頁位置, 通過limit獲取當前資料之後的條目數
from rest_framework.pagination import CursorPagination
沒有引數, 預設只有上一頁, 下一頁. 通過ordering排序進行分頁
優點: 查詢速度快. 缺點: 無法選擇起始位置
提示: 以上都可以限制最大顯示條目數 和 預設獲取條目數. 以及篩選的key都可以重定義.
2. 如果規範化分頁器的restful返回規範#
Copyfrom rest_framework.generics import ListAPIView
from utils.response import CommonResponse
from utils.throttle import CustomSimpleRateThrottle
from . import ser
from . import models
class BookAPIView(ListAPIView):
queryset = models.Book.objects.all()
serializer_class = ser.BookModelSerializer
throttle_classes = [CustomSimpleRateThrottle, ]
def get(self, request, *args, **kwargs):
instance = self.get_queryset()
# 呼叫分頁器, 例項化出分頁器物件
page_obj = CustomCursorPagination()
instance = page_obj.paginate_queryset(queryset=instance, request=request, view=self)
# 通過分頁器物件. 獲取下一條的url連結
next_url = page_obj.get_next_link()
# 通過分頁器物件. 獲取上一條的url連結
previous_url = page_obj.get_previous_link()
# print('next_url:', next_url)
# print('previous_url:', previous_url)
serializer = self.get_serializer(instance=instance, many=True)
return CommonResponse(results=serializer.data, next=next_url, previous_url=previous_url)
# 總結
# 1. 呼叫分頁器, 例項化出分頁器物件
page_obj = CustomCursorPagination()
# 2. 通過分頁器物件. 獲取下一條的url連結 (注意: 一下獲取都在序列化之後)
next_url = page_obj.get_next_link()
# 3. 通過分頁器物件. 獲取上一條的url連結
previous_url = page_obj.get_previous_link()
# 補充: 通過自定義類繼承分頁類, 就可以實現修改引數了, 如果像上面一樣那麼就不能自定義一些引數了
default_limit = 3
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = 5
四. 異常處理#
1. 從原始碼分析到如何實現自定義異常處理#
Copy# 思路: 發現有些錯誤被drf捕獲了, 而有些錯誤會交給django自己處理, 這是為什麼呢? 原始碼分析一波
# 查詢路徑: APIView -> dispatch -> try..except -> handle_exception
# 原始碼分析:
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc) # 這裡
1. 異常的捕獲範圍: 注意, 並不是所有位置的異常出可以捕獲. 例如: 自定義檢視中的類中丟擲的異常就不行
self.initial(request, *args, **kwargs)
認證: self.perform_authentication(request)
提示: 不會捕獲自定義的認證類. 因此perform_authentication做的事情就是request.user賦值
許可權: self.check_permissions(request)
頻率: self.check_throttles(request)
自定義檢視類中的方法:
response = handler(request, *args, **kwargs)
2. 關鍵實現 handle_exception方法
def handle_exception(self, exc):
# 1) 這裡的在認證失敗的時候會走
if isinstance(exc, (exceptions.NotAuthenticated,
exceptions.AuthenticationFailed)):
# WWW-Authenticate header for 401 responses, else coerce to 403
auth_header = self.get_authenticate_header(self.request)
if auth_header:
exc.auth_header = auth_header
else:
exc.status_code = status.HTTP_403_FORBIDDEN
# 2) 這裡就是通過配置檔案配置的路徑, 拿到處理異常的函式, 內部就一句程式碼: self.settings.EXCEPTION_HANDLER
'''
def get_exception_handler(self):
"""
配置檔案匯入的內容:
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
"""
return self.settings.EXCEPTION_HANDLER
'''
exception_handler = self.get_exception_handler()
# 3) 獲取異常的處理的上下文內容, 本質裡面就是獲取操作的檢視物件的結果
'''
def get_exception_handler_context(self):
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None)
}
'''
context = self.get_exception_handler_context()
# 4) 將剛剛從配置檔案中匯入的檢視函式傳參呼叫
'''
exc: 這裡的exc是APIView中定義的dispatch中傳過來的異常物件
context: 這裡的context是對出現異常物件的上下文捕獲
'''
response = exception_handler(exc, context)
# 5) 關鍵轉折:
'''
這裡就是通過在drf提供的exception_handler函式處理的返回值結果來判斷時候交給django自己處理.
如果response的返回值是None就會交給django處理了, 現在我們要的就是在exception_handler函式執行完畢以後將返回值進行判斷,
並且返回的結果不再是None, 而應該是response物件
'''
if response is None:
self.raise_uncaught_exception(exc)
response.exception = True
return response
# 步驟:
1. 先新建一個.py檔案存放自定義的異常處理函式
2. 在drf提供的預設配置檔案中匯入exception_handler函式的
3. 在自定義異常處理函式中先將exception_handler傳入讓drf先處理一番, 根據返回的結果為None是來執行自己的判斷.
如果返回不為None也不應該直接將原本的response物件直接返回, 可以自己封裝一個符合restful規範的類用來繼承Response類
將原本的response物件中的返回結果通過 response.data.get('detail') 方法獲取
4. settings.py檔案中配置自定義的exception_handler函式的路徑
'EXCEPTION_HANDLER': 'app01.app_auth.custom_exception_handler',
2. 程式碼例項#
Copyfrom rest_framework.views import exception_handler
from rest_framework import status
from rest_framework.response import Response
class APIResponse(Response):
def __init__(self, code=1000, messages='成功', results=None, error=None,
status=None,
template_name=None, headers=None,
exception=False, content_type=None, **kwargs):
data = {
'code:': code,
'messages:': messages,
}
print('error:', error)
print('results:', results)
if results:
data['results'] = results
if error:
data['error'] = error
data.update(kwargs)
super().__init__(data=data, status=status,
template_name=template_name, headers=headers,
exception=exception, content_type=content_type)
def custom_exception_handler(exc, context):
"""
:param exc: 這裡的exc是APIView中定義的dispatch中傳過來的異常物件
try:
...
except Exception as exc:
response = self.handle_exception(exc)
:param context: 這裡的context是對出現異常物件的上下文捕獲
查詢: handle_exception -> get_exception_handler_context
def get_exception_handler_context(self):
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None)
}
:return: 這裡返回Response物件, 本來drf沒有處理的的異常會交給django處理, 但是我們捕獲這種異常, 規定成統一的處理. 讓drf處理.
注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
這裡可以捕獲的異常範圍由以下原始碼得知:
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
捕獲範圍:
self.initial(request, *args, **kwargs)
認證: self.perform_authentication(request)
提示: 不會捕獲自定義的認證類. 因此perform_authentication做的事情就是request.user賦值
許可權: self.check_permissions(request)
頻率: self.check_throttles(request)
自定義檢視類中的方法:
response = handler(request, *args, **kwargs)
"""
obj = None
response = exception_handler(exc, context)
# 注意: exc, context都不是可json序列化的格式, 需要轉換成字串型別.
if not response:
# 自己的處理
if isinstance(exc, AttributeError):
obj = APIResponse(2000, '失敗', error=str(exc), results=str(context), status=status.HTTP_403_FORBIDDEN)
elif isinstance(exc, ImportError):
obj = APIResponse(2002, '失敗', error=str(exc), results=str(context), status=status.HTTP_403_FORBIDDEN)
elif isinstance(exc, TypeError):
obj = APIResponse(2003, '失敗', error=str(exc), results=str(context), status=status.HTTP_403_FORBIDDEN)
elif isinstance(exc, Exception):
obj = APIResponse(2004, '失敗', error=str(exc), results=str(context), status=status.HTTP_403_FORBIDDEN)
else:
# 在drf處理的基礎之上再次處理
obj = APIResponse(2005, '失敗', error=response.data.get('detail'), results=str(context),
status=status.HTTP_403_FORBIDDEN)
return obj
3. 總結#
Copy1. 匯入需要在drf提供的預設函式的基礎之上的函式
from rest_framework.views import exception_handler
2. 自定義異常處理函式2個引數exc, context
3. 先讓drf處理一波, 處理它處理不完的, 或者 在他處理完的基礎之上拓展, 通過response返回結果來進行區分
提示: 可以通過 response.data.get('detail') 獲取drf處理完的物件中返回的響應資訊
4. 注意#
Copy配置檔案中配置自定義的異常處理函式時, drf提供的exception_handler的匯入會與在同一個檔案中自定義的認證類 或者 自定義的許可權類的匯入起衝突.
自定義的異常的處理程式碼邏輯最好新建一個純淨的.py檔案存放
5. 快速使用#
Copyfrom rest_framework.views import exception_handler
from rest_framework.response import Response
class CommonResponse(Response):
def __init__(self, code=1000, messages='ok', results=None,
status=None, template_name=None, headers=None,
exception=False, content_type=None,
**kwargs):
data = {
'code': code,
'messages': messages,
}
data.update(kwargs)
if results:
data['results'] = results
super().__init__(data=data, status=status,
template_name=template_name, headers=headers,
exception=exception, content_type=content_type)
def common_exception_handler(exc, context):
response = exception_handler(exc, context)
if not response:
obj = CommonResponse(code=2000, messages='error', results=str(exc))
else:
obj = CommonResponse(code=2000, messages='error', results=response.data)
return obj