2011-04-02 19:32:37 +04:00
|
|
|
"""Django supports parsing the content of an HTTP request, but only for form POST requests.
|
|
|
|
That behaviour is sufficient for dealing with standard HTML forms, but it doesn't map well
|
|
|
|
to general HTTP requests.
|
2011-03-04 13:28:20 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
We need a method to be able to:
|
2011-03-04 13:28:20 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
1) Determine the parsed content on a request for methods other than POST (eg typically also PUT)
|
|
|
|
2) Determine the parsed content on a request for media types other than application/x-www-form-urlencoded
|
|
|
|
and multipart/form-data. (eg also handle multipart/json)
|
|
|
|
"""
|
|
|
|
from django.http.multipartparser import MultiPartParser as DjangoMPParser
|
2011-02-19 13:47:26 +03:00
|
|
|
from djangorestframework.response import ResponseException
|
|
|
|
from djangorestframework import status
|
2011-04-02 19:32:37 +04:00
|
|
|
from djangorestframework.utils import as_tuple
|
|
|
|
from djangorestframework.mediatypes import MediaType
|
2011-01-24 02:08:16 +03:00
|
|
|
|
|
|
|
try:
|
|
|
|
import json
|
|
|
|
except ImportError:
|
|
|
|
import simplejson as json
|
|
|
|
|
2011-03-04 13:28:20 +03:00
|
|
|
try:
|
|
|
|
from urlparse import parse_qs
|
|
|
|
except ImportError:
|
|
|
|
from cgi import parse_qs
|
2011-02-07 11:23:54 +03:00
|
|
|
|
|
|
|
|
2011-01-24 02:08:16 +03:00
|
|
|
|
|
|
|
class BaseParser(object):
|
2011-04-02 19:32:37 +04:00
|
|
|
"""All parsers should extend BaseParser, specifying a media_type attribute,
|
2011-01-26 11:58:09 +03:00
|
|
|
and overriding the parse() method."""
|
2011-01-26 23:31:47 +03:00
|
|
|
media_type = None
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
def __init__(self, view):
|
|
|
|
"""
|
|
|
|
Initialise the parser with the View instance as state,
|
|
|
|
in case the parser needs to access any metadata on the View object.
|
|
|
|
|
|
|
|
"""
|
|
|
|
self.view = view
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
@classmethod
|
|
|
|
def handles(self, media_type):
|
|
|
|
"""
|
|
|
|
Returns `True` if this parser is able to deal with the given MediaType.
|
|
|
|
"""
|
|
|
|
return media_type.match(self.media_type)
|
|
|
|
|
|
|
|
def parse(self, stream):
|
|
|
|
"""Given a stream to read from, return the deserialized output.
|
|
|
|
The return value may be of any type, but for many parsers it might typically be a dict-like object."""
|
|
|
|
raise NotImplementedError("BaseParser.parse() Must be overridden to be implemented.")
|
2011-01-24 02:08:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
class JSONParser(BaseParser):
|
2011-04-02 19:32:37 +04:00
|
|
|
media_type = MediaType('application/json')
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
def parse(self, stream):
|
2011-01-24 02:08:16 +03:00
|
|
|
try:
|
2011-04-02 19:32:37 +04:00
|
|
|
return json.load(stream)
|
2011-01-24 02:08:16 +03:00
|
|
|
except ValueError, exc:
|
|
|
|
raise ResponseException(status.HTTP_400_BAD_REQUEST, {'detail': 'JSON parse error - %s' % str(exc)})
|
|
|
|
|
2011-01-26 11:58:09 +03:00
|
|
|
|
2011-03-04 18:06:44 +03:00
|
|
|
class DataFlatener(object):
|
2011-03-10 17:49:11 +03:00
|
|
|
"""Utility object for flatening dictionaries of lists. Useful for "urlencoded" decoded data."""
|
2011-03-04 18:06:44 +03:00
|
|
|
|
|
|
|
def flatten_data(self, data):
|
2011-03-10 17:49:11 +03:00
|
|
|
"""Given a data dictionary {<key>: <value_list>}, returns a flattened dictionary
|
|
|
|
with information provided by the method "is_a_list"."""
|
2011-03-04 18:06:44 +03:00
|
|
|
flatdata = dict()
|
2011-03-11 15:34:39 +03:00
|
|
|
for key, val_list in data.items():
|
|
|
|
if self.is_a_list(key, val_list):
|
|
|
|
flatdata[key] = val_list
|
2011-03-04 18:06:44 +03:00
|
|
|
else:
|
2011-03-11 15:34:39 +03:00
|
|
|
if val_list:
|
|
|
|
flatdata[key] = val_list[0]
|
2011-03-04 18:06:44 +03:00
|
|
|
else:
|
2011-03-11 15:34:39 +03:00
|
|
|
# If the list is empty, but the parameter is not a list,
|
|
|
|
# we strip this parameter.
|
|
|
|
data.pop(key)
|
2011-03-04 18:06:44 +03:00
|
|
|
return flatdata
|
|
|
|
|
2011-03-11 15:34:39 +03:00
|
|
|
def is_a_list(self, key, val_list):
|
2011-03-10 17:49:11 +03:00
|
|
|
"""Returns True if the parameter with name *key* is expected to be a list, or False otherwise.
|
2011-03-11 15:34:39 +03:00
|
|
|
*val_list* which is the received value for parameter *key* can be used to guess the answer."""
|
2011-03-04 18:06:44 +03:00
|
|
|
return False
|
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
|
2011-04-11 14:54:26 +04:00
|
|
|
class PlainTextParser(BaseParser):
|
|
|
|
"""
|
|
|
|
Plain text parser.
|
|
|
|
|
|
|
|
Simply returns the content of the stream
|
|
|
|
"""
|
|
|
|
media_type = MediaType('text/plain')
|
|
|
|
|
|
|
|
def parse(self, stream):
|
|
|
|
return stream.read()
|
|
|
|
|
|
|
|
|
2011-03-04 18:06:44 +03:00
|
|
|
class FormParser(BaseParser, DataFlatener):
|
2011-01-24 02:08:16 +03:00
|
|
|
"""The default parser for form data.
|
|
|
|
Return a dict containing a single value for each non-reserved parameter.
|
2011-03-10 17:49:11 +03:00
|
|
|
|
|
|
|
In order to handle select multiple (and having possibly more than a single value for each parameter),
|
2011-03-11 16:05:35 +03:00
|
|
|
you can customize the output by subclassing the method 'is_a_list'."""
|
2011-03-10 17:49:11 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
media_type = MediaType('application/x-www-form-urlencoded')
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2011-03-10 17:49:11 +03:00
|
|
|
"""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'
|
2011-04-02 19:32:37 +04:00
|
|
|
RESERVED_FORM_PARAMS = ('csrfmiddlewaretoken',)
|
2011-03-04 18:23:18 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
def parse(self, stream):
|
|
|
|
data = parse_qs(stream.read(), keep_blank_values=True)
|
2011-03-04 18:06:44 +03:00
|
|
|
|
2011-03-11 15:34:39 +03:00
|
|
|
# removing EMPTY_VALUEs from the lists and flatening the data
|
|
|
|
for key, val_list in data.items():
|
|
|
|
self.remove_empty_val(val_list)
|
2011-03-04 18:06:44 +03:00
|
|
|
data = self.flatten_data(data)
|
2011-01-24 02:08:16 +03:00
|
|
|
|
|
|
|
# Strip any parameters that we are treating as reserved
|
2011-03-08 18:19:55 +03:00
|
|
|
for key in data.keys():
|
2011-04-02 19:32:37 +04:00
|
|
|
if key in self.RESERVED_FORM_PARAMS:
|
2011-03-04 13:28:20 +03:00
|
|
|
data.pop(key)
|
2011-04-02 19:32:37 +04:00
|
|
|
|
2011-01-24 02:08:16 +03:00
|
|
|
return data
|
|
|
|
|
2011-03-04 18:23:18 +03:00
|
|
|
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)
|
|
|
|
|
2011-02-15 11:19:57 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
class MultipartData(dict):
|
|
|
|
def __init__(self, data, files):
|
|
|
|
dict.__init__(self, data)
|
|
|
|
self.FILES = files
|
|
|
|
|
|
|
|
class MultipartParser(BaseParser, DataFlatener):
|
|
|
|
media_type = MediaType('multipart/form-data')
|
|
|
|
RESERVED_FORM_PARAMS = ('csrfmiddlewaretoken',)
|
2011-03-04 13:28:20 +03:00
|
|
|
|
2011-04-02 19:32:37 +04:00
|
|
|
def parse(self, stream):
|
|
|
|
upload_handlers = self.view.request._get_upload_handlers()
|
|
|
|
django_mpp = DjangoMPParser(self.view.request.META, stream, upload_handlers)
|
2011-03-10 17:03:46 +03:00
|
|
|
data, files = django_mpp.parse()
|
2011-03-04 18:06:44 +03:00
|
|
|
|
2011-03-04 18:23:18 +03:00
|
|
|
# Flatening data, files and combining them
|
2011-03-11 16:05:35 +03:00
|
|
|
data = self.flatten_data(dict(data.iterlists()))
|
|
|
|
files = self.flatten_data(dict(files.iterlists()))
|
2011-04-02 19:32:37 +04:00
|
|
|
|
2011-03-04 13:28:20 +03:00
|
|
|
# Strip any parameters that we are treating as reserved
|
2011-03-08 18:19:55 +03:00
|
|
|
for key in data.keys():
|
2011-04-02 19:32:37 +04:00
|
|
|
if key in self.RESERVED_FORM_PARAMS:
|
2011-03-04 13:28:20 +03:00
|
|
|
data.pop(key)
|
2011-04-02 19:32:37 +04:00
|
|
|
|
|
|
|
return MultipartData(data, files)
|