Okay, I forgot about your headers. According to the spec:
Content-Type = "Content-Type" ":" media-type
MIME provides for a number of "multipart" types -- encapsulations of
one or more entities within a single message-body.
All multipart types share a common syntax, ... and MUST include a boundary parameter as part of the media type
value.
Here is what a request containing multipart/form-data looks like:
POST /myapp/company/ HTTP/1.1
Host: localhost:8000
Content-Length: 265
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.9.0
Connection: keep-alive
Content-Type: multipart/form-data; boundary=63c5979328c44e2c869349443a94200e
--63c5979328c44e2c869349443a94200e
Content-Disposition: form-data; name="hello"
world
--63c5979328c44e2c869349443a94200e
Content-Disposition: form-data; name="mydata"; filename="data.txt"
line 1
line 2
line 3
line 4
--63c5979328c44e2c869349443a94200e--
See how the sections of data are separated by the boundary:
--63c5979328c44e2c869349443a94200e--
The idea is to use something for a boundary that is unlikely to appear in the data. Note that the boundary was included in the Content-Type
header of the request.
That request was produced by this code:
import requests
myfile = {'mydata': open('data.txt','rb')}
r = requests.post(url,
#headers = myheaders
data = {'hello': 'world'},
files = myfile
)
It looks like you were paying careful attention to the following note in the django-rest-framework docs:
Note: When developing client applications always remember to make sure
you're setting the Content-Type header when sending data in an HTTP
request.
If you don't set the content type, most clients will default to using
'application/x-www-form-urlencoded', which may not be what you wanted.
But when you are using requests
, if you specify the Content-Type
header yourself, then requests
assumes that you know what you're doing, and it doesn't overwrite your Content-Type
header with the Content-Type
header it would have provided.
You didn't provide the boundary in your Content-Type
header--as required. How could you? You didn't assemble the body of the request and create a boundary to separate the various pieces of data, so you couldn't possibly know what the boundary is.
When the django-rest-framework
note says that you should include a Content-Type
header in your request, what that really means is:
You or any programs you use to create the request need to include a
Content-Type
header.
So @AChampion was exactly right in the comments: let requests
provide the Content-Type header
, after all the requests
docs advertise:
Requests takes all of the work out of Python HTTP/1.1
requests
works like this: if you provide a files
keyword arg, then requests uses a Content-Type
header of multipart/form-data
and also specifies a boundary in the header; then requests
assembles the body of the request using the boundary. If you provide a data
keyword argument then requests uses a Content-Type
of application/x-www-form-urlencoded
, which just assembles all the keys and values in the dictionary into this format:
x=10&y=20
No boundary required.
And, if you provide both a files
keyword arg and a data
keyword arg, then requests uses a Content-Type
of multipart/form-data
.