You need to enable JavaScript to run this app. Django测试中APIClient发送带文件请求,视图request.FILES为空问题排查
优惠活动
大模型
产品
解决方案
定价
更多

Django测试中APIClient发送带文件请求,视图request.FILES为空问题排查

问题根因

你遇到的问题本质是DRF 测试客户端 APIClient 的传参规则和 Django 原生测试客户端不兼容,你传递的 files 参数被完全忽略,根本没有进入请求 payload。

  • 两个测试客户端的核心差异:
    • 从 django.test 导入的原生 Client,其 post 方法明确支持独立的 files 入参,会自动将文件对象和普通表单字段拼接为符合规范的 multipart 请求体。
    • 你使用的从 rest_framework.test 导入的 APIClient,重写了请求构造与编码逻辑,post 方法本身不接收独立的 files 形参。你额外传入的 files={"resume": fp} 会被当成无意义的额外关键字参数丢弃,不会被编码到请求体中,自然视图里的 request.FILES 为空。
  • 为什么用 Python requests 发送相同请求是正常的?
    requests 库的 post 方法原生支持独立的 files 参数,会自动完成 multipart 结构拼接,整个请求到达视图后的处理逻辑和测试客户端发出的请求完全一致——这也直接排除了请求格式不规范、中间件异常、视图解析器配置错误的可能性,问题100%出在测试请求的构造环节。
  • 代码中的隐性笔误:
    你定义的测试数据路径变量是 TEST_DIR,但实际打开文件时使用的是 FILE_DIR + file_name,如果不是你写示例时的手误,这里实际运行时会直接抛出 NameError,需要注意保持变量名统一,避免出现文件路径读取错误的问题。
正确的实现方式

按照 DRF APIClient 的规则,所有需要携带的普通表单字段、文件对象,必须统一放入 data 参数字典中,配合 format="multipart" 参数,客户端会自动完成正确的 multipart 编码:

TEST_DIR = "myapp/tests/test_data/"
file_name = "some_resume.pdf"
email_data = {
  "sender": "somedude@someprovidercom",
  "recipient": "someotherdude@someotherprovider.com",
  "subject": "New candidate",
  "stripped-html": "Check out this new candidate."
}

api = APIClient()

# 保持在文件打开的上下文中发起请求
with open(f"{TEST_DIR}{file_name}", "rb") as fp:
  # 将文件对象直接嵌入data字典,不需要单独传files参数
  email_data["resume"] = fp
  response = api.post(
    "/api/v1.0/emails/receive/",
    data=email_data,
    format="multipart",
  )

# 后续测试断言逻辑

如果你的测试不依赖本地磁盘的固定文件,更推荐使用 Django 提供的内存文件对象 SimpleUploadedFile 构造测试文件,避免路径依赖、文件句柄泄漏的问题:

from rest_framework.test import APIClient
from django.core.files.uploadedfile import SimpleUploadedFile

# 构造内存中的测试PDF文件
test_resume = SimpleUploadedFile(
    name="some_resume.pdf",
    content=b"%PDF-1.4 这是测试PDF的二进制内容",
    content_type="application/pdf"
)

email_data = {
    "sender": "somedude@someprovidercom",
    "recipient": "someotherdude@someotherprovider.com",
    "subject": "New candidate",
    "stripped-html": "Check out this new candidate.",
    "resume": test_resume
}

api = APIClient()
response = api.post(
    "/api/v1.0/emails/receive/",
    data=email_data,
    format="multipart"
)
小概率排查项

如果按照上述写法修改后依然无法获取 request.FILES,可以检查两个场景(不过根据你描述的用 requests 能正常取到文件的前提,这两个场景基本可以排除):

  • 视图的解析器配置是否包含 MultiPartParser:如果全局或视图级别的解析器列表没有覆盖 multipart 格式,DRF 会无法正确解析请求体中的文件字段。
  • 自定义中间件是否在请求流转过程中重新构造了 request 对象,导致原 request 中的 FILES 属性被清空。

内容的提问来源于stack exchange,提问作者Martin Faucheux

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.30 10:45:02