DRF学习——解析器

目录

一、简介

二、解析器概念

三、快速使用

四、源码分析


一、简介

本篇文章主要用于介绍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即传入的值

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hemameba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值