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]
本文来自博客园,作者:Chimengmeng,转载请注明原文链接:https://www.cnblogs.com/dream-ze/p/17712494.html