diff options
Diffstat (limited to 'djangorestframework/parsers.py')
| -rw-r--r-- | djangorestframework/parsers.py | 121 |
1 files changed, 82 insertions, 39 deletions
diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index 7c686ca8..3bd020ad 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 = () @@ -70,55 +78,90 @@ class JSONParser(BaseParser): class XMLParser(BaseParser): media_type = 'application/xml' - -class FormParser(BaseParser): +class DataFlatener(object): + """Utility object for flatening dictionaries of lists. Useful for "urlencoded" decoded data.""" + + def flatten_data(self, data): + """Given a data dictionary {<key>: <value_list>}, returns a flattened dictionary + with information provided by the method "is_a_list".""" + flatdata = dict() + for key, val_list in data.items(): + if self.is_a_list(key, val_list): + flatdata[key] = val_list + else: + if val_list: + flatdata[key] = val_list[0] + else: + # If the list is empty, but the parameter is not a list, + # we strip this parameter. + data.pop(key) + return flatdata + + def is_a_list(self, key, val_list): + """Returns True if the parameter with name *key* is expected to be a list, or False otherwise. + *val_list* which is the received value for parameter *key* can be used to guess the answer.""" + return False + +class FormParser(BaseParser, DataFlatener): """The default parser for form data. Return a dict containing a single value for each non-reserved parameter. - """ - + + In order to handle select multiple (and having possibly more than a single value for each parameter), + you can customize the output by subclassing the method 'is_a_list'.""" + media_type = 'application/x-www-form-urlencoded' + """The value of the parameter when the select multiple is empty. + Browsers are usually stripping the select multiple that have no option selected from the parameters sent. + A common hack to avoid this is to send the parameter with a value specifying that the list is empty. + This value will always be stripped before the data is returned.""" + EMPTY_VALUE = '_empty' + 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 + data = parse_qs(input, keep_blank_values=True) - 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' + # removing EMPTY_VALUEs from the lists and flatening the data + for key, val_list in data.items(): + self.remove_empty_val(val_list) + data = self.flatten_data(data) # 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.keys(): + if key in self.resource.RESERVED_FORM_PARAMS: + data.pop(key) return data + def remove_empty_val(self, val_list): + """ """ + while(1): # Because there might be several times EMPTY_VALUE in the list + try: + ind = val_list.index(self.EMPTY_VALUE) + except ValueError: + break + else: + val_list.pop(ind) + # TODO: Allow parsers to specify multiple media_types -class MultipartParser(FormParser): +class MultipartParser(BaseParser, DataFlatener): media_type = 'multipart/form-data' + def parse(self, input): + + request = self.resource.request + #TODO : that's pretty dumb : files are loaded with + #upload_handlers, but as we read the request body completely (input), + #then it kind of misses the point. Why not input as a stream ? + upload_handlers = request._get_upload_handlers() + django_mpp = DjangoMPParser(request.META, StringIO(input), upload_handlers) + data, files = django_mpp.parse() + + # Flatening data, files and combining them + data = self.flatten_data(dict(data.iterlists())) + files = self.flatten_data(dict(files.iterlists())) + data.update(files) + + # Strip any parameters that we are treating as reserved + for key in data.keys(): + if key in self.resource.RESERVED_FORM_PARAMS: + data.pop(key) + return data |
