diff options
| -rw-r--r-- | README.md | 49 | ||||
| -rw-r--r-- | docs/api-guide/parsers.md | 14 | ||||
| -rw-r--r-- | docs/api-guide/renderers.md | 14 | ||||
| -rw-r--r-- | docs/index.md | 2 | ||||
| -rw-r--r-- | requirements-test.txt | 1 | ||||
| -rw-r--r-- | rest_framework/compat.py | 7 | ||||
| -rw-r--r-- | rest_framework/parsers.py | 76 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 54 | ||||
| -rw-r--r-- | tests/test_parsers.py | 62 | ||||
| -rw-r--r-- | tests/test_renderers.py | 107 | 
10 files changed, 32 insertions, 354 deletions
| @@ -53,7 +53,7 @@ Add `'rest_framework'` to your `INSTALLED_APPS` setting.  Let's take a look at a quick example of using REST framework to build a simple model-backed API for accessing users and groups. -Startup up a new project like so...  +Startup up a new project like so...      pip install django      pip install djangorestframework @@ -79,7 +79,7 @@ class UserViewSet(viewsets.ModelViewSet):      queryset = User.objects.all()      serializer_class = UserSerializer -     +  # Routers provide a way of automatically determining the URL conf.  router = routers.DefaultRouter()  router.register(r'users', UserViewSet) @@ -100,7 +100,7 @@ Add the following to your `settings.py` module:  ```python  INSTALLED_APPS = (      ...  # Make sure to include the default installed apps here. -    'rest_framework',         +    'rest_framework',  )  REST_FRAMEWORK = { @@ -123,10 +123,10 @@ You can also interact with the API using command line tools such as [`curl`](htt      $ curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/  	[  	    { -	        "url": "http://127.0.0.1:8000/users/1/",  -	        "username": "admin",  -	        "email": "admin@example.com",  -	        "is_staff": true,  +	        "url": "http://127.0.0.1:8000/users/1/", +	        "username": "admin", +	        "email": "admin@example.com", +	        "is_staff": true,  	    }  	] @@ -134,10 +134,10 @@ Or to create a new user:      $ curl -X POST -d username=new -d email=new@example.com -d is_staff=false -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/      { -        "url": "http://127.0.0.1:8000/users/2/",  -        "username": "new",  -        "email": "new@example.com",  -        "is_staff": false,  +        "url": "http://127.0.0.1:8000/users/2/", +        "username": "new", +        "email": "new@example.com", +        "is_staff": false,      }  # Documentation & Support @@ -159,24 +159,24 @@ Send a description of the issue via email to [rest-framework-security@googlegrou  Copyright (c) 2011-2014, Tom Christie  All rights reserved. -Redistribution and use in source and binary forms, with or without  +Redistribution and use in source and binary forms, with or without  modification, are permitted provided that the following conditions are met: -Redistributions of source code must retain the above copyright notice, this  +Redistributions of source code must retain the above copyright notice, this  list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this  -list of conditions and the following disclaimer in the documentation and/or  +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or  other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND  -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED  -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE  -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE  -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL  -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR  -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER  -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,  -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -215,6 +215,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  [urlobject]: https://github.com/zacharyvoase/urlobject  [markdown]: http://pypi.python.org/pypi/Markdown/  [pyyaml]: http://pypi.python.org/pypi/PyYAML -[defusedxml]: https://pypi.python.org/pypi/defusedxml  [django-filter]: http://pypi.python.org/pypi/django-filter  [security-mail]: mailto:rest-framework-security@googlegroups.com diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 73e3a705..32819146 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -78,18 +78,6 @@ Requires the `pyyaml` package to be installed.  **.media_type**: `application/yaml` -## XMLParser - -Parses REST framework's default style of `XML` request content. - -Note that the `XML` markup language is typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`. - -If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type. - -Requires the `defusedxml` package to be installed. - -**.media_type**: `application/xml` -  ## FormParser  Parses HTML form content.  `request.data` will be populated with a `QueryDict` of data. @@ -161,7 +149,7 @@ By default this will include the following keys: `view`, `request`, `args`, `kwa  ## Example -The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request.  +The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request.      class PlainTextParser(BaseParser):      """ diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 035ec1d2..47bf2e60 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -145,20 +145,6 @@ Note that non-ascii characters will not be character escaped.  For example:  **.charset**: `utf-8` -## XMLRenderer - -Renders REST framework's default style of `XML` response content. - -Note that the `XML` markup language is used typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`. - -If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type. - -**.media_type**: `application/xml` - -**.format**: `'.xml'` - -**.charset**: `utf-8` -  ## TemplateHTMLRenderer  Renders data to HTML, using Django's standard template rendering. diff --git a/docs/index.md b/docs/index.md index dc7bb6d0..ac5d5a01 100644 --- a/docs/index.md +++ b/docs/index.md @@ -55,7 +55,6 @@ The following packages are optional:  * [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.  * [PyYAML][yaml] (3.10+) - YAML content-type support. -* [defusedxml][defusedxml] (0.3+) - XML content-type support.  * [django-filter][django-filter] (0.5.4+) - Filtering support.  * [django-restframework-oauth][django-restframework-oauth] package for OAuth 1.0a and 2.0 support.  * [django-guardian][django-guardian] (1.1.1+) - Object level permissions support. @@ -256,7 +255,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  [eventbrite]: https://www.eventbrite.co.uk/about/  [markdown]: http://pypi.python.org/pypi/Markdown/  [yaml]: http://pypi.python.org/pypi/PyYAML -[defusedxml]: https://pypi.python.org/pypi/defusedxml  [django-filter]: http://pypi.python.org/pypi/django-filter  [django-restframework-oauth]: https://github.com/jlafon/django-rest-framework-oauth  [django-guardian]: https://github.com/lukaszb/django-guardian diff --git a/requirements-test.txt b/requirements-test.txt index 19b2c610..2fb859da 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -7,7 +7,6 @@ flake8==2.2.2  # Optional packages  markdown>=2.1.0  PyYAML>=3.10 -defusedxml>=0.3  django-guardian==1.2.4  django-filter>=0.5.4  Pillow==2.3.0 diff --git a/rest_framework/compat.py b/rest_framework/compat.py index da2496d8..06da3770 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -244,13 +244,6 @@ except ImportError:      yaml = None -# XML is optional -try: -    import defusedxml.ElementTree as etree -except ImportError: -    etree = None - -  # `seperators` argument to `json.dumps()` differs between 2.x and 3.x  # See: http://bugs.python.org/issue22767  if six.PY3: diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index ccb82f03..6d0e932b 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -12,12 +12,10 @@ from django.http import QueryDict  from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser  from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter  from django.utils import six -from rest_framework.compat import etree, yaml, force_text, urlparse +from rest_framework.compat import yaml, force_text, urlparse  from rest_framework.exceptions import ParseError  from rest_framework import renderers  import json -import datetime -import decimal  class DataAndFiles(object): @@ -136,78 +134,6 @@ class MultiPartParser(BaseParser):              raise ParseError('Multipart form parse error - %s' % six.text_type(exc)) -class XMLParser(BaseParser): -    """ -    XML parser. -    """ - -    media_type = 'application/xml' - -    def parse(self, stream, media_type=None, parser_context=None): -        """ -        Parses the incoming bytestream as XML and returns the resulting data. -        """ -        assert etree, 'XMLParser requires defusedxml to be installed' - -        parser_context = parser_context or {} -        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) -        parser = etree.DefusedXMLParser(encoding=encoding) -        try: -            tree = etree.parse(stream, parser=parser, forbid_dtd=True) -        except (etree.ParseError, ValueError) as exc: -            raise ParseError('XML parse error - %s' % six.text_type(exc)) -        data = self._xml_convert(tree.getroot()) - -        return data - -    def _xml_convert(self, element): -        """ -        convert the xml `element` into the corresponding python object -        """ - -        children = list(element) - -        if len(children) == 0: -            return self._type_convert(element.text) -        else: -            # if the fist child tag is list-item means all children are list-item -            if children[0].tag == "list-item": -                data = [] -                for child in children: -                    data.append(self._xml_convert(child)) -            else: -                data = {} -                for child in children: -                    data[child.tag] = self._xml_convert(child) - -            return data - -    def _type_convert(self, value): -        """ -        Converts the value returned by the XMl parse into the equivalent -        Python type -        """ -        if value is None: -            return value - -        try: -            return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S') -        except ValueError: -            pass - -        try: -            return int(value) -        except ValueError: -            pass - -        try: -            return decimal.Decimal(value) -        except decimal.InvalidOperation: -            pass - -        return value - -  class FileUploadParser(BaseParser):      """      Parser for file upload data. diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index e87d16d0..dd49ae82 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -16,11 +16,8 @@ from django.http.multipartparser import parse_header  from django.template import Context, RequestContext, loader, Template  from django.test.client import encode_multipart  from django.utils import six -from django.utils.xmlutils import SimplerXMLGenerator  from rest_framework import exceptions, serializers, status, VERSION -from rest_framework.compat import ( -    SHORT_SEPARATORS, LONG_SEPARATORS, StringIO, smart_text, yaml -) +from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS, yaml  from rest_framework.exceptions import ParseError  from rest_framework.settings import api_settings  from rest_framework.request import is_form_media_type, override_method @@ -140,55 +137,6 @@ class JSONPRenderer(JSONRenderer):          return callback.encode(self.charset) + b'(' + json + b');' -class XMLRenderer(BaseRenderer): -    """ -    Renderer which serializes to XML. -    """ - -    media_type = 'application/xml' -    format = 'xml' -    charset = 'utf-8' - -    def render(self, data, accepted_media_type=None, renderer_context=None): -        """ -        Renders `data` into serialized XML. -        """ -        if data is None: -            return '' - -        stream = StringIO() - -        xml = SimplerXMLGenerator(stream, self.charset) -        xml.startDocument() -        xml.startElement("root", {}) - -        self._to_xml(xml, data) - -        xml.endElement("root") -        xml.endDocument() -        return stream.getvalue() - -    def _to_xml(self, xml, data): -        if isinstance(data, (list, tuple)): -            for item in data: -                xml.startElement("list-item", {}) -                self._to_xml(xml, item) -                xml.endElement("list-item") - -        elif isinstance(data, dict): -            for key, value in six.iteritems(data): -                xml.startElement(key, {}) -                self._to_xml(xml, value) -                xml.endElement(key) - -        elif data is None: -            # Don't output any value -            pass - -        else: -            xml.characters(smart_text(data)) - -  class YAMLRenderer(BaseRenderer):      """      Renderer which serializes to YAML. diff --git a/tests/test_parsers.py b/tests/test_parsers.py index 3f2672df..32fb0595 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -1,15 +1,11 @@  # -*- coding: utf-8 -*-  from __future__ import unicode_literals -from rest_framework.compat import StringIO  from django import forms  from django.core.files.uploadhandler import MemoryFileUploadHandler  from django.test import TestCase -from django.utils import unittest -from rest_framework.compat import etree +from rest_framework.compat import StringIO  from rest_framework.parsers import FormParser, FileUploadParser -from rest_framework.parsers import XMLParser -import datetime  class Form(forms.Form): @@ -31,62 +27,6 @@ class TestFormParser(TestCase):          self.assertEqual(Form(data).is_valid(), True) -class TestXMLParser(TestCase): -    def setUp(self): -        self._input = StringIO( -            '<?xml version="1.0" encoding="utf-8"?>' -            '<root>' -            '<field_a>121.0</field_a>' -            '<field_b>dasd</field_b>' -            '<field_c></field_c>' -            '<field_d>2011-12-25 12:45:00</field_d>' -            '</root>' -        ) -        self._data = { -            'field_a': 121, -            'field_b': 'dasd', -            'field_c': None, -            'field_d': datetime.datetime(2011, 12, 25, 12, 45, 00) -        } -        self._complex_data_input = StringIO( -            '<?xml version="1.0" encoding="utf-8"?>' -            '<root>' -            '<creation_date>2011-12-25 12:45:00</creation_date>' -            '<sub_data_list>' -            '<list-item><sub_id>1</sub_id><sub_name>first</sub_name></list-item>' -            '<list-item><sub_id>2</sub_id><sub_name>second</sub_name></list-item>' -            '</sub_data_list>' -            '<name>name</name>' -            '</root>' -        ) -        self._complex_data = { -            "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00), -            "name": "name", -            "sub_data_list": [ -                { -                    "sub_id": 1, -                    "sub_name": "first" -                }, -                { -                    "sub_id": 2, -                    "sub_name": "second" -                } -            ] -        } - -    @unittest.skipUnless(etree, 'defusedxml not installed') -    def test_parse(self): -        parser = XMLParser() -        data = parser.parse(self._input) -        self.assertEqual(data, self._data) - -    @unittest.skipUnless(etree, 'defusedxml not installed') -    def test_complex_data_parse(self): -        parser = XMLParser() -        data = parser.parse(self._complex_data_input) -        self.assertEqual(data, self._complex_data) - -  class TestFileUploadParser(TestCase):      def setUp(self):          class MockRequest(object): diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 416d7f22..1eec37dc 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -6,19 +6,18 @@ from django.conf.urls import patterns, url, include  from django.core.cache import cache  from django.db import models  from django.test import TestCase -from django.utils import six, unittest +from django.utils import six  from django.utils.translation import ugettext_lazy as _  from rest_framework import status, permissions -from rest_framework.compat import yaml, etree, StringIO, BytesIO +from rest_framework.compat import yaml, BytesIO  from rest_framework.response import Response  from rest_framework.views import APIView  from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ -    XMLRenderer, JSONPRenderer, BrowsableAPIRenderer -from rest_framework.parsers import YAMLParser, XMLParser +    JSONPRenderer, BrowsableAPIRenderer +from rest_framework.parsers import YAMLParser  from rest_framework.settings import api_settings  from rest_framework.test import APIRequestFactory  from collections import MutableMapping -import datetime  import json  import pickle  import re @@ -501,104 +500,6 @@ if yaml:              self.assertEqual(content.strip(), 'countries: [United Kingdom, France, EspaƱa]'.encode('utf-8')) -class XMLRendererTestCase(TestCase): -    """ -    Tests specific to the XML Renderer -    """ - -    _complex_data = { -        "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00), -        "name": "name", -        "sub_data_list": [ -            { -                "sub_id": 1, -                "sub_name": "first" -            }, -            { -                "sub_id": 2, -                "sub_name": "second" -            } -        ] -    } - -    def test_render_string(self): -        """ -        Test XML rendering. -        """ -        renderer = XMLRenderer() -        content = renderer.render({'field': 'astring'}, 'application/xml') -        self.assertXMLContains(content, '<field>astring</field>') - -    def test_render_integer(self): -        """ -        Test XML rendering. -        """ -        renderer = XMLRenderer() -        content = renderer.render({'field': 111}, 'application/xml') -        self.assertXMLContains(content, '<field>111</field>') - -    def test_render_datetime(self): -        """ -        Test XML rendering. -        """ -        renderer = XMLRenderer() -        content = renderer.render({ -            'field': datetime.datetime(2011, 12, 25, 12, 45, 00) -        }, 'application/xml') -        self.assertXMLContains(content, '<field>2011-12-25 12:45:00</field>') - -    def test_render_float(self): -        """ -        Test XML rendering. -        """ -        renderer = XMLRenderer() -        content = renderer.render({'field': 123.4}, 'application/xml') -        self.assertXMLContains(content, '<field>123.4</field>') - -    def test_render_decimal(self): -        """ -        Test XML rendering. -        """ -        renderer = XMLRenderer() -        content = renderer.render({'field': Decimal('111.2')}, 'application/xml') -        self.assertXMLContains(content, '<field>111.2</field>') - -    def test_render_none(self): -        """ -        Test XML rendering. -        """ -        renderer = XMLRenderer() -        content = renderer.render({'field': None}, 'application/xml') -        self.assertXMLContains(content, '<field></field>') - -    def test_render_complex_data(self): -        """ -        Test XML rendering. -        """ -        renderer = XMLRenderer() -        content = renderer.render(self._complex_data, 'application/xml') -        self.assertXMLContains(content, '<sub_name>first</sub_name>') -        self.assertXMLContains(content, '<sub_name>second</sub_name>') - -    @unittest.skipUnless(etree, 'defusedxml not installed') -    def test_render_and_parse_complex_data(self): -        """ -        Test XML rendering. -        """ -        renderer = XMLRenderer() -        content = StringIO(renderer.render(self._complex_data, 'application/xml')) - -        parser = XMLParser() -        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) - -    def assertXMLContains(self, xml, string): -        self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>')) -        self.assertTrue(xml.endswith('</root>')) -        self.assertTrue(string in xml, '%r not in %r' % (string, xml)) - -  # Tests for caching issue, #346  class CacheRenderTest(TestCase):      """ | 
