diff --git a/README.md b/README.md
index b01fb94..4d75998 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# REST Framework XML
-[![build-status-image]][github-action]
+[![build-status-image]][travis]
[![pypi-version]][pypi]
**XML support for Django REST Framework**
@@ -15,9 +15,11 @@ XML support extracted as a third party package directly from the official Django
## Requirements
-* Python 3.5+
-* Django 2.2+
-* Django REST Framework 3.11+
+* Python (2.7, 3.4, 3.5, 3.6)
+* Django (1.8 - 1.11, 2.0 - 2.1)
+* Django REST Framework (2.4, 3.0 - 3.9)
+
+This project is tested on the combinations of Python and Django that are supported by each version of Django REST Framework.
## Installation
@@ -75,6 +77,59 @@ class UserViewSet(viewsets.ModelViewSet):
```
+## SOAP Renderer
+
+You can also set the SOAP renderer for an individual view, or viewset, using the APIView class based views. You must reload soap schema.
+
+```python
+from rest_framework import routers, serializers, viewsets
+from rest_framework_xml.parsers import XMLParser
+from rest_framework_xml.renderers import SOAPRenderer
+
+
+class UserSerializer(serializers.HyperlinkedModelSerializer):
+ class Meta:
+ model = User
+ fields = ('url', 'username', 'email', 'is_staff')
+
+
+class UserViewSet(viewsets.ModelViewSet):
+ queryset = User.objects.all()
+ serializer_class = UserSerializer
+ parser_classes = (XMLParser,)
+
+ soap_tag = "SOAP-TEST"
+ soap_endpoint = "https://xml.com/soap"
+ soap_service = "soapService"
+
+ renderer = SOAPRenderer
+ renderer.set_schema_attrs(soap_tag, soap_endpoint, soap_service)
+ renderer_classes = (renderer,)
+```
+
+### Sample output
+
+```xml
+\n
+
+
+
+
+ Some words
+
+ 1
+ tag one
+ 2
+ tag two
+
+ 2020-04-14 12:45:00
+
+
+
+```
+
## Documentation & Support
Full documentation for the project is available at [http://jpadilla.github.io/django-rest-framework-xml][docs].
@@ -82,8 +137,8 @@ Full documentation for the project is available at [http://jpadilla.github.io/dj
You may also want to follow the [author][jpadilla] on Twitter.
-[build-status-image]: https://github.com/jpadilla/django-rest-framework-xml/workflows/CI/badge.svg
-[github-action]: https://github.com/jpadilla/django-rest-framework-xml/actions?query=workflow%3ACI
+[build-status-image]: https://secure.travis-ci.org/jpadilla/django-rest-framework-xml.svg?branch=master
+[travis]: http://travis-ci.org/jpadilla/django-rest-framework-xml?branch=master
[pypi-version]: https://img.shields.io/pypi/v/djangorestframework-xml.svg
[pypi]: https://pypi.python.org/pypi/djangorestframework-xml
[defusedxml]: https://pypi.python.org/pypi/defusedxml
diff --git a/docs/renderers.md b/docs/renderers.md
index 7916053..6ff8f25 100644
--- a/docs/renderers.md
+++ b/docs/renderers.md
@@ -62,3 +62,17 @@ If you are considering using `XML` for your API, you may want to consider implem
**item_tag_name**: `list-item`
**.root_tag_name**: `root`
+
+## SOAP Renderer
+
+If you are considering using `SOAP` renderer for your API, you may must need to create your own custom SOAP envelope structure.
+
+**soap_tag**: `SOAP-TEST`
+**soap_endpoint**: `https://xml.com/soap`
+**soap_service**: `soapService`
+
+For creating your SOAP envelope structure you must need to reload the schema:
+
+ renderer = SOAPRenderer
+ renderer.set_schema_attrs(soap_tag, soap_endpoint, soap_service)
+ renderer_classes = (renderer,)
\ No newline at end of file
diff --git a/rest_framework_xml/renderers.py b/rest_framework_xml/renderers.py
index 81765e8..f9c7d36 100644
--- a/rest_framework_xml/renderers.py
+++ b/rest_framework_xml/renderers.py
@@ -13,18 +13,18 @@ class XMLRenderer(BaseRenderer):
Renderer which serializes to XML.
"""
- media_type = "application/xml"
- format = "xml"
- charset = "utf-8"
- item_tag_name = "list-item"
- root_tag_name = "root"
+ media_type = 'application/xml'
+ format = 'xml'
+ charset = 'utf-8'
+ item_tag_name = 'list-item'
+ root_tag_name = 'root'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Renders `data` into serialized XML.
"""
if data is None:
- return ""
+ return ''
stream = StringIO()
@@ -57,3 +57,81 @@ def _to_xml(self, xml, data):
else:
xml.characters(force_str(data))
+
+
+class SOAPRenderer(BaseRenderer):
+ """
+ Renderer which serialize to SOAP Envelope
+ """
+ media_type = 'application/xml'
+ format = 'xml'
+ charset = 'utf-8'
+
+ soap_tag = 'SOAP-ENV'
+ service_endpoint = 'http://dummyservice.com/endpoint'
+ service_name = 'dummyService'
+
+ schema_attrs = {
+ 'xmlns:%s' % soap_tag: 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'xmlns:%s' % service_name: '%s' % service_endpoint
+ }
+
+ envelope_tag_name = '%s:Envelope' % soap_tag
+ header_tag_name = '%s:Header' % soap_tag
+ body_tag_name = '%s:Body' % soap_tag
+ response_tag_name = '%s:Response' % service_name
+
+ @classmethod
+ def set_schema_attrs(cls, soap_tag, service_endpoint, service_name):
+ cls.envelope_tag_name = '%s:Envelope' % soap_tag
+ cls.header_tag_name = '%s:Header' % soap_tag
+ cls.body_tag_name = '%s:Body' % soap_tag
+ cls.response_tag_name = '%s:Response' % service_name
+
+ cls.schema_attrs = {
+ 'xmlns:%s' % soap_tag: 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'xmlns:%s' % service_name: '%s' % service_endpoint
+ }
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ """
+ Renders `data` into serialized SOAP Envelope.
+ """
+ if not data:
+ return ''
+
+ stream = StringIO()
+
+ xml = SimplerXMLGenerator(stream, self.charset)
+ xml.startDocument()
+
+ xml.startElement(self.envelope_tag_name, self.schema_attrs)
+ xml.addQuickElement(self.header_tag_name)
+ xml.startElement(self.body_tag_name, {})
+ xml.startElement(self.response_tag_name, {})
+
+ self._to_xml(xml, data)
+
+ xml.endElement(self.response_tag_name)
+ xml.endElement(self.body_tag_name)
+ xml.endElement(self.envelope_tag_name)
+
+ return stream.getvalue()
+
+ def _to_xml(self, xml, data):
+ if isinstance(data, (list, tuple)):
+ for item in data:
+ self._to_xml(xml, item)
+
+ elif isinstance(data, dict):
+ for key, value in data.items():
+ xml.startElement(key, {})
+ self._to_xml(xml, value)
+ xml.endElement(key)
+
+ elif data is None:
+ # Don't output any value
+ pass
+
+ else:
+ xml.characters(force_str(data))
\ No newline at end of file
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 597aa2c..d18993b 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -11,6 +11,7 @@
from rest_framework_xml.compat import etree
from rest_framework_xml.parsers import XMLParser
from rest_framework_xml.renderers import XMLRenderer
+from rest_framework_xml.renderers import SOAPRenderer
class XMLRendererTestCase(TestCase):
@@ -22,9 +23,15 @@ class XMLRendererTestCase(TestCase):
"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"},
- ],
+ {
+ "sub_id": 1,
+ "sub_name": "first"
+ },
+ {
+ "sub_id": 2,
+ "sub_name": "second"
+ }
+ ]
}
def test_render_string(self):
@@ -32,96 +39,199 @@ def test_render_string(self):
Test XML rendering.
"""
renderer = XMLRenderer()
- content = renderer.render({"field": "astring"}, "application/xml")
- self.assertXMLContains(content, "astring")
+ content = renderer.render({'Field': 'astring'}, 'application/xml')
+ self.assertXMLContains(content, 'astring')
def test_render_integer(self):
"""
Test XML rendering.
"""
renderer = XMLRenderer()
- content = renderer.render({"field": 111}, "application/xml")
- self.assertXMLContains(content, "111")
+ content = renderer.render({'Field': 111}, 'application/xml')
+ self.assertXMLContains(content, '111')
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, "2011-12-25 12:45:00")
+ content = renderer.render({
+ 'Field': datetime.datetime(2011, 12, 25, 12, 45, 00)
+ }, 'application/xml')
+ self.assertXMLContains(content, '2011-12-25 12:45:00')
def test_render_float(self):
"""
Test XML rendering.
"""
renderer = XMLRenderer()
- content = renderer.render({"field": 123.4}, "application/xml")
- self.assertXMLContains(content, "123.4")
+ content = renderer.render({'Field': 123.4}, 'application/xml')
+ self.assertXMLContains(content, '123.4')
def test_render_decimal(self):
"""
Test XML rendering.
"""
renderer = XMLRenderer()
- content = renderer.render(
- {"field": Decimal("111.2")}, "application/xml"
- )
- self.assertXMLContains(content, "111.2")
+ content = renderer.render({'Field': Decimal('111.2')}, 'application/xml')
+ self.assertXMLContains(content, '111.2')
def test_render_none(self):
"""
Test XML rendering.
"""
renderer = XMLRenderer()
- content = renderer.render({"field": None}, "application/xml")
- self.assertXMLContains(content, "")
+ content = renderer.render({'Field': None}, 'application/xml')
+ self.assertXMLContains(content, '')
def test_render_complex_data(self):
"""
Test XML rendering.
"""
renderer = XMLRenderer()
- content = renderer.render(self._complex_data, "application/xml")
- self.assertXMLContains(content, "first")
- self.assertXMLContains(content, "second")
+ content = renderer.render(self._complex_data, 'application/xml')
+ self.assertXMLContains(content, 'first')
+ self.assertXMLContains(content, 'second')
def test_render_list(self):
renderer = XMLRenderer()
- content = renderer.render(self._complex_data, "application/xml")
- self.assertXMLContains(content, "")
- self.assertXMLContains(content, "")
+ content = renderer.render(self._complex_data, 'application/xml')
+ self.assertXMLContains(content, '')
+ self.assertXMLContains(content, '')
def test_render_lazy(self):
renderer = XMLRenderer()
- lazy = gettext_lazy("hello")
- content = renderer.render({"field": lazy}, "application/xml")
- self.assertXMLContains(content, "hello")
+ lazy = gettext_lazy('hello')
+ content = renderer.render({'Field': lazy}, 'application/xml')
+ self.assertXMLContains(content, 'hello')
- @skipUnless(etree, "defusedxml not installed")
+ @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")
- )
+ 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),
- )
+ 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('\n')
- )
- self.assertTrue(xml.endswith(""))
- self.assertTrue(string in xml, "%r not in %r" % (string, xml))
+ self.assertTrue(xml.startswith('\n'))
+ self.assertTrue(xml.endswith(''))
+ self.assertTrue(string in xml, '%r not in %r' % (string, xml))
+
+
+class SOAPRendererTestCase(TestCase):
+ """
+ Test speecific to the SOAP Renderer
+ """
+ _complex_data = {
+ "TextSearch": "Some words",
+ "ComparsionResult": [
+ {
+ "ResultId": 1,
+ "ResultTag": "tag one"
+ },
+ {
+ "ResultId": 2,
+ "ResultTag": "tag two"
+ }
+ ],
+ "RecordDate": datetime.datetime(2020, 4, 14, 12, 45, 00)
+ }
+
+ def test_set_schema(self):
+ """
+ Test set soap schema
+ """
+ test_tag = "SOAP-TEST"
+ test_endpoint = "https://xml.com/soap"
+ test_service = "soapService"
+
+ expected_vals = {
+ 'xmlns:%s' % test_tag: 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'xmlns:%s' % test_service: '%s' % test_endpoint
+ }
+
+ renderer = SOAPRenderer()
+ renderer.set_schema_attrs(test_tag, test_endpoint, test_service)
+ content = renderer.render({'Field': 'astring'}, 'application/xml')
+
+ self.assertEqual(renderer.schema_attrs, expected_vals)
+ print(content)
+ self.assertTrue(content.startswith('\n'))
+
+ def test_render_string(self):
+ """
+ Test SOAP rendering.
+ """
+ renderer = SOAPRenderer()
+
+ content = renderer.render({'Field': 'astring'}, 'application/xml')
+ self.assertSOAPContains(content, 'astring')
+
+ def test_render_integer(self):
+ """
+ Test SOAP rendering.
+ """
+ renderer = SOAPRenderer()
+ content = renderer.render({'Digit': 111}, 'application/xml')
+ self.assertSOAPContains(content, '111')
+
+ def test_render_datetime(self):
+ """
+ Test SOAP rendering.
+ """
+ renderer = SOAPRenderer()
+ content = renderer.render({
+ 'Field': datetime.datetime(2011, 12, 25, 12, 45, 00)
+ }, 'application/xml')
+ self.assertSOAPContains(content, '2011-12-25 12:45:00')
+
+ def test_render_float(self):
+ """
+ Test SOAP rendering.
+ """
+ renderer = SOAPRenderer()
+ content = renderer.render({'Field': 123.4}, 'application/xml')
+ self.assertSOAPContains(content, '123.4')
+
+ def test_render_decimal(self):
+ """
+ Test SOAP rendering.
+ """
+ renderer = SOAPRenderer()
+ content = renderer.render({'Field': Decimal('111.2')}, 'application/xml')
+ self.assertSOAPContains(content, '111.2')
+
+ def test_render_none(self):
+ """
+ Test SOAP rendering.
+ """
+ renderer = SOAPRenderer()
+ content = renderer.render({'Field': None}, 'application/xml')
+ self.assertSOAPContains(content, '')
+
+ def test_render_complex_data(self):
+ """
+ Test SOAP rendering.
+ """
+ renderer = SOAPRenderer()
+ content = renderer.render(self._complex_data, 'application/xml')
+ self.assertSOAPContains(content, '')
+ self.assertSOAPContains(content, '')
+
+ def test_render_lazy(self):
+ renderer = SOAPRenderer()
+ lazy = gettext_lazy('hello')
+ content = renderer.render({'Field': lazy}, 'application/xml')
+ self.assertSOAPContains(content, 'hello')
+
+ def assertSOAPContains(self, xml, string):
+ self.assertTrue(xml.startswith('\n'))
+ self.assertTrue(string in xml, '%r not in %r' % (string, xml))
\ No newline at end of file