mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 17:47:04 +03:00
Remove XML support from core
This commit is contained in:
parent
3a5b3772fe
commit
7f9dc73672
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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-oauth-plus][django-oauth-plus] (2.0+) and [oauth2][oauth2] (1.5.211+) - OAuth 1.0a support.
|
||||
* [django-oauth2-provider][django-oauth2-provider] (0.2.3+) - OAuth 2.0 support.
|
||||
|
@ -259,7 +258,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
|
||||
[oauth2]: https://github.com/simplegeo/python-oauth2
|
||||
[django-oauth-plus]: https://bitbucket.org/david/django-oauth-plus/wiki/Home
|
||||
|
|
|
@ -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
|
||||
django-oauth-plus>=2.2.1
|
||||
|
|
|
@ -244,13 +244,6 @@ except ImportError:
|
|||
yaml = None
|
||||
|
||||
|
||||
# XML is optional
|
||||
try:
|
||||
import defusedxml.ElementTree as etree
|
||||
except ImportError:
|
||||
etree = None
|
||||
|
||||
|
||||
# OAuth2 is optional
|
||||
try:
|
||||
# Note: The `oauth2` package actually provides oauth1.0a support. Urg.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue
Block a user