Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing multipart/form-data in django-rest-framework

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.

like image 234
maxf130 Avatar asked Dec 17 '15 17:12

maxf130


1 Answers

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.

like image 168
Ali Asgari Avatar answered Sep 21 '22 00:09

Ali Asgari