The problem is the Content-Type
in your request' headers isn't correctly set. [Reference]
Tastypie only recognizes xml
, json
, yaml
and bplist
. So when sending the POST request, you need to set Content-Type
in the request headers to either one of them (eg., application/json
).
EDIT:
It seems like you are trying to send a multipart form with files through
Tastypie.
A little background on Tastypie's file upload support by Issac Kelly for
roadmap 1.0 final (hasn't released yet):
- Implement a Base64FileField which accepts base64 encoded files (like the one in issue #42) for PUT/POST, and provides the URL for GET requests. This will be part of the main tastypie repo.
- We'd like to encourage other implementations to implement as independent projects. There's several ways to do this, and most of them are slightly finicky, and they all have different drawbacks, We'd like to have other options, and document the pros and cons of each
That means for now at least, Tastypie does not officially support multipart
file upload. However, there are forks in the wild that are supposedly working
well, this is one of
them. I haven't tested it though.
Now let me try to explain why you are encountering that error.
In Tastypie resource.py
, line 452:
def dispatch(self, request_type, request, **kwargs):
"""
Handles the common operations (allowed HTTP method, authentication,
throttling, method lookup) surrounding most CRUD interactions.
"""
allowed_methods = getattr(self._meta, "%s_allowed_methods" % request_type, None)
if 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
request.method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
request_method = self.method_check(request, allowed=allowed_methods)
method = getattr(self, "%s_%s" % (request_method, request_type), None)
if method is None:
raise ImmediateHttpResponse(response=http.HttpNotImplemented())
self.is_authenticated(request)
self.is_authorized(request)
self.throttle_check(request)
# All clear. Process the request.
request = convert_post_to_put(request)
response = method(request, **kwargs)
# Add the throttled request.
self.log_throttled_access(request)
# If what comes back isn't a ``HttpResponse``, assume that the
# request was accepted and that some action occurred. This also
# prevents Django from freaking out.
if not isinstance(response, HttpResponse):
return http.HttpNoContent()
return response
convert_post_to_put(request)
is called from here. And here is the code for
convert_post_to_put
:
# Based off of ``piston.utils.coerce_put_post``. Similarly BSD-licensed.
# And no, the irony is not lost on me.
def convert_post_to_VERB(request, verb):
"""
Force Django to process the VERB.
"""
if request.method == verb:
if hasattr(request, '_post'):
del(request._post)
del(request._files)
try:
request.method = "POST"
request._load_post_and_files()
request.method = verb
except AttributeError:
request.META['REQUEST_METHOD'] = 'POST'
request._load_post_and_files()
request.META['REQUEST_METHOD'] = verb
setattr(request, verb, request.POST)
return request
def convert_post_to_put(request):
return convert_post_to_VERB(request, verb='PUT')
And this method isn't really intended to handled multipart as it has
side-effect of preventing any further accesses to request.body
because
_load_post_and_files()
method will set _read_started
flag to True
:
Django request.body
and _load_post_and_files()
:
@property
def body(self):
if not hasattr(self, '_body'):
if self._read_started:
raise Exception("You cannot access body after reading from request's data stream")
try:
self._body = self.read()
except IOError as e:
six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
self._stream = BytesIO(self._body)
return self._body
def read(self, *args, **kwargs):
self._read_started = True
return self._stream.read(*args, **kwargs)
def _load_post_and_files(self):
# Populates self._post and self._files
if self.method != 'POST':
self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
return
if self._read_started and not hasattr(self, '_body'):
self._mark_post_parse_error()
return
if self.META.get('CONTENT_TYPE', '').startswith('multipart'):
if hasattr(self, '_body'):
# Use already read data
data = BytesIO(self._body)
else:
data = self
try:
self._post, self._files = self.parse_file_upload(self.META, data)
except:
# An error occured while parsing POST data. Since when
# formatting the error the request handler might access
# self.POST, set self._post and self._file to prevent
# attempts to parse POST data again.
# Mark that an error occured. This allows self.__repr__ to
# be explicit about it instead of simply representing an
# empty POST
self._mark_post_parse_error()
raise
else:
self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
So, you can (though probably shouldn't) monkey-patch Tastypie's
convert_post_to_VERB()
method by setting request._body
by calling
request.body
and then immediately set _read_started=False
so that
_load_post_and_files()
will read from _body
and won't set
_read_started=True
:
def convert_post_to_VERB(request, verb):
"""
Force Django to process the VERB.
"""
if request.method == verb:
if hasattr(request, '_post'):
del(request._post)
del(request._files)
request.body # now request._body is set
request._read_started = False # so it won't cause side effects
try:
request.method = "POST"
request._load_post_and_files()
request.method = verb
except AttributeError:
request.META['REQUEST_METHOD'] = 'POST'
request._load_post_and_files()
request.META['REQUEST_METHOD'] = verb
setattr(request, verb, request.POST)
return request
request.POST
... Django saves the POST variables on therequest
object for you to do exactly that. I don't think there's a problem with this code directly... I suspect TastyPie's doing something odd - maybe check your request has the correct Content-type headers, etc? As a side point, it's a good idea to either use e.g.token = request.POST.get('token', some_default)
or catch and deal with theKeyError
thrown if that parameter doesn't exist - maybe by returning an HTTP 401. – Saucyrequest.body
, but the data is available inrequest.data
– Roma