django-restframework
一、安裝與使用
1.安裝
>: pip3 install djangorestframework
2.使用
-
在settings.py中注冊:
INSTALLED_APPS = [ .... 'api.apps.ApiConfig', # drf必須注冊 'rest_framework', ]
-
模塊
# drf的封裝風格 from rest_framework.views import APIView # CBV from rest_framework.response import Response # 響應 from rest_framework.request import Request # 請求 from rest_framework.filters import SearchFilter # 過濾器 from rest_framework.pagination import PageNumberPagination # 分頁 from rest_framework.exceptions import APIException # 異常 from rest_framework.authentication import BaseAuthentication # 驗證 from rest_framework.permissions import IsAuthenticated # 組件 from rest_framework.throttling import SimpleRateThrottle # from rest_framework.settings import APISettings # rest_framework配置文件 from rest_framework import status # 狀態碼 from django.http.request import QueryDict # 類型
-
基於CBV完成滿足RSSTful規范接口
二、request請求分析
2.1. request數據請求
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.http.request import QueryDict
class BookAPIView(APIView):
def get(self, request, *args, **kwargs):
print(request._request.GET) # 原生wigs中值
print(request.GET) # 二次封裝的值
print(request.META) # 所有get請求信息
print(request.META.get("HTTP_AUTH")) # 前台發來的頭都會大寫並加上HTTP
print(request.query_params) # 所有url數據
print(request.POST) # get請求不能獲取Post請求數據
return Response('get ok')
def post(self, request, *args, **kwargs):
print(request._request.POST) # 原生wigs中值
print(request.POST) # 二次封裝的值
print(request.data) # 所有post請求數據
print(request._request) # url數據包<WSGIRequest: POST '/books/?age=123'>
print(request.GET) # POST請求可以Post請求數據
# QueryDict轉為
print(request.query_params.dict())
if isinstance(request.data, QueryDict):
print(request.data.dict())
return Response('post ok')
# urls.py
from . import views
urlpatterns = [
url(r'^books/$', views.BookAPIView.as_view()),
]
總結:
- drf中的request是在wsgi的request基礎上進行再一次封裝
- 將wsgi的request作為drf的request的一個屬性,
_request
- drf中的request對wsgi中的request做完全兼容,新的可以直接獲取wsgi中的數據
- drf中的request對數據解析更規范化,所有的拼接參數(url?age=18)都解析到了query_params中,所有數據報數據都解析到data中
- query_params和data屬於QueryDict類型,可以通過.dict()轉化為字典類型
2.2 request請求源碼分析
# 1. 2. urls.py
from . import views
urlpatterns = [
url(r'^books/$', views.BookAPIView.as_view()),
]
#3.調用drf中的as_view
@classmethod
def as_view(cls, **initkwargs): # cls 為 views.BookAPIView類
...
view = super().as_view(**initkwargs) # as_view原生的as_view,super() 為 view
view.cls = cls
view.initkwargs = initkwargs
return csrf_exempt(view)
# 4.進入dif中dispatch
def dispatch(self, request, *args, **kwargs): # self為views.BookAPIView類
self.args = args
self.kwargs = kwargs
# 對原生request進行了二次封裝
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
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.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# 4.1 返回封裝的request對象
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request) # 解析
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
# 5.Request類創建 self._request = request自己的屬性
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
.format(request.__class__.__module__, request.__class__.__name__)
)
self._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
#6.如何實現獲取wgis中的屬性
def __getattr__(self, attr):
"""
If an attribute does not exist on this instance, then we also attempt
to proxy it to the underlying HttpRequest object.
"""
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
- 導入drf中的APIView並創建類繼承它,在配置url:
url(r'^books/$', views.BookAPIView.as_view()),
- 當項目啟動時會執行drf中的as_view()方法,返回view方法(局部禁用了csrf認證), url:
url(r'^books/$', views.BookAPIView.view
- 瀏覽器發送請求來時則會執行url中view函數,在執行view中返回的是
self.dispatch(request, *args, **kwargs)
, dispatch是drf中的dispatch, 不是wsgi中原生的dispatch - 進入drf中的dispatch首先會對原生的request進行二次封裝:
request = self.initialize_request(request, *args, **kwargs)
,在initialize_request
方法中返回了一個request實例對象 - 進入request.py中的Request方法中就會發現在初始化方法中將wsgi方法的request變為了自己的一個屬性: 'self._request = request'
- request通過反射的方法獲取wsgi中request屬性,當request.獲取屬性先到 _request中查找,沒有則查自己本身的
- 核心
- 走drf的Request初始化方法__init__:self._request = request
- drf的Request的getter方法__getattr__:先從self._request反射取屬性,沒取到再從drf的request中取
- request除了可以訪問原wsgi協議的request所有內容,還可以訪問 query_params、data
三、渲染模板
根據用戶請求的RUL向服務器要響應的數據類型,比如:json數據,xml數據,將這些數據向用戶返回
3.1渲染模板的使用
# 在renderers.py模板模塊中導入你要渲染的模板
# JSONRenderer: 返回json數據
# BrowsableAPIRenderer: 返回瀏覽器html頁面
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
# 1.局部配置
# 渲染模塊的局部配置
# 局部禁用就是配置空list:[] # pytthon3.7 有問題
renderer_classes = [JSONRenderer, BrowsableAPIRenderer]
# 2.全局配置
# drf的配置,在drf中settings.py查看如何配置
REST_FRAMEWORK = {
# 渲染模塊的全局配置:開發一般只配置json
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
],
}
3.2渲染模板的源碼解析
# 1 對請求響應進行二次封裝
def dispatch(self, request, *args, **kwargs):
....
....
# 二次處理響應
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# 2. 接續配置的渲染類
def finalize_response(self, request, response, *args, **kwargs):
if isinstance(response, Response):
if not getattr(request, 'accepted_renderer', None):
# 點進去,內部解析了配置的渲染的類
neg = self.perform_content_negotiation(request, force=True)
# 拿到渲染類對象
request.accepted_renderer, request.accepted_media_type = neg
response.accepted_renderer = request.accepted_renderer
response.accepted_media_type = request.accepted_media_type
response.renderer_context = self.get_renderer_context()
# 3. 獲取渲染對象的類
def perform_content_negotiation(self, request, force=False):
"""
Determine which renderer and media type to use render the response.
"""
# 點進去,獲取配置的渲染類
renderers = self.get_renderers()
conneg = self.get_content_negotiator()
try:
return conneg.select_renderer(request, renderers, self.format_kwarg)
except Exception:
if force:
return (renderers[0], renderers[0].media_type)
raise
# 3 renderer_classes 自己全局
def get_renderers(self):
"""
Instantiates and returns the list of renderers that this view can use.
"""
# 從自己的視圖類找renderer_classer類屬性(局部配置)APIView的類屬性(從自己配置文件中找)> 自己的項目配置文件中找(全局配置), drf默認配置問價中找(默認配置)
return [renderer() for renderer in self.renderer_classes]
class BaseRenderer:
"""
All renderers should extend this class, setting the `media_type`
and `format` attributes, and override the `.render()` method.
"""
media_type = None
format = None
charset = 'utf-8'
render_style = 'text'
def render(self, data, accepted_media_type=None, renderer_context=None):
raise NotImplementedError('Renderer class requires .render() to be implemented')
總結:
- 根據流程,再次進入drf的dispatch方法,該方法對響應數據進行了二次封裝,然后,進入self.finalize_response方法
- 在self.finalize_respons方法中perform_content_negotiation中獲取渲染的對象結果,進入perform_content_negotiation方法
- perform_content_negotiation方法中實現了獲取配置文件的渲染結果集,通過self.get_renderers()方法
- 在self.get_renderers()方法中self.renderer_classes讀取配置的渲染對象,先從創建的視圖類中(BookAPIView(APIView)(局部配置))找,然后在從自己的項目文件中找(項目中 settings.py,全局配置), 最后從drf默認配置文件中(默認配置)
- renderer()是從配置文件中獲取到的對應類然后到 renderer.py 文件中獲取類對象,創建的類對象(JSONRenderer())返回數據渲染的結果
- 核心:可以全局和局部配置視圖類支持的結果渲染:默認可以json和頁面渲染,學習該模塊的目的是開發可以全局只配置json方式渲染
四、解析模塊的使用
服務器根據設置的請求頭content-type接收客戶端對應的數據信息
# JSONParser: 只接收json數據
# FormParser: 只接收urlencoded
# MultiPartParser:只接收form-data
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
# 1.局部配置
# 解析模塊的局部配置
#局部配置禁用就是配置空list[]
parser_classes = [JSONParser, MultiPartParser, FormParser]
# 2.全局配置
# drf的配置,在drf中settings.py查看如何配置
REST_FRAMEWORK = {
# 解析模塊的全局配置
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser'
]
}
# 1.對數據進行二次解析
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
# 對原生request進行了二次封裝,查看解析
request = self.initialize_request(request, *args, **kwargs)
...
# 2 獲取解析數據
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
# 准備要解析的字典
parser_context = self.get_parser_context(request)
return Request(
# 初始wigs中request
request,
# 解析模塊在封裝原生request是,將數據一並解析
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context # 解析的內容
)
# 2.1 返回要解析內容字典
def get_parser_context(self, http_request):
"""
Returns a dict that is passed through to Parser.parse(),
as the `parser_context` keyword argument.
"""
# Note: Additionally `request` and `encoding` will also be added
# to the context by the Request object.
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {})
}
# 3 獲取要解析的對象
def get_parsers(self):
"""
Instantiates and returns the list of parsers that this view can use.
"""
return [parser() for parser in self.parser_classes]
class BaseParser:
media_type = None
def parse(self, stream, media_type=None, parser_context=None):
raise NotImplementedError(".parse() must be overridden.")
總結:
- 根據流程,再次進入drf的dispatch方法,該方法對響應數據進行了二次封裝同時也對數據進行了二次解析,然后,進入 request = self.initialize_request(request, *args, **kwargs)方法
- 在 self.initialize_request方法中通過self.get_parser_context(request)方法,獲取要解析的數據,get_parser_context(self, http_request):方法返回解析的數據字典返回
- 在Request類中調用get_parsers(self)方法獲取解析的對象,解析的隨想先從創建的視圖類中(BookAPIView(APIView)(局部配置))找,然后在從自己的項目文件中找(項目中 settings.py,全局配置), 最后從drf默認配置文件中(默認配置),實現數據的解析
- 只有在后台設置了解析的數據,才會給前台正確的響應否則報錯
- 核心:請求的數據包格式會有三種(json、urlencoded、form-data),drf默認支持三種數據的解析,可以全局或局部配置視圖類具體支持的解析方式
五、異常模塊的使用
重寫異常模塊的目的是記錄異常信息(日志記錄)
5.1異常模塊的使用
# 1. 配置異常模塊,通過settings.py獲取
REST_FRAMEWORK = {
# 異常模塊配置
# Exception handling
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', # 默認
'EXCEPTION_HANDLER': 'api.utils.exception_handler', # 自己重寫的路徑
'NON_FIELD_ERRORS_KEY': 'non_field_errors',
}
# 2.編寫異常模塊,創建一個新的文件.py重寫exception_handler方法
from rest_framework.response import Response
def exception_handler(exc, context):
# 在exception_handler函數完成異常信息的返回以及異常信息的logging日志
print(exc)
print(context)
print(type(exc))
# exc.detail 攜帶錯誤的信息
return Response(f'{context["view"].__class__.__name__}{exc}')
# 2.2升級版
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler
def exception_handler(exc, context):
response = drf_exception_handler(exc, context)
if response is None: # drf沒有處理的異常
response = Response({"detail": f'{context["view"].__class__.__name__}{exc}'})
# 項目階段,將日志記錄保存到文件中
return response
# 2.3終極版
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework import status
def exception_handler(exc, context):
response = drf_exception_handler(exc, context)
if response is None: # drf沒有處理的異常
response = Response(data={
'status': 7,
"detail": f'{context["view"].__class__.__name__}{exc}',
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
response = Response(status=response.status_code, data={
'status': 7,
"detail": f'{response.data.get("detail")}',
})
# 項目階段,將日志記錄保存到文件中
return response
5.2異常源碼分析
# 1. 異常出錯
def dispatch(self, request, *args, **kwargs):
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.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# 2 異常處理
def handle_exception(self, exc):
"""
Handle any exception that occurs, by returning an appropriate response,
or re-raising the error.
"""
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
# 都會走,獲取異常處理函數
exception_handler = self.get_exception_handler()
# 獲取請求數據內容字典
context = self.get_exception_handler_context()
# 異常函數:接收exc,context,返回response對象
response = exception_handler(exc, context)
if response is None:
# 異常函數如果為空交給原生django處理
self.raise_uncaught_exception(exc)
# 異常返回
response.exception = True
return response
# 2.1獲取異常處理函數具體函數,可以自己配置
def get_exception_handler(self):
"""
Returns the exception handler that this view uses.
"""
return self.settings.EXCEPTION_HANDLER
總結:
-
在APIView的dispatch方法中,有一個try...except...異常,將代碼運行的異常都交給異常處理模塊進行處理: response = self.handle_exception(exc)
-
在handle_exception()中從配置中映射處理異常的函數(自定義異常模塊就是自定義配置函數指向自己的函數),通過self.get_exception_handler()去獲取配置文件中異常函數對象,然后將數據給自己寫的函數進行處理,如果沒有配置則是框架中默認的
-
異常函數exception_handler(exe,content)處理異常,就會走自己重寫的異常函數,先交給系統處理(客戶端的異常),系統沒處理(服務器異常),再自己處理
-
核心:異常信息都需要被logging記錄,所以需要自定義;drf只處理客戶端異常,服務器異常需要手動處理,統一處理結果
5.3.狀態碼響應模塊
class APIResponse(Response):
# 格式化data
def __init__(self, status=0, msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs):
data = { # json的response基礎有數據狀態碼和數據狀態信息
'status': status,
'msg': msg
}
if results is not None: # 后台有數據,響應數據
data['results'] = results
data.update(**kwargs) # 后台的一切自定義響應數據直接放到響應數據data中
super().__init__(data=data, status=http_status, headers=headers, exception=exception)
Response類生成對象需要的參數,以及Response類的對象可以使用的屬性
-
參數:Response(data=響應的數據, status=響應的網絡狀態碼, headers=想通過響應頭再攜帶部分信息給前端)
-
屬性:response.data response.status_code response.status_text
-
源碼:Response類的__init__方法
-
核心:知道response對象產生可以傳那些信息,response對象又是如何訪問這些信息的