DRF之Response原始碼分析
【一】響應類的物件Response原始碼
【1】路由
from django.contrib import admin
from django.urls import path
from book import views
urlpatterns = [
path('admin/', admin.site.urls),
path('test/', views.TestView.as_view()),
]
【2】檢視
from rest_framework.views import APIView
from rest_framework.response import Response
class TestView(APIView):
def get(self, request, *args, **kwargs):
return Response('ok')
【3】原始碼
from rest_framework.response import Response
class Response(SimpleTemplateResponse):
"""
An HttpResponse that allows its data to be rendered into
arbitrary media types.
"""
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
"""
Alters the init arguments slightly.
For example, drop 'template_name', and instead use 'data'.
Setting 'renderer' and 'media_type' will typically be deferred,
For example being set automatically by the `APIView`.
"""
super().__init__(None, status=status)
if isinstance(data, Serializer):
msg = (
'You passed a Serializer instance as data, but '
'probably meant to pass serialized `.data` or '
'`.error`. representation.'
)
raise AssertionError(msg)
self.data = data
self.template_name = template_name
self.exception = exception
self.content_type = content_type
if headers:
for name, value in headers.items():
self[name] = value
@property
def rendered_content(self):
renderer = getattr(self, 'accepted_renderer', None)
accepted_media_type = getattr(self, 'accepted_media_type', None)
context = getattr(self, 'renderer_context', None)
assert renderer, ".accepted_renderer not set on Response"
assert accepted_media_type, ".accepted_media_type not set on Response"
assert context is not None, ".renderer_context not set on Response"
context['response'] = self
media_type = renderer.media_type
charset = renderer.charset
content_type = self.content_type
if content_type is None and charset is not None:
content_type = "{}; charset={}".format(media_type, charset)
elif content_type is None:
content_type = media_type
self['Content-Type'] = content_type
ret = renderer.render(self.data, accepted_media_type, context)
if isinstance(ret, str):
assert charset, (
'renderer returned unicode, and did not specify '
'a charset value.'
)
return ret.encode(charset)
if not ret:
del self['Content-Type']
return ret
@property
def status_text(self):
"""
Returns reason text corresponding to our HTTP response status code.
Provided for convenience.
"""
return responses.get(self.status_code, '')
def __getstate__(self):
"""
Remove attributes from the response that shouldn't be cached.
"""
state = super().__getstate__()
for key in (
'accepted_renderer', 'renderer_context', 'resolver_match',
'client', 'request', 'json', 'wsgi_request'
):
if key in state:
del state[key]
state['_closable_objects'] = []
return state
【二】原始碼分析
【1】__init__
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
"""
# 稍微更改init引數。
Alters the init arguments slightly.
# 例如,刪除“template_name”,改為使用“data”。
For example, drop 'template_name', and instead use 'data'.
# 設定“renderer”和“media_type”
Setting 'renderer' and 'media_type' will typically be deferred,
For example being set automatically by the `APIView`.
"""
# 呼叫父類的 init 方法
super().__init__(None, status=status)
# 判斷當前處理過的資料是否是 序列化過後的資料
if isinstance(data, Serializer):
# 這裡的意思是 ,data 只能是序列化後的 serializer.data 或者是 serializer.errors
msg = (
'You passed a Serializer instance as data, but '
'probably meant to pass serialized `.data` or '
'`.error`. representation.'
)
raise AssertionError(msg)
# 初始化 data 資料
self.data = data
# 初識化模板名字
self.template_name = template_name
# 初始化異常捕獲物件
self.exception = exception
# 初始化當前資料型別
self.content_type = content_type
# 如果頭部有資料
if headers:
# 遍歷頭部資料
for name, value in headers.items():
# 新增到響應資料中
self[name] = value
【2】rendered_content
# 包裝成資料屬性
@property
# 渲染響應內容
def rendered_content(self):
# 獲取響應物件中的屬性值,獲取模版渲染器
renderer = getattr(self, 'accepted_renderer', None)
# 獲取響應物件中的屬性值,獲取媒體型別
accepted_media_type = getattr(self, 'accepted_media_type', None)
# 獲取響應物件中的屬性值,獲取處理過後的資料
context = getattr(self, 'renderer_context', None)
# 使用斷言語句,確保上述獲取到的屬性值都不為空。如果為空,丟擲異常並給出相應的錯誤提示
assert renderer, ".accepted_renderer not set on Response"
assert accepted_media_type, ".accepted_media_type not set on Response"
assert context is not None, ".renderer_context not set on Response"
# 設定上下文字典中的鍵response的值為當前的響應物件
context['response'] = self
# 獲取渲染器物件的媒體型別
media_type = renderer.media_type
# 獲取渲染器物件的編碼集
charset = renderer.charset
# 獲取渲染器物件的資料型別
content_type = self.content_type
# 如果content_type為空且charset不為空
if content_type is None and charset is not None:
# 將media_type和charset拼接為新的content_type值
content_type = "{}; charset={}".format(media_type, charset)
# 如果content_type還是為空
elif content_type is None:
# 則將其值設定為media_type
content_type = media_type
# 將響應的資料格式新增到響應物件頭中
self['Content-Type'] = content_type
# 使用渲染器渲染資料
ret = renderer.render(self.data, accepted_media_type, context)
# 判斷渲染出來的資料是不是字串格式
if isinstance(ret, str):
# 若不存在,則丟擲異常並提示渲染器返回了Unicode字串但未指定字符集
assert charset, (
'renderer returned unicode, and did not specify '
'a charset value.'
)
# 如果存在,則將ret按指定的charset進行編碼並返回
return ret.encode(charset)
# 如果ret是字串型別,再次檢查charset是否存在。
# 如果ret為空,則刪除響應物件的Content-Type頭部屬性
if not ret:
del self['Content-Type']
# 返回渲染後的結果給呼叫方
return ret
【3】status_text
@property
# 狀態碼校驗
def status_text(self):
"""
#返回與我們的HTTP響應狀態程式碼相對應的原因文字。
Returns reason text corresponding to our HTTP response status code.
Provided for convenience.
"""
# 獲取到響應物件中的狀態碼並返回
return responses.get(self.status_code, '')
def __getstate__(self):
"""
Remove attributes from the response that shouldn't be cached.
"""
# 呼叫了父類的__getstate__方法來獲取父類中定義的狀態,並將其賦值給變數state
state = super().__getstate__()
# 遍歷 鍵
for key in (
'accepted_renderer', 'renderer_context', 'resolver_match',
'client', 'request', 'json', 'wsgi_request'
):
# 判斷當前鍵是否在狀態碼物件中
# 如果存在則刪除響應的響應頭
if key in state:
del state[key]
# 將一個空列表賦值給state['_closable_objects']。
# 目的是確保物件在進行序列化時沒有包含不必要的屬性,從而避免潛在的問題。
state['_closable_objects'] = []
return state
【三】SimpleTemplateResponse(沒什麼價值)
class SimpleTemplateResponse(HttpResponse):
rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks']
def __init__(self, template, context=None, content_type=None, status=None,
charset=None, using=None, headers=None):
# 很明顯,將這兩個成員稱為“模板”
# It would seem obvious to call these next two members 'template' and
# “context”,但這些名稱是作為測試客戶端的一部分保留的
# 'context', but those names are reserved as part of the test Client
# API為了避免名稱衝突,我們使用不同的名稱。
# API. To avoid the name collision, we use different names.
# 初始化模版名字
self.template_name = template
# 初始化上下文物件
self.context_data = context
# 判斷是夠被使用
self.using = using
# 是否有回撥函式
self._post_render_callbacks = []
# request將當前請求物件儲存在已知的子類中
#關於請求,如TemplateResponse。它是在基類中定義的
#以最大限度地減少程式碼重複。
#這叫做自我_請求,因為self.request被覆蓋
#django.test.client.client.與template_name和context_data不同,
#_request不應被視為公共API的一部分。
# _request stores the current request object in subclasses that know
# about requests, like TemplateResponse. It's defined in the base class
# to minimize code duplication.
# It's called self._request because self.request gets overwritten by
# django.test.client.Client. Unlike template_name and context_data,
# _request should not be considered part of the public API.
# 初始化 request 物件 ---- 上一步處理過的request物件(檢視類中的request物件)
self._request = None
#content引數在這裡沒有意義,因為它將被替換
#使用渲染的模板,所以我們總是傳遞空字串,以便
#防止錯誤並提供更短的簽名。
# content argument doesn't make sense here because it will be replaced
# with rendered template so we always pass empty string in order to
# prevent errors and provide shorter signature.
# 呼叫父類初識化方法初始化
super().__init__('', content_type, status, charset=charset, headers=headers)
#_is_render跟蹤模板和上下文是否已烘焙
#轉化為最終響應。
#超級__init__不知道什麼比將self.content設定為更好的了
#我們剛剛給它的空字串,它錯誤地設定了_is_render
#True,所以我們在呼叫super__init__之後將其初始化為False。
# _is_rendered tracks whether the template and context has been baked
# into a final response.
# Super __init__ doesn't know any better than to set self.content to
# the empty string we just gave it, which wrongly sets _is_rendered
# True, so we initialize it to False after the call to super __init__.
self._is_rendered = False
def __getstate__(self):
"""
Raise an exception if trying to pickle an unrendered response. Pickle
only rendered data, not the data used to construct the response.
"""
obj_dict = self.__dict__.copy()
if not self._is_rendered:
raise ContentNotRenderedError('The response content must be '
'rendered before it can be pickled.')
for attr in self.rendering_attrs:
if attr in obj_dict:
del obj_dict[attr]
return obj_dict
def resolve_template(self, template):
"""Accept a template object, path-to-template, or list of paths."""
if isinstance(template, (list, tuple)):
return select_template(template, using=self.using)
elif isinstance(template, str):
return get_template(template, using=self.using)
else:
return template
def resolve_context(self, context):
return context
@property
def rendered_content(self):
"""Return the freshly rendered content for the template and context
described by the TemplateResponse.
This *does not* set the final content of the response. To set the
response content, you must either call render(), or set the
content explicitly using the value of this property.
"""
template = self.resolve_template(self.template_name)
context = self.resolve_context(self.context_data)
return template.render(context, self._request)
def add_post_render_callback(self, callback):
"""Add a new post-rendering callback.
If the response has already been rendered,
invoke the callback immediately.
"""
if self._is_rendered:
callback(self)
else:
self._post_render_callbacks.append(callback)
def render(self):
"""Render (thereby finalizing) the content of the response.
If the content has already been rendered, this is a no-op.
Return the baked response instance.
"""
retval = self
if not self._is_rendered:
self.content = self.rendered_content
for post_callback in self._post_render_callbacks:
newretval = post_callback(retval)
if newretval is not None:
retval = newretval
return retval
@property
def is_rendered(self):
return self._is_rendered
def __iter__(self):
if not self._is_rendered:
raise ContentNotRenderedError(
'The response content must be rendered before it can be iterated over.'
)
return super().__iter__()
@property
def content(self):
if not self._is_rendered:
raise ContentNotRenderedError(
'The response content must be rendered before it can be accessed.'
)
return super().content
@content.setter
def content(self, value):
"""Set the content for the response."""
HttpResponse.content.fset(self, value)
self._is_rendered = True
class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request']
def __init__(self, request, template, context=None, content_type=None,
status=None, charset=None, using=None, headers=None):
super().__init__(template, context, content_type, status, charset, using, headers=headers)
self._request = request
【四】響應類的物件Respons引數詳解
【1】data
- 響應體的內容,可以字串,字典,列表
【2】status
-
http響應狀態碼
- drf把所有響應碼都定義成了一個常量
from rest_framework.status import HTTP_200_OK HTTP_100_CONTINUE = 100 HTTP_101_SWITCHING_PROTOCOLS = 101 HTTP_102_PROCESSING = 102 HTTP_103_EARLY_HINTS = 103 HTTP_200_OK = 200 HTTP_201_CREATED = 201 HTTP_202_ACCEPTED = 202 HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 HTTP_204_NO_CONTENT = 204 HTTP_205_RESET_CONTENT = 205 HTTP_206_PARTIAL_CONTENT = 206 HTTP_207_MULTI_STATUS = 207 HTTP_208_ALREADY_REPORTED = 208 HTTP_226_IM_USED = 226 HTTP_300_MULTIPLE_CHOICES = 300 HTTP_301_MOVED_PERMANENTLY = 301 HTTP_302_FOUND = 302 HTTP_303_SEE_OTHER = 303 HTTP_304_NOT_MODIFIED = 304 HTTP_305_USE_PROXY = 305 HTTP_306_RESERVED = 306 HTTP_307_TEMPORARY_REDIRECT = 307 HTTP_308_PERMANENT_REDIRECT = 308 HTTP_400_BAD_REQUEST = 400 HTTP_401_UNAUTHORIZED = 401 HTTP_402_PAYMENT_REQUIRED = 402 HTTP_403_FORBIDDEN = 403 HTTP_404_NOT_FOUND = 404 HTTP_405_METHOD_NOT_ALLOWED = 405 HTTP_406_NOT_ACCEPTABLE = 406 HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 HTTP_408_REQUEST_TIMEOUT = 408 HTTP_409_CONFLICT = 409 HTTP_410_GONE = 410 HTTP_411_LENGTH_REQUIRED = 411 HTTP_412_PRECONDITION_FAILED = 412 HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 HTTP_414_REQUEST_URI_TOO_LONG = 414 HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 HTTP_417_EXPECTATION_FAILED = 417 HTTP_418_IM_A_TEAPOT = 418 HTTP_421_MISDIRECTED_REQUEST = 421 HTTP_422_UNPROCESSABLE_ENTITY = 422 HTTP_423_LOCKED = 423 HTTP_424_FAILED_DEPENDENCY = 424 HTTP_425_TOO_EARLY = 425 HTTP_426_UPGRADE_REQUIRED = 426 HTTP_428_PRECONDITION_REQUIRED = 428 HTTP_429_TOO_MANY_REQUESTS = 429 HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451 HTTP_500_INTERNAL_SERVER_ERROR = 500 HTTP_501_NOT_IMPLEMENTED = 501 HTTP_502_BAD_GATEWAY = 502 HTTP_503_SERVICE_UNAVAILABLE = 503 HTTP_504_GATEWAY_TIMEOUT = 504 HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 HTTP_506_VARIANT_ALSO_NEGOTIATES = 506 HTTP_507_INSUFFICIENT_STORAGE = 507 HTTP_508_LOOP_DETECTED = 508 HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509 HTTP_510_NOT_EXTENDED = 510 HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
【3】template_name
- 模板名字,用瀏覽器訪問,看到好看的頁面,用postman訪問,返回正常資料
- 自定製頁面
- 根本不用
【4】headers
- 響應頭加資料(後面講跨域問題再講)
- headers=
【5】content_type
- 響應編碼,一般不用
三個重要的引數:data,status,headers
【五】請求響應的格式
- 預設是兩種:
- 純json
- 瀏覽器看到的樣子
【1】限制方式一:
- 在檢視類上寫
- 只是區域性檢視類有效
# 總共有兩個個:JSONRenderer,BrowsableAPIRenderer
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
renderer_classes = [JSONRenderer]
【2】限制方式二:
- 在配置檔案中寫
- 全域性有效
# drf的配置,統一寫成它
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
# 'rest_framework.renderers.BrowsableAPIRenderer',
],
}
【3】全域性配置了只支援json,區域性想支援2個
- 只需要在區域性,檢視類中,寫2個即可
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
renderer_classes = [JSONRenderer,BrowsableAPIRenderer]