13

I'm having some difficulties parsing multipart form data when using the django-rest-framework. I've set up a minimal view to just echo back the request data:

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)})

I am expecting that raw (slightly badly named I admit) contains effectively the same data as request._request.POST and request._request.FILES.

If I POST to the view with Content-Type= application/x-www-form-urlencoded this works as expected:

$ 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"
    }
}

However if I post with Content-Type=multipart/form-data I get the following:

$ 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": {}
}

Am I missing something here? I am using HTTPIE to generate the requests here but the same behaviour exists with curl so I'm pretty sure that is not the problem. I am using djangorestframework==3.3.0 and Django==1.8.4

EDIT:

It seems that PUTing to the url (with an otherwise identical request) achieves the desired result:

$ 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 ()>]}>"
 }

So I could just use PUT. That is however not ideal as the client does not control what the file is named or where it is located on the server. POST is more appropriate in that sense. In any sense, I don't see why PUT works when POST doesn't.

1
  • Have you tried leaving the content type out of the post altogether? According to this answer: if no content type is passed to the post method it automatically sets multipart/form-data Commented Jun 25, 2019 at 20:48

1 Answer 1

6

It is a known issue for the version you're using. Upgrading django rest framework to the latest version will solve the problem. However, you can PUT the request as a workaround.

Sign up to request clarification or add additional context in comments.

2 Comments

Indeed, that's what I figured out eventually too. Thanks for referencing the issue on Github, it means this question finally has an authoritative answer!
@maxf130 I have the same issue, can you please help??stackoverflow.com/questions/67976100/… You can check the updated code

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.