DRF解析器

1、概念

解析器(Parsers)是 Django REST Framework 中的重要组件,它负责将传入的请求体内容解析为 Python 数据类型。
解析器的作用

  • 解析请求体内容(如 JSON、表单数据、XML 等)
  • 将原始数据转换为 Python 数据结构
  • 使转换后的数据可在 request.data 中访问

解析器工作流程

  1. 客户端发送请求(包含 Content-Type 头)
  2. DRF 根据 Content-Type 选择合适的解析器
  3. 解析器解析请求体内容(首次访问request.data解析)
  4. 视图可以访问和使用这些数据

2、内置解析器

2.1 BaseParser

基类,所有解释器应该继承该类,并制定media_type属性和覆盖parse方法

python 复制代码
class BaseParser:
    media_type = None

    def parse(self, stream, media_type=None, parser_context=None):
        raise NotImplementedError(".parse() must be overridden.")

2.1 JSONParser

最常用的解析器,解析 application/json类型的请求体,将 JSON 数据转换为 Python 字典。核心就是json.load

python 复制代码
class JSONParser(BaseParser):
    media_type = 'application/json'
    renderer_class = renderers.JSONRenderer
    strict = api_settings.STRICT_JSON

    def parse(self, stream, media_type=None, parser_context=None):
        # ...
        return json.load(decoded_stream, parse_constant=parse_constant)

2.2 FormParser

解析 application/x-www-form-urlencoded类型的请求体(典型的 HTML 表单数据),返回 QueryDict 对象

python 复制代码
class FormParser(BaseParser):
    media_type = 'application/x-www-form-urlencoded'

    def parse(self, stream, media_type=None, parser_context=None):
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        return QueryDict(stream.read(), encoding=encoding)

2.3 FileUploadParser

FileUploadParser用于将整个请求体内容视为一个单一的文件进行解析。它通常用于API接口,客户端直接将文件内容作为请求体发送,而不使用 multipart/form-data格式。

请求示例

bash 复制代码
curl -X PUT \
  http://your-api-domain/upload/sample.jpg \  # 文件名可作为URL的一部分
  -H 'Content-Type: image/jpeg' \             # 设置文件的实际MIME类型
  --data-binary '@/path/to/your/file.jpg'

视图代码实现

python 复制代码
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import FileUploadParser
from rest_framework import status

class SimpleFileUploadView(APIView):
    # 使用 FileUploadParser
    parser_classes = [FileUploadParser]

    def put(self, request, filename, *args, **kwargs): # 注意这里常用 PUT 方法
        # 整个请求体被解析为一个文件对象,可通过 request.data 获取
        uploaded_file = request.data

        # 检查是否有文件数据
        if not uploaded_file:
            return Response(
                {"error": "No file content"}, 
                status=status.HTTP_400_BAD_REQUEST
            )

        # 通常文件名可以从URL参数或请求头中获取,这里假设从URL捕获
        # 处理文件,例如保存
        with open(filename, 'wb+') as destination:
            for chunk in uploaded_file.chunks():
                destination.write(chunk)

        return Response({
            "message": "File uploaded successfully as single content",
            "filename": filename,
            "size": uploaded_file.size
        }, status=status.HTTP_201_CREATED)

2.4 MultiPartParser

用于解析 multipart/form-data类型的请求内容,这是浏览器通过表单上传文件时使用的编码格式。它能够同时处理普通的表单字段和文件字段。

请求示例

bash 复制代码
curl -X POST \
  http://your-api-domain/upload/ \
  -H 'Content-Type: multipart/form-data' \
  -F 'username=testuser' \
  -F 'file=@/path/to/your/file.jpg'

视图实现示例

