目录
一、简介
本篇文章主要用于介绍drf的解析器(使用URLPathVersioning)的快速使用以及源码分析
二、解析器概念
个人理解为前端发送到后端数据,经过解析器可以转化为后端可以理解的数据格式
三、快速使用
①在views.py中编写一个简单的视图类
# api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import JSONParser, FormParser
from rest_framework.negotiation import DefaultContentNegotiation
class HomeView(APIView):
# 所有的解析器
# parser_classes = api_settings.DEFAULT_PARSER_CLASSES
# 规定传入的数据可以用JSON格式和Form格式
parser_classes = [JSONParser, FormParser]
# 根据请求头选择解析器
# content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
content_negotiation_class = DefaultContentNegotiation
def get(self, request, *args, **kwargs):
print(request.version)
return Response("...")
def post(self, request, *args, **kwargs):
print(request.data)
self.dispatch
return Response("post")
②在urls.py中定义URL
# day14/urls.py
from django.urls import path
from api import views
urlpatterns = [
path('home/<str:version>/', views.HomeView.as_view(), name="home-list"),
]
③测试接口
请求体中的数据采用JSON格式
此时的request.data打印为:
# dict格式
{'123': 123}
请求体中的数据采用Form格式
此时的request.data打印为:
# QueryDict格式
<QueryDict: {'123': ['123']}>
④补充
在我们平时的编程中,我们是没有写如同上面的代码一样指定解析器的,因为drf会默认三个指定的解析器,可以用打印的方式查看
print(self.parser_classes)
[<class 'rest_framework.parsers.JSONParser'>, <class 'rest_framework.parsers.FormParser'>, <class 'rest_framework.parsers.MultiPartParser'>]
可见drf有三个默认的解析器,分别是JSONParser,FormParser,MultiPartParser。其中最后一个是混合解析器,它支持JSON和文件格式的数据,如图片等
四、源码分析
找到APIView的dispatch方法,其源码如下:
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
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
封装request的语句如下:
request = self.initialize_request(request, *args, **kwargs)
调用了initialize_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
)
首先将get_parser_context方法的返回值赋值给parser_context ,其中get_parser_context的源码如下:
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', {})
}
返回了一个字典,其中'view'的值是当前实例化对象即drf的request,'args'的值是从url传入过来的参数,'kwargs'同理
回到initialize_request,最后返回了一个Request实例化对象,其中的参数分别是django的request,解析器的对象列表,认证类的对象列表,选择解析器的对象列表以及含有parser_context,实例化对象时,都需要关注其有没有__init__方法,Request类的源码如下:
class Request:
"""
Wrapper allowing to enhance a standard `HttpRequest` instance.
Kwargs:
- request(HttpRequest). The original request instance.
- parsers(list/tuple). The parsers to use for parsing the
request content.
- authenticators(list/tuple). The authenticators used to try
authenticating the request's user.
"""
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
'The `request` argument must be an instance of '
'`django.http.HttpRequest`, not `{}.{}`.'
.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
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
if self.parser_context is None:
self.parser_context = {}
self.parser_context['request'] = self
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
force_user = getattr(request, '_force_auth_user', None)
force_token = getattr(request, '_force_auth_token', None)
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
Request是有__init__方法的,前面很好理解,就将传入的参数分别赋值给当前对象同名的参数,即request.parsers =parsers ,request.authenticators=authenticators等,我们需要关注下面这段初始化:
if self.parser_context is None:
self.parser_context = {}
self.parser_context['request'] = self
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
当parser_context 不为空时,在parser_context 这个字典中添加'request'和'encoding'两个key,其值分别是当前drf的request和解码方法
以上便是drf的request对象的初始化过程,那么为什么我们调用request.data时就能够得到当前传入的数据呢?这就需要找到Request类中的data方法,其源码如下:
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data
首先是一个反射,查看当前对象中是否有_full_data成员,如果没有则调用_load_data_and_files方法,_load_data_and_files的源码如下:
def _load_data_and_files(self):
"""
Parses the request content into `self.data`.
"""
if not _hasattr(self, '_data'):
self._data, self._files = self._parse()
if self._files:
self._full_data = self._data.copy()
self._full_data.update(self._files)
else:
self._full_data = self._data
# if a form media type, copy data & files refs to the underlying
# http request so that closable objects are handled appropriately.
if is_form_media_type(self.content_type):
self._request._post = self.POST
self._request._files = self.FILES
首先也是一个反射,查看当前对象中是否有_data成员,如果没有则调用_parse方法,_parse的源码如下:
def _parse(self):
"""
Parse the request content, returning a two-tuple of (data, files)
May raise an `UnsupportedMediaType`, or `ParseError` exception.
"""
media_type = self.content_type
try:
stream = self.stream
except RawPostDataException:
if not hasattr(self._request, '_post'):
raise
# If request.POST has been accessed in middleware, and a method='POST'
# request was made with 'multipart/form-data', then the request stream
# will already have been exhausted.
if self._supports_form_parsing():
return (self._request.POST, self._request.FILES)
stream = None
if stream is None or media_type is None:
if media_type and is_form_media_type(media_type):
empty_data = QueryDict('', encoding=self._request._encoding)
else:
empty_data = {}
empty_files = MultiValueDict()
return (empty_data, empty_files)
parser = self.negotiator.select_parser(self, self.parsers)
if not parser:
raise exceptions.UnsupportedMediaType(media_type)
try:
parsed = parser.parse(stream, media_type, self.parser_context)
except Exception:
# If we get an exception during parsing, fill in empty data and
# re-raise. Ensures we don't simply repeat the error when
# attempting to render the browsable renderer response, or when
# logging the request or similar.
self._data = QueryDict('', encoding=self._request._encoding)
self._files = MultiValueDict()
self._full_data = self._data
raise
# Parser classes may return the raw data, or a
# DataAndFiles object. Unpack the result as required.
try:
return (parsed.data, parsed.files)
except AttributeError:
empty_files = MultiValueDict()
return (parsed, empty_files)
首先是将当前请求的content_type赋值给了media_type,然后可以简单理解为通过当前的content_type选择解析器之后,返回已将数据解析后的值
回到_load_data_and_files,其源码如下:
def _load_data_and_files(self):
"""
Parses the request content into `self.data`.
"""
if not _hasattr(self, '_data'):
self._data, self._files = self._parse()
if self._files:
self._full_data = self._data.copy()
self._full_data.update(self._files)
else:
self._full_data = self._data
# if a form media type, copy data & files refs to the underlying
# http request so that closable objects are handled appropriately.
if is_form_media_type(self.content_type):
self._request._post = self.POST
self._request._files = self.FILES
首先判断 self._files是否为空,如果不为空,则将_data赋值给_full_data,因此每次用request.data时就是调用self._full_data即传入的值