在Django Rest Framework中解析multipart/form-data数据

13

在使用django-rest-framework时,我遇到了解析多部分表单数据的一些困难。我已经设置了一个最小视图,只是回显请求数据:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser


class FileUpload(APIView):
    parser_classes = (MultiPartParser, FormParser, )

    def post(self, request, format=None, *args, **kwargs):
        return Response({'raw': request.data, 'data': request._request.POST,
                         'files': str(request._request.FILES)})

我希望raw(我承认名称有点不好)实际上包含与request._request.POSTrequest._request.FILES相同的数据。

如果我使用Content-Type = application / x-www-form-urlencoded向视图POST,则可以按预期工作:

$ http -v --form POST http://localhost:8000/upload/api/ course=3 name=name

POST /upload/api/ HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 18
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: localhost:8000
User-Agent: HTTPie/0.9.2

course=3&name=name

HTTP/1.0 200 OK
Allow: POST, OPTIONS
Content-Type: application/json
Date: Thu, 17 Dec 2015 16:52:37 GMT
Server: WSGIServer/0.2 CPython/3.4.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "data": {
        "course": "3",
        "name": "name"
    },
    "files": "<MultiValueDict: {}>",
    "raw": {
        "course": "3",
        "name": "name"
    }
}

但是,如果我使用Content-Type=multipart/form-data发布,我会得到以下结果:

$ http -v --form POST http://localhost:8000/upload/api/ file@~/Projects/lms/manage.py course=3 name=name
POST /upload/api/ HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 577
Content-Type: multipart/form-data; boundary=634ec7c7e89a487b89c1c07c0d24744c
Host: localhost:8000
User-Agent: HTTPie/0.9.2

--634ec7c7e89a487b89c1c07c0d24744c
Content-Disposition: form-data; name="course"

3
--634ec7c7e89a487b89c1c07c0d24744c
Content-Disposition: form-data; name="name"

name
--634ec7c7e89a487b89c1c07c0d24744c
Content-Disposition: form-data; name="file"; filename="manage.py"

#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")

    from django.core.management import execute_from_command_line

    execute_from_command_line(sys.argv)

--634ec7c7e89a487b89c1c07c0d24744c--

HTTP/1.0 200 OK
Allow: POST, OPTIONS
Content-Type: application/json
Date: Thu, 17 Dec 2015 16:55:44 GMT
Server: WSGIServer/0.2 CPython/3.4.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "data": {
        "course": "3",
        "name": "name"
    },
    "files": "<MultiValueDict: {'file': [<InMemoryUploadedFile: manage.py ()>]}>",
    "raw": {}
}

我是否漏掉了什么重要的东西?我在此使用HTTPIE生成请求,但curl也存在相同的行为,所以我相信那不是问题。我正在使用djangorestframework==3.3.0和Django==1.8.4

编辑:

似乎将请求方法改为PUT(其他内容相同)可以达到期望的结果:

$ http -v --form PUT http://localhost:8000/upload/api/ file@~/Projects/lms/manage.py course=3 name=name
PUT /upload/api/ HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 577
Content-Type: multipart/form-data; boundary=98feb59a8abe4bfb95a7321f536ed800
Host: localhost:8000
User-Agent: HTTPie/0.9.2

--98feb59a8abe4bfb95a7321f536ed800
Content-Disposition: form-data; name="course"

3
--98feb59a8abe4bfb95a7321f536ed800
Content-Disposition: form-data; name="name"

name
--98feb59a8abe4bfb95a7321f536ed800
Content-Disposition: form-data; name="file"; filename="manage.py"

#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")

    from django.core.management import execute_from_command_line

    execute_from_command_line(sys.argv)

--98feb59a8abe4bfb95a7321f536ed800--

HTTP/1.0 200 OK
Allow: POST, PUT, OPTIONS
Content-Type: application/json
Date: Thu, 17 Dec 2015 18:10:34 GMT
Server: WSGIServer/0.2 CPython/3.4.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "data": {},
    "files": "<MultiValueDict: {}>",
    "raw": "<QueryDict: {'name': ['name'], 'course': ['3'], 'file': [<InMemoryUploadedFile: manage.py ()>]}>"
 }

所以我可以只使用PUT。但是这并不理想,因为客户端无法控制文件的名称或在服务器上的位置。POST更适合这种情况。无论如何,我不明白为什么PUT能够工作而POST不能。


你尝试过完全省略帖子的内容类型吗?根据这个回答:如果不传递内容类型给post方法,它会自动设置为multipart/form-data。 - Geoff Walmsley
1个回答

6
你使用的版本存在一个已知问题,与这里描述的相同。将 Django Rest Framework 升级到最新版本即可解决该问题。不过,你可以使用 PUT 请求来绕过此问题。

确实,那也是我最终得出的结论。感谢您在 Github 上引用了这个问题,这意味着这个问题最终有了权威的答案! - diestl
@maxf130 我也遇到了同样的问题,你能帮忙吗??https://stackoverflow.com/questions/67976100/expected-a-list-of-items-but-got-type-str-in-django-rest-framework 你可以查看更新后的代码。 - Reactoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接