mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-09 08:00:52 +03:00
Parsers may return raw data, or a DataAndFiles object
This commit is contained in:
parent
d180e984e9
commit
7abef9ac3b
|
@ -36,6 +36,12 @@ __all__ = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DataAndFiles(object):
|
||||||
|
def __init__(self, data, files):
|
||||||
|
self.data = data
|
||||||
|
self.files = files
|
||||||
|
|
||||||
|
|
||||||
class BaseParser(object):
|
class BaseParser(object):
|
||||||
"""
|
"""
|
||||||
All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute,
|
All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute,
|
||||||
|
@ -80,7 +86,7 @@ class JSONParser(BaseParser):
|
||||||
`files` will always be `None`.
|
`files` will always be `None`.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return (json.load(stream), None)
|
return json.load(stream)
|
||||||
except ValueError, exc:
|
except ValueError, exc:
|
||||||
raise ParseError('JSON parse error - %s' % unicode(exc))
|
raise ParseError('JSON parse error - %s' % unicode(exc))
|
||||||
|
|
||||||
|
@ -100,7 +106,7 @@ class YAMLParser(BaseParser):
|
||||||
`files` will always be `None`.
|
`files` will always be `None`.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return (yaml.safe_load(stream), None)
|
return yaml.safe_load(stream)
|
||||||
except (ValueError, yaml.parser.ParserError), exc:
|
except (ValueError, yaml.parser.ParserError), exc:
|
||||||
raise ParseError('YAML parse error - %s' % unicode(exc))
|
raise ParseError('YAML parse error - %s' % unicode(exc))
|
||||||
|
|
||||||
|
@ -119,7 +125,7 @@ class PlainTextParser(BaseParser):
|
||||||
`data` will simply be a string representing the body of the request.
|
`data` will simply be a string representing the body of the request.
|
||||||
`files` will always be `None`.
|
`files` will always be `None`.
|
||||||
"""
|
"""
|
||||||
return (stream.read(), None)
|
return stream.read()
|
||||||
|
|
||||||
|
|
||||||
class FormParser(BaseParser):
|
class FormParser(BaseParser):
|
||||||
|
@ -137,7 +143,7 @@ class FormParser(BaseParser):
|
||||||
`files` will always be :const:`None`.
|
`files` will always be :const:`None`.
|
||||||
"""
|
"""
|
||||||
data = QueryDict(stream.read())
|
data = QueryDict(stream.read())
|
||||||
return (data, None)
|
return data
|
||||||
|
|
||||||
|
|
||||||
class MultiPartParser(BaseParser):
|
class MultiPartParser(BaseParser):
|
||||||
|
@ -149,16 +155,17 @@ class MultiPartParser(BaseParser):
|
||||||
|
|
||||||
def parse(self, stream, **opts):
|
def parse(self, stream, **opts):
|
||||||
"""
|
"""
|
||||||
Returns a 2-tuple of `(data, files)`.
|
Returns a DataAndFiles object.
|
||||||
|
|
||||||
`data` will be a :class:`QueryDict` containing all the form parameters.
|
`.data` will be a `QueryDict` containing all the form parameters.
|
||||||
`files` will be a :class:`QueryDict` containing all the form files.
|
`.files` will be a `QueryDict` containing all the form files.
|
||||||
"""
|
"""
|
||||||
meta = opts['meta']
|
meta = opts['meta']
|
||||||
upload_handlers = opts['upload_handlers']
|
upload_handlers = opts['upload_handlers']
|
||||||
try:
|
try:
|
||||||
parser = DjangoMultiPartParser(meta, stream, upload_handlers)
|
parser = DjangoMultiPartParser(meta, stream, upload_handlers)
|
||||||
return parser.parse()
|
data, files = parser.parse()
|
||||||
|
return DataAndFiles(data, files)
|
||||||
except MultiPartParserError, exc:
|
except MultiPartParserError, exc:
|
||||||
raise ParseError('Multipart form parse error - %s' % unicode(exc))
|
raise ParseError('Multipart form parse error - %s' % unicode(exc))
|
||||||
|
|
||||||
|
@ -171,19 +178,13 @@ class XMLParser(BaseParser):
|
||||||
media_type = 'application/xml'
|
media_type = 'application/xml'
|
||||||
|
|
||||||
def parse(self, stream, **opts):
|
def parse(self, stream, **opts):
|
||||||
"""
|
|
||||||
Returns a 2-tuple of `(data, files)`.
|
|
||||||
|
|
||||||
`data` will simply be a string representing the body of the request.
|
|
||||||
`files` will always be `None`.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
tree = ET.parse(stream)
|
tree = ET.parse(stream)
|
||||||
except (ExpatError, ETParseError, ValueError), exc:
|
except (ExpatError, ETParseError, ValueError), exc:
|
||||||
raise ParseError('XML parse error - %s' % unicode(exc))
|
raise ParseError('XML parse error - %s' % unicode(exc))
|
||||||
data = self._xml_convert(tree.getroot())
|
data = self._xml_convert(tree.getroot())
|
||||||
|
|
||||||
return (data, None)
|
return data
|
||||||
|
|
||||||
def _xml_convert(self, element):
|
def _xml_convert(self, element):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -146,7 +146,7 @@ class Request(object):
|
||||||
self._load_method_and_content_type()
|
self._load_method_and_content_type()
|
||||||
|
|
||||||
if not _hasattr(self, '_data'):
|
if not _hasattr(self, '_data'):
|
||||||
(self._data, self._files) = self._parse()
|
self._data, self._files = self._parse()
|
||||||
|
|
||||||
def _load_method_and_content_type(self):
|
def _load_method_and_content_type(self):
|
||||||
"""
|
"""
|
||||||
|
@ -201,11 +201,11 @@ class Request(object):
|
||||||
self._CONTENTTYPE_PARAM in self._data):
|
self._CONTENTTYPE_PARAM in self._data):
|
||||||
self._content_type = self._data.pop(self._CONTENTTYPE_PARAM)[0]
|
self._content_type = self._data.pop(self._CONTENTTYPE_PARAM)[0]
|
||||||
self._stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0])
|
self._stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0])
|
||||||
(self._data, self._files) = self._parse()
|
self._data, self._files = self._parse()
|
||||||
|
|
||||||
def _parse(self):
|
def _parse(self):
|
||||||
"""
|
"""
|
||||||
Parse the request content.
|
Parse the request content, returning a two-tuple of (data, files)
|
||||||
|
|
||||||
May raise an `UnsupportedMediaType`, or `ParseError` exception.
|
May raise an `UnsupportedMediaType`, or `ParseError` exception.
|
||||||
"""
|
"""
|
||||||
|
@ -214,8 +214,14 @@ class Request(object):
|
||||||
|
|
||||||
for parser in self.get_parsers():
|
for parser in self.get_parsers():
|
||||||
if parser.can_handle_request(self.content_type):
|
if parser.can_handle_request(self.content_type):
|
||||||
return parser.parse(self.stream, meta=self.META,
|
parsed = parser.parse(self.stream, meta=self.META,
|
||||||
upload_handlers=self.upload_handlers)
|
upload_handlers=self.upload_handlers)
|
||||||
|
# Parser classes may return the raw data, or a
|
||||||
|
# DataAndFiles object. Unpack the result as required.
|
||||||
|
try:
|
||||||
|
return (parsed.data, parsed.files)
|
||||||
|
except AttributeError:
|
||||||
|
return (parsed, None)
|
||||||
|
|
||||||
raise UnsupportedMediaType(self._content_type)
|
raise UnsupportedMediaType(self._content_type)
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@ class TestFormParser(TestCase):
|
||||||
parser = FormParser()
|
parser = FormParser()
|
||||||
|
|
||||||
stream = StringIO(self.string)
|
stream = StringIO(self.string)
|
||||||
(data, files) = parser.parse(stream)
|
data = parser.parse(stream)
|
||||||
|
|
||||||
self.assertEqual(Form(data).is_valid(), True)
|
self.assertEqual(Form(data).is_valid(), True)
|
||||||
|
|
||||||
|
@ -203,10 +203,10 @@ class TestXMLParser(TestCase):
|
||||||
|
|
||||||
def test_parse(self):
|
def test_parse(self):
|
||||||
parser = XMLParser()
|
parser = XMLParser()
|
||||||
(data, files) = parser.parse(self._input)
|
data = parser.parse(self._input)
|
||||||
self.assertEqual(data, self._data)
|
self.assertEqual(data, self._data)
|
||||||
|
|
||||||
def test_complex_data_parse(self):
|
def test_complex_data_parse(self):
|
||||||
parser = XMLParser()
|
parser = XMLParser()
|
||||||
(data, files) = parser.parse(self._complex_data_input)
|
data = parser.parse(self._complex_data_input)
|
||||||
self.assertEqual(data, self._complex_data)
|
self.assertEqual(data, self._complex_data)
|
||||||
|
|
|
@ -301,7 +301,7 @@ if YAMLRenderer:
|
||||||
parser = YAMLParser()
|
parser = YAMLParser()
|
||||||
|
|
||||||
content = renderer.render(obj, 'application/yaml')
|
content = renderer.render(obj, 'application/yaml')
|
||||||
(data, files) = parser.parse(StringIO(content))
|
data = parser.parse(StringIO(content))
|
||||||
self.assertEquals(obj, data)
|
self.assertEquals(obj, data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -392,7 +392,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
|
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
|
||||||
|
|
||||||
parser = XMLParser()
|
parser = XMLParser()
|
||||||
complex_data_out, dummy = parser.parse(content)
|
complex_data_out = parser.parse(content)
|
||||||
error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out))
|
error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out))
|
||||||
self.assertEqual(self._complex_data, complex_data_out, error_msg)
|
self.assertEqual(self._complex_data, complex_data_out, error_msg)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ Tests for content parsing, and form-overloaded content parsing.
|
||||||
from django.conf.urls.defaults import patterns
|
from django.conf.urls.defaults import patterns
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.utils import simplejson as json
|
|
||||||
|
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
from djangorestframework.authentication import UserLoggedInAuthentication
|
from djangorestframework.authentication import UserLoggedInAuthentication
|
||||||
|
@ -13,7 +12,6 @@ from djangorestframework.parsers import (
|
||||||
FormParser,
|
FormParser,
|
||||||
MultiPartParser,
|
MultiPartParser,
|
||||||
PlainTextParser,
|
PlainTextParser,
|
||||||
JSONParser
|
|
||||||
)
|
)
|
||||||
from djangorestframework.request import Request
|
from djangorestframework.request import Request
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
"""
|
||||||
|
Helper classes for parsers.
|
||||||
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -6,10 +9,12 @@ from django.utils import simplejson as json
|
||||||
|
|
||||||
class JSONEncoder(json.JSONEncoder):
|
class JSONEncoder(json.JSONEncoder):
|
||||||
"""
|
"""
|
||||||
JSONEncoder subclass that knows how to encode date/time and decimal types.
|
JSONEncoder subclass that knows how to encode date/time,
|
||||||
|
decimal types, and generators.
|
||||||
"""
|
"""
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
# See "Date Time String Format" in the ECMA-262 specification.
|
# For Date Time string spec, see ECMA 262
|
||||||
|
# http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
|
||||||
if isinstance(o, datetime.datetime):
|
if isinstance(o, datetime.datetime):
|
||||||
r = o.isoformat()
|
r = o.isoformat()
|
||||||
if o.microsecond:
|
if o.microsecond:
|
||||||
|
|
|
@ -127,7 +127,7 @@ We've now got a few comment instances to play with. Let's take a look at serial
|
||||||
|
|
||||||
serializer = CommentSerializer(instance=c1)
|
serializer = CommentSerializer(instance=c1)
|
||||||
serializer.data
|
serializer.data
|
||||||
# {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
|
# {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774, tzinfo=<UTC>)}
|
||||||
|
|
||||||
At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`.
|
At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user