2011-03-04 13:28:20 +03:00
|
|
|
from StringIO import StringIO
|
|
|
|
|
|
|
|
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-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
|
|
|
|
|
|
|
class ParserMixin(object):
|
|
|
|
parsers = ()
|
|
|
|
|
|
|
|
def parse(self, content_type, content):
|
|
|
|
# See RFC 2616 sec 3 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
|
|
|
split = content_type.split(';', 1)
|
|
|
|
if len(split) > 1:
|
|
|
|
content_type = split[0]
|
|
|
|
content_type = content_type.strip()
|
|
|
|
|
|
|
|
media_type_to_parser = dict([(parser.media_type, parser) for parser in self.parsers])
|
|
|
|
|
|
|
|
try:
|
|
|
|
parser = media_type_to_parser[content_type]
|
|
|
|
except KeyError:
|
|
|
|
raise ResponseException(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
|
|
|
|
{'error': 'Unsupported media type in request \'%s\'.' % content_type})
|
|
|
|
|
|
|
|
return parser(self).parse(content)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def parsed_media_types(self):
|
|
|
|
"""Return an list of all the media types that this ParserMixin can parse."""
|
|
|
|
return [parser.media_type for parser in self.parsers]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def default_parser(self):
|
|
|
|
"""Return the ParerMixin's most prefered emitter.
|
|
|
|
(This has no behavioural effect, but is may be used by documenting emitters)"""
|
|
|
|
return self.parsers[0]
|
|
|
|
|
2011-01-24 02:08:16 +03:00
|
|
|
|
|
|
|
class BaseParser(object):
|
2011-01-26 11:58:09 +03:00
|
|
|
"""All parsers should extend BaseParser, specifing a media_type attribute,
|
|
|
|
and overriding the parse() method."""
|
|
|
|
|
2011-01-26 23:31:47 +03:00
|
|
|
media_type = None
|
2011-01-24 02:08:16 +03:00
|
|
|
|
|
|
|
def __init__(self, resource):
|
2011-01-26 11:58:09 +03:00
|
|
|
"""Initialise the parser with the Resource instance as state,
|
|
|
|
in case the parser needs to access any metadata on the Resource object."""
|
2011-01-24 02:08:16 +03:00
|
|
|
self.resource = resource
|
|
|
|
|
|
|
|
def parse(self, input):
|
2011-01-26 11:58:09 +03:00
|
|
|
"""Given some serialized input, return the deserialized output.
|
|
|
|
The input will be the raw request content body. The return value may be of
|
|
|
|
any type, but for many parsers/inputs it might typically be a dict."""
|
|
|
|
return input
|
2011-01-24 02:08:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
class JSONParser(BaseParser):
|
2011-01-26 23:31:47 +03:00
|
|
|
media_type = 'application/json'
|
2011-01-24 02:08:16 +03:00
|
|
|
|
|
|
|
def parse(self, input):
|
|
|
|
try:
|
|
|
|
return json.loads(input)
|
|
|
|
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-01-24 02:08:16 +03:00
|
|
|
class XMLParser(BaseParser):
|
2011-01-26 23:31:47 +03:00
|
|
|
media_type = 'application/xml'
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2011-03-04 18:06:44 +03:00
|
|
|
class DataFlatener(object):
|
2011-03-04 18:23:18 +03:00
|
|
|
#TODO : document + test
|
2011-03-04 18:06:44 +03:00
|
|
|
|
|
|
|
def flatten_data(self, data):
|
|
|
|
"""Given a data dictionary ``{<attr_name>: <value_list>}``, returns a flattened dictionary according to :meth:`FormParser.is_a_list`.
|
|
|
|
"""
|
|
|
|
flatdata = dict()
|
|
|
|
for attr_name, attr_value in data.items():
|
|
|
|
if self.is_a_list(attr_name):
|
|
|
|
if isinstance(attr_value, list):
|
|
|
|
flatdata[attr_name] = attr_value
|
|
|
|
else:
|
|
|
|
flatdata[attr_name] = [attr_value]
|
|
|
|
else:
|
|
|
|
if isinstance(attr_value, list):
|
|
|
|
flatdata[attr_name] = attr_value[0]
|
|
|
|
else:
|
|
|
|
flatdata[attr_name] = attr_value
|
|
|
|
return flatdata
|
|
|
|
|
|
|
|
def is_a_list(self, attr_name):
|
|
|
|
""" """
|
|
|
|
return False
|
|
|
|
|
|
|
|
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-04 18:23:18 +03:00
|
|
|
# TODO: document flatening
|
2011-03-04 18:06:44 +03:00
|
|
|
# TODO: writing tests for PUT files + normal data
|
2011-03-04 18:23:18 +03:00
|
|
|
# TODO: document EMPTY workaround
|
2011-01-26 23:31:47 +03:00
|
|
|
media_type = 'application/x-www-form-urlencoded'
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2011-03-04 18:23:18 +03:00
|
|
|
EMPTY_VALUE = 'EMPTY'
|
|
|
|
|
2011-01-24 02:08:16 +03:00
|
|
|
def parse(self, input):
|
|
|
|
request = self.resource.request
|
|
|
|
|
|
|
|
if request.method == 'PUT':
|
2011-03-04 13:28:20 +03:00
|
|
|
data = parse_qs(input)
|
2011-03-04 18:23:18 +03:00
|
|
|
elif request.method == 'POST':
|
2011-03-04 13:28:20 +03:00
|
|
|
# Django has already done the form parsing for us.
|
2011-03-04 18:06:44 +03:00
|
|
|
data = request.POST
|
|
|
|
|
2011-03-04 18:23:18 +03:00
|
|
|
# Flatening data and removing EMPTY_VALUEs from the lists
|
2011-03-04 18:06:44 +03:00
|
|
|
data = self.flatten_data(data)
|
2011-03-04 18:23:18 +03:00
|
|
|
for key in filter(lambda k: self.is_a_list(k), data):
|
|
|
|
self.remove_empty_val(data[key])
|
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-03-04 13:28:20 +03:00
|
|
|
if key in self.resource.RESERVED_FORM_PARAMS:
|
|
|
|
data.pop(key)
|
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-19 13:26:27 +03:00
|
|
|
# TODO: Allow parsers to specify multiple media_types
|
2011-03-04 18:06:44 +03:00
|
|
|
class MultipartParser(BaseParser, DataFlatener):
|
2011-02-15 11:19:57 +03:00
|
|
|
media_type = 'multipart/form-data'
|
|
|
|
|
2011-03-04 13:28:20 +03:00
|
|
|
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()
|
2011-03-04 18:06:44 +03:00
|
|
|
elif request.method == 'POST':
|
2011-03-04 13:28:20 +03:00
|
|
|
# Django has already done the form parsing for us.
|
2011-03-04 18:06:44 +03:00
|
|
|
data = request.POST
|
|
|
|
files = request.FILES
|
|
|
|
|
2011-03-04 18:23:18 +03:00
|
|
|
# Flatening data, files and combining them
|
2011-03-04 18:06:44 +03:00
|
|
|
data = self.flatten_data(data)
|
|
|
|
files = self.flatten_data(files)
|
2011-03-04 13:28:20 +03:00
|
|
|
data.update(files)
|
|
|
|
|
|
|
|
# Strip any parameters that we are treating as reserved
|
2011-03-08 18:19:55 +03:00
|
|
|
for key in data.keys():
|
2011-03-04 13:28:20 +03:00
|
|
|
if key in self.resource.RESERVED_FORM_PARAMS:
|
|
|
|
data.pop(key)
|
|
|
|
return data
|