diff --git a/djangorestframework/content.py b/djangorestframework/content.py index d612a2ee0..abe2069ed 100644 --- a/djangorestframework/content.py +++ b/djangorestframework/content.py @@ -24,6 +24,27 @@ class StandardContentMixin(ContentMixin): return None 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): """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.""" if not request.META.get('CONTENT_LENGTH', None) and not request.META.get('TRANSFER_ENCODING', None): return None - + content_type = request.META.get('CONTENT_TYPE', None) 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) return (content_type, request.POST[self.CONTENT_PARAM]) - - return (content_type, request.raw_post_data) \ No newline at end of file + elif request.method == 'PUT': + 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) diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index 7c686ca81..e5dd7df47 100644 --- a/djangorestframework/parsers.py +++ b/djangorestframework/parsers.py @@ -1,3 +1,7 @@ +from StringIO import StringIO + +from django.http.multipartparser import MultiPartParser as DjangoMPParser + from djangorestframework.response import ResponseException from djangorestframework import status @@ -6,6 +10,10 @@ try: except ImportError: import simplejson as json +try: + from urlparse import parse_qs +except ImportError: + from cgi import parse_qs class ParserMixin(object): parsers = () @@ -75,50 +83,57 @@ class FormParser(BaseParser): """The default parser for form data. 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' 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 if request.method == 'PUT': - # Fix from piston to force Django to give PUT requests the same - # form processing that POST requests get... - # - # Bug fix: if _load_post_and_files has already been called, for - # example by middleware accessing request.POST, the below code to - # pretend the request is a POST instead of a PUT will be too late - # to make a difference. Also calling _load_post_and_files will result - # in the following exception: - # 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' + data = parse_qs(input) + # Flattening the parsed query data + for key, val in data.items(): + data[key] = val[0] + + if request.method == 'POST': + # Django has already done the form parsing for us. + data = dict(request.POST.items()) # Strip any parameters that we are treating as reserved - data = {} - for (key, val) in request.POST.items(): - if key not in self.resource.RESERVED_FORM_PARAMS: - data[key] = val - + for key in data: + if key in self.resource.RESERVED_FORM_PARAMS: + data.pop(key) return data # TODO: Allow parsers to specify multiple media_types class MultipartParser(FormParser): 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 +