python 复制代码
# views.py
class FileUploadView(APIView):
    # 指定该视图使用的解析器类
    parser_classes = [MultiPartParser]

    def post(self, request, *args, **kwargs):
        # request.data 是一个类似字典的对象,包含所有解析后的表单数据和文件
        # 获取名为 'file' 的文件对象
        uploaded_file = request.data.get('file')
        # 获取名为 'username' 的普通文本字段
        username = request.data.get('username')

        if not uploaded_file:
            return Response(
                {"error": "No file provided"}, 
                status=status.HTTP_400_BAD_REQUEST
            )

        # 处理文件,例如保存到磁盘
        with open(uploaded_file.name, 'wb+') as destination:
            for chunk in uploaded_file.chunks():
                destination.write(chunk)

        # 返回成功响应
        return Response({
            "message": "File uploaded successfully",
            "filename": uploaded_file.name,
            "username": username,
            "size": uploaded_file.size
        }, status=status.HTTP_201_CREATED)

3、自定义解释器

创建自定义解析器需要继承 BaseParser 并实现 parse 方法

python 复制代码
from rest_framework.parsers import BaseParser
import xml.etree.ElementTree as ET

class XMLParser(BaseParser):
    """
    自定义 XML 解析器
    """
    media_type = 'application/xml'  # 处理的 Content-Type
    
    def parse(self, stream, media_type=None, parser_context=None):
        """
        将 XML 流解析为 Python 字典
        """
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', 'utf-8')
        
        try:
            # 读取并解析 XML
            xml_text = stream.read().decode(encoding)
            root = ET.fromstring(xml_text)
            return self._xml_to_dict(root)
        except ET.ParseError as e:
            from rest_framework.exceptions import ParseError
            raise ParseError(f'XML parse error - {e}')
    
    def _xml_to_dict(self, element):
        """
        将 XML 元素转换为字典
        """
        result = {}
        for child in element:
            if len(child) == 0:
                result[child.tag] = child.text
            else:
                result[child.tag] = self._xml_to_dict(child)
        return result

4、配置使用

4.1 全局配置

settings.py 中设置默认解析器,默认为JSONParser,FormParser,MultiPartParser

python 复制代码
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
    ],
}

4.2 视图级配置

python 复制代码
class FlexibleParserView(APIView):
    parser_classes = [JSONParser, FormParser, MultiPartParser]
    
    def post(self, request):
        return Response({
            "data": request.data,
            "content_type": request.content_type
        })


@api_view(['POST'])
@parser_classes([JSONParser])
def json_only_view(request):
    return Response({"data": request.data})

5、解析器选择流程

DRF 通过内容协商来选择解析器,也就是根据media_type选择

python 复制代码
# rest_framework/request.py

class Request:
    def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None):
        self.parsers = parsers or ()

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            with wrap_attributeerrors():
                self._load_data_and_files()
        return self._full_data      

    def _load_data_and_files(self):
        # 只加载一次
        if not _hasattr(self, '_data'):
            # 解析数据
            self._data, self._files = self._parse()
    
    def _parse(self):
        # 获取内容类型
        media_type = self.content_type
        #  # 选择解析器
        parser = self.negotiator.select_parser(self, self.parsers)
        # 使用选择的解析器进行解析
        parsed = parser.parse(stream, media_type, self.parser_context)
        return (parsed.data, parsed.files)
相关推荐
mit6.8241 小时前
[AI人脸替换] docs | 环境部署指南 | 用户界面解析
人工智能·python
fantasy_arch1 小时前
Pytorch超分辨率模型实现与详细解释
人工智能·pytorch·python
wu_jing_sheng01 小时前
ArcPy 断点续跑脚本:深度性能优化指南
python
playStudy3 小时前
从0到1玩转 Google SEO
python·搜索引擎·github·全文检索·中文分词·solr·lucene
dreams_dream3 小时前
django注册app时两种方式比较
前端·python·django
励志不掉头发的内向程序员4 小时前
从零开始的python学习——常量与变量
开发语言·python·学习
海飘飘5 小时前
技术实现解析:用Trae打造Robocopy可视化界面(文末附带源码)
python
LTXb5 小时前
Python基础语法知识
python
csdn5659738505 小时前
MaxCompute MaxFrame | 分布式Python计算服务MaxFrame(完整操作版)
分布式·python·odps·maxframe
高级测试工程师欧阳6 小时前
Flask模块如何使用
服务器·python·html