fix for PUT files

This commit is contained in:
spiq 2011-03-04 12:28:20 +02:00
parent 30fd23d7f5
commit 91b33659b5
2 changed files with 80 additions and 36 deletions

View File

@ -24,6 +24,27 @@ class StandardContentMixin(ContentMixin):
return None return None
return (request.META.get('CONTENT_TYPE', None), request.raw_post_data) return (request.META.get('CONTENT_TYPE', None), request.raw_post_data)
from django.core.files.base import File
class SocketFile(File):
# Only forward access is allowed
def __init__(self, socket, size):
super(SocketFile, self).__init__(socket)
self._size = int(size)
self._pos = 0
def read(self, num_bytes=None):
if num_bytes is None:
num_bytes = self._size - self._pos
else:
num_bytes = min(num_bytes, self._size - self._pos)
self._pos += num_bytes
return self.file.read(num_bytes)
def tell(self):
return self._pos
def seek(self, position):
pass
class OverloadedContentMixin(ContentMixin): class OverloadedContentMixin(ContentMixin):
"""HTTP request content behaviour that also allows arbitrary content to be tunneled in form data.""" """HTTP request content behaviour that also allows arbitrary content to be tunneled in form data."""
@ -39,7 +60,7 @@ class OverloadedContentMixin(ContentMixin):
Note that content_type may be None if it is unset.""" Note that content_type may be None if it is unset."""
if not request.META.get('CONTENT_LENGTH', None) and not request.META.get('TRANSFER_ENCODING', None): if not request.META.get('CONTENT_LENGTH', None) and not request.META.get('TRANSFER_ENCODING', None):
return None return None
content_type = request.META.get('CONTENT_TYPE', None) content_type = request.META.get('CONTENT_TYPE', None)
if (request.method == 'POST' and self.CONTENT_PARAM and if (request.method == 'POST' and self.CONTENT_PARAM and
@ -51,5 +72,13 @@ class OverloadedContentMixin(ContentMixin):
content_type = request.POST.get(self.CONTENTTYPE_PARAM, None) content_type = request.POST.get(self.CONTENTTYPE_PARAM, None)
return (content_type, request.POST[self.CONTENT_PARAM]) return (content_type, request.POST[self.CONTENT_PARAM])
elif request.method == 'PUT':
return (content_type, request.raw_post_data) f = SocketFile(request.environ['wsgi.input'], request.META['CONTENT_LENGTH'])
returned = (content_type, f.read())
return returned
#try:
# f.close()
#except Exception as e:
# print 'exception', e
else:
return (content_type, request.raw_post_data)

View File

@ -1,3 +1,7 @@
from StringIO import StringIO
from django.http.multipartparser import MultiPartParser as DjangoMPParser
from djangorestframework.response import ResponseException from djangorestframework.response import ResponseException
from djangorestframework import status from djangorestframework import status
@ -6,6 +10,10 @@ try:
except ImportError: except ImportError:
import simplejson as json import simplejson as json
try:
from urlparse import parse_qs
except ImportError:
from cgi import parse_qs
class ParserMixin(object): class ParserMixin(object):
parsers = () parsers = ()
@ -75,50 +83,57 @@ class FormParser(BaseParser):
"""The default parser for form data. """The default parser for form data.
Return a dict containing a single value for each non-reserved parameter. Return a dict containing a single value for each non-reserved parameter.
""" """
# TODO: not good, because posted/put lists are flattened !!!
media_type = 'application/x-www-form-urlencoded' media_type = 'application/x-www-form-urlencoded'
def parse(self, input): def parse(self, input):
# The FormParser doesn't parse the input as other parsers would, since Django's already done the
# form parsing for us. We build the content object from the request directly.
request = self.resource.request request = self.resource.request
if request.method == 'PUT': if request.method == 'PUT':
# Fix from piston to force Django to give PUT requests the same data = parse_qs(input)
# form processing that POST requests get... # Flattening the parsed query data
# for key, val in data.items():
# Bug fix: if _load_post_and_files has already been called, for data[key] = val[0]
# example by middleware accessing request.POST, the below code to
# pretend the request is a POST instead of a PUT will be too late if request.method == 'POST':
# to make a difference. Also calling _load_post_and_files will result # Django has already done the form parsing for us.
# in the following exception: data = dict(request.POST.items())
# AttributeError: You cannot set the upload handlers after the upload has been processed.
# The fix is to check for the presence of the _post field which is set
# the first time _load_post_and_files is called (both by wsgi.py and
# modpython.py). If it's set, the request has to be 'reset' to redo
# the query value parsing in POST mode.
if hasattr(request, '_post'):
del request._post
del request._files
try:
request.method = "POST"
request._load_post_and_files()
request.method = "PUT"
except AttributeError:
request.META['REQUEST_METHOD'] = 'POST'
request._load_post_and_files()
request.META['REQUEST_METHOD'] = 'PUT'
# Strip any parameters that we are treating as reserved # Strip any parameters that we are treating as reserved
data = {} for key in data:
for (key, val) in request.POST.items(): if key in self.resource.RESERVED_FORM_PARAMS:
if key not in self.resource.RESERVED_FORM_PARAMS: data.pop(key)
data[key] = val
return data return data
# TODO: Allow parsers to specify multiple media_types # TODO: Allow parsers to specify multiple media_types
class MultipartParser(FormParser): class MultipartParser(FormParser):
media_type = 'multipart/form-data' media_type = 'multipart/form-data'
def parse(self, input):
request = self.resource.request
if request.method == 'PUT':
upload_handlers = request._get_upload_handlers()
django_mpp = DjangoMPParser(request.META, StringIO(input), upload_handlers)
data, files = django_mpp.parse()
data = dict(data)
files = dict(files)
if request.method == 'POST':
# Django has already done the form parsing for us.
data = dict(request.POST)
files = dict(request.FILES)
# Flattening, then merging the POSTED/PUT data/files
for key, val in dict(data).items():
data[key] = val[0]
for key, val in dict(files).items():
files[key] = val[0].read()
data.update(files)
# Strip any parameters that we are treating as reserved
for key in data:
if key in self.resource.RESERVED_FORM_PARAMS:
data.pop(key)
return data