python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.parsers import JSONParser
class TestView(APIView):
# JSONParser:请求头content-type为application/json
# FormParser:请求头content-type为application/x-www-form-urlencoded
# MultiPartParser: 请求头content-type为multipart/form-data
# FileUploadParser:上传文件
parser_classes = [JSONParser, FormParser, MultiPartParser, FileUploadParser, ]
def post(self, request, *args, **kwargs):
print(request.content_type)
# 获取请求的值,并使用对应的JSONParser进行处理
print(request.data)
# application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值
print(request.POST)
print(request.FILES)
return Response('POST请求,响应内容')
def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')
parser_classes属性变量中的值,是各种解析器对象。前端会向后台发送不同类型的请求,而django后台的drf接口必须通过配置解析器才能获取到相关请求数据。常用的解析器主要是"JSONParser"和"FormParser"这两个解析器。
源码分析
python
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
)
def get_parsers(self):
"""
Instantiates and returns the list of parsers that this view can use.
"""
return [parser() for parser in self.parser_classes]
同样还是从APIView类的入口方法dispatch入口,在dispatch方法中,调用了initialize_request方法。这个方法在前文中已经说过,是用来封装django原生的request请求的。由上可知,django原生的request请求数据被封装到了Request对象中,在实例化该对象时,将解析数据的解析器初始化到了parsers属性变量中。跳转到get_parsers方法中,可以看到,同样也是返回的是列表生成式。
python
class APIView(View):
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
# Allow dependency injection of other settings to make testing easier.
settings = api_settings
schema = DefaultSchema()
再跳转回APIView类中,可以看到属性变量parser_classes定义的地方。同样也可以通过settings配置文件进行全局配置。如果不需要进行全局配置,那就要在自定义的视图类中对parser_classes属性变量进行重新赋值,即:"parser_classes = [JSONParser, FormParser, MultiPartParser, FileUploadParser,]"。这列表元素都是解析器对象,只要这样配置好,drf就会根据parser_classes中的解析器去解析数据。
为什么从request.data中获取数据?
看到这里,心中有了疑惑,Django Rest Framework框架是在那里触发解析器这个功能的,在上面的源码分析中并未调用任何一个关于解析相关功能的方法,只是做了一个读取配置文件,封装新的request对象的过程,所以是在我们需要读数据的时候才会去触发解析器这个功能,所以我们从request.data入手,我们知道,django原生的request请求,会被drf通过Request对象封装,那么就跳转到Request类定义中看看这个"data"的实现。在data方法的实现中可以看到前端请求的数据是通过"*load_data_and_files"方法获取的,并且返回值是"*full_data"属性变量。先跳转到"_load_data_and_files"方法中:
python
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data
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
在"load_data_and_files"方法中可知,是通过调用" parse"方法去获取请求数据的,并且将请求数据保存在"data"和" files"属性变量中。除文件相关的数据外的数据都保存在了"data"中,而" data"中的数据又都赋值到"*full_data"属性变量中。又由上一张图可知,"data"方法的返回值是"*full_data",即:data方法获取到的数据就是"_full_data"中的数据。
python
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)
@property
def content_type(self):
meta = self._request.META
return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
现在进一步分析_parse,首先第一步做的是获取请求头,可以看到将获取到的请求头保存到了"media_type"变量中。然后又通过调用"select_parser(self, self.parsers)"方法,来选择所需要的解析器(参数"self.parsers"就是我们在自定义视图中"parser_classes"的值)。
python
def select_parser(self, request, parsers):
"""
Given a list of parsers and a media type, return the appropriate
parser to handle the incoming request.
"""
for parser in parsers:
if media_type_matches(parser.media_type, request.content_type):
return parser
return None
class JSONParser(BaseParser):
"""
Parses JSON-serialized data.
"""
media_type = 'application/json'
renderer_class = renderers.JSONRenderer
strict = api_settings.STRICT_JSON
def parse(self, stream, media_type=None, parser_context=None):
"""
Parses the incoming bytestream as JSON and returns the resulting data.
"""
parser_context = parser_context or {}
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
try:
decoded_stream = codecs.getreader(encoding)(stream)
parse_constant = json.strict_constant if self.strict else None
return json.load(decoded_stream, parse_constant=parse_constant)
except ValueError as exc:
raise ParseError('JSON parse error - %s' % str(exc))
又通过,parser_classes中解析器对象中的parser方法来解析请求到的数据,即:"parsed = parser.parse(steam, media_type, self.parser_context)"。这里以"JSONParser"对象为例,由上可知,media_type属性变量中保存的是该解析器所对应的请求头,self.parser_context中,保存的是从前端请求中获取到的请求头。
再来到"JSONParser"类的"parse"方法中可知,再通过返回"json.load"来处理请求数据。返回的请求数据就会保存到"Request"类的"parse"方法中的parsed变量中。而 parse方法的返回值为元组,第一个元素就是需要的数据。这些数据会在"load_data_and_files"方法中,赋值给"Request"类的" data"属性方法,而*data中的数据会赋值给"*full_data"中。因此,"Request"类中"data"方法返回值是"_full_data",这样,就可以通过"request.data"获取请求数据。
全局配置
python
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES':[
'rest_framework.parsers.JSONParser'
'rest_framework.parsers.FormParser'
'rest_framework.parsers.MultiPartParser'
]
}
配置加入不同的解析器,就会解析不同类型的请求数据,当然是可以同时配置加入多个解析器的。同样的,配置了全局解析器后,那么,在自定义的视图类中,就可以不用通过"parser_classes"属性变量进行添加解析器的。