diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index 43ea0c4dc..fc0260fcf 100644 --- a/djangorestframework/parsers.py +++ b/djangorestframework/parsers.py @@ -36,6 +36,12 @@ __all__ = ( ) +class DataAndFiles(object): + def __init__(self, data, files): + self.data = data + self.files = files + + class BaseParser(object): """ All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute, @@ -80,7 +86,7 @@ class JSONParser(BaseParser): `files` will always be `None`. """ try: - return (json.load(stream), None) + return json.load(stream) except ValueError, exc: raise ParseError('JSON parse error - %s' % unicode(exc)) @@ -100,7 +106,7 @@ class YAMLParser(BaseParser): `files` will always be `None`. """ try: - return (yaml.safe_load(stream), None) + return yaml.safe_load(stream) except (ValueError, yaml.parser.ParserError), 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. `files` will always be `None`. """ - return (stream.read(), None) + return stream.read() class FormParser(BaseParser): @@ -137,7 +143,7 @@ class FormParser(BaseParser): `files` will always be :const:`None`. """ data = QueryDict(stream.read()) - return (data, None) + return data class MultiPartParser(BaseParser): @@ -149,16 +155,17 @@ class MultiPartParser(BaseParser): 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. - `files` will be a :class:`QueryDict` containing all the form files. + `.data` will be a `QueryDict` containing all the form parameters. + `.files` will be a `QueryDict` containing all the form files. """ meta = opts['meta'] upload_handlers = opts['upload_handlers'] try: parser = DjangoMultiPartParser(meta, stream, upload_handlers) - return parser.parse() + data, files = parser.parse() + return DataAndFiles(data, files) except MultiPartParserError, exc: raise ParseError('Multipart form parse error - %s' % unicode(exc)) @@ -171,19 +178,13 @@ class XMLParser(BaseParser): media_type = 'application/xml' 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: tree = ET.parse(stream) except (ExpatError, ETParseError, ValueError), exc: raise ParseError('XML parse error - %s' % unicode(exc)) data = self._xml_convert(tree.getroot()) - return (data, None) + return data def _xml_convert(self, element): """ diff --git a/djangorestframework/request.py b/djangorestframework/request.py index 84ca05753..2e4e89092 100644 --- a/djangorestframework/request.py +++ b/djangorestframework/request.py @@ -146,7 +146,7 @@ class Request(object): self._load_method_and_content_type() if not _hasattr(self, '_data'): - (self._data, self._files) = self._parse() + self._data, self._files = self._parse() def _load_method_and_content_type(self): """ @@ -201,11 +201,11 @@ class Request(object): self._CONTENTTYPE_PARAM in self._data): self._content_type = self._data.pop(self._CONTENTTYPE_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): """ - Parse the request content. + Parse the request content, returning a two-tuple of (data, files) May raise an `UnsupportedMediaType`, or `ParseError` exception. """ @@ -214,8 +214,14 @@ class Request(object): for parser in self.get_parsers(): if parser.can_handle_request(self.content_type): - return parser.parse(self.stream, meta=self.META, - upload_handlers=self.upload_handlers) + parsed = parser.parse(self.stream, meta=self.META, + 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) diff --git a/djangorestframework/tests/parsers.py b/djangorestframework/tests/parsers.py index a85409dc0..c9b6afd0a 100644 --- a/djangorestframework/tests/parsers.py +++ b/djangorestframework/tests/parsers.py @@ -153,7 +153,7 @@ class TestFormParser(TestCase): parser = FormParser() stream = StringIO(self.string) - (data, files) = parser.parse(stream) + data = parser.parse(stream) self.assertEqual(Form(data).is_valid(), True) @@ -203,10 +203,10 @@ class TestXMLParser(TestCase): def test_parse(self): parser = XMLParser() - (data, files) = parser.parse(self._input) + data = parser.parse(self._input) self.assertEqual(data, self._data) def test_complex_data_parse(self): parser = XMLParser() - (data, files) = parser.parse(self._complex_data_input) + data = parser.parse(self._complex_data_input) self.assertEqual(data, self._complex_data) diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py index adf8d8fa1..dc30f4870 100644 --- a/djangorestframework/tests/renderers.py +++ b/djangorestframework/tests/renderers.py @@ -301,7 +301,7 @@ if YAMLRenderer: parser = YAMLParser() content = renderer.render(obj, 'application/yaml') - (data, files) = parser.parse(StringIO(content)) + data = parser.parse(StringIO(content)) self.assertEquals(obj, data) @@ -392,7 +392,7 @@ class XMLRendererTestCase(TestCase): content = StringIO(renderer.render(self._complex_data, 'application/xml')) 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)) self.assertEqual(self._complex_data, complex_data_out, error_msg) diff --git a/djangorestframework/tests/request.py b/djangorestframework/tests/request.py index 85b2f4186..1d74ea323 100644 --- a/djangorestframework/tests/request.py +++ b/djangorestframework/tests/request.py @@ -4,7 +4,6 @@ Tests for content parsing, and form-overloaded content parsing. from django.conf.urls.defaults import patterns from django.contrib.auth.models import User from django.test import TestCase, Client -from django.utils import simplejson as json from djangorestframework import status from djangorestframework.authentication import UserLoggedInAuthentication @@ -13,7 +12,6 @@ from djangorestframework.parsers import ( FormParser, MultiPartParser, PlainTextParser, - JSONParser ) from djangorestframework.request import Request from djangorestframework.response import Response diff --git a/djangorestframework/utils/encoders.py b/djangorestframework/utils/encoders.py index 3cd2e8e11..ba7c85538 100644 --- a/djangorestframework/utils/encoders.py +++ b/djangorestframework/utils/encoders.py @@ -1,3 +1,6 @@ +""" +Helper classes for parsers. +""" import datetime import decimal from django.utils import timezone @@ -6,10 +9,12 @@ from django.utils import simplejson as json 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): - # 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): r = o.isoformat() if o.microsecond: diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 0b6eac9d2..34bac1559 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -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.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=)} At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`.