mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-25 19:14:01 +03:00
XML Parsers
This commit is contained in:
parent
c10a95de08
commit
48c7171aa0
|
@ -1,6 +1,7 @@
|
|||
from django.template import RequestContext, loader
|
||||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
||||
import json
|
||||
from utils import dict2xml
|
||||
|
||||
class BaseEmitter(object):
|
||||
def __init__(self, resource, request, status, headers):
|
||||
|
@ -38,7 +39,8 @@ class JSONEmitter(BaseEmitter):
|
|||
return json.dumps(output)
|
||||
|
||||
class XMLEmitter(BaseEmitter):
|
||||
pass
|
||||
def emit(self, output):
|
||||
return dict2xml(output)
|
||||
|
||||
class HTMLEmitter(TemplatedEmitter):
|
||||
template = 'emitter.html'
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
from django.http import HttpResponse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
||||
from rest import emitters, parsers, utils
|
||||
from decimal import Decimal
|
||||
|
||||
for (key, val) in STATUS_CODE_TEXT.items():
|
||||
locals()["STATUS_%d_%s" % (key, val.replace(' ', '_'))] = key
|
||||
#
|
||||
STATUS_400_BAD_REQUEST = 400
|
||||
STATUS_405_METHOD_NOT_ALLOWED = 405
|
||||
STATUS_406_NOT_ACCEPTABLE = 406
|
||||
STATUS_415_UNSUPPORTED_MEDIA_TYPE = 415
|
||||
STATUS_500_INTERNAL_SERVER_ERROR = 500
|
||||
STATUS_501_NOT_IMPLEMENTED = 501
|
||||
|
||||
|
||||
class ResourceException(Exception):
|
||||
|
@ -30,7 +34,8 @@ class Resource(object):
|
|||
|
||||
parsers = { 'application/json': parsers.JSONParser,
|
||||
'application/xml': parsers.XMLParser,
|
||||
'application/x-www-form-urlencoded': parsers.FormParser }
|
||||
'application/x-www-form-urlencoded': parsers.FormParser,
|
||||
'multipart/form-data': parsers.FormParser }
|
||||
|
||||
create_form = None
|
||||
update_form = None
|
||||
|
@ -74,11 +79,17 @@ class Resource(object):
|
|||
def _determine_parser(self, request):
|
||||
"""Return the appropriate parser for the input, given the client's 'Content-Type' header,
|
||||
and the content types that this Resource knows how to parse."""
|
||||
content_type = request.META.get('CONTENT_TYPE', 'application/x-www-form-urlencoded')
|
||||
split = content_type.split(';', 1)
|
||||
if len(split) > 1:
|
||||
content_type = split[0]
|
||||
content_type = content_type.strip()
|
||||
|
||||
try:
|
||||
return self.parsers[request.META['CONTENT_TYPE']]
|
||||
except:
|
||||
return self.parsers[content_type]
|
||||
except KeyError:
|
||||
raise ResourceException(STATUS_415_UNSUPPORTED_MEDIA_TYPE,
|
||||
{'detail': 'Unsupported media type'})
|
||||
{'detail': 'Unsupported content type \'%s\'' % content_type})
|
||||
|
||||
def _determine_emitter(self, request):
|
||||
"""Return the appropriate emitter for the output, given the client's 'Accept' header,
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
# From piston...
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.xmlutils import SimplerXMLGenerator
|
||||
try:
|
||||
import cStringIO as StringIO
|
||||
except ImportError:
|
||||
import StringIO
|
||||
|
||||
# From piston
|
||||
def coerce_put_post(request):
|
||||
"""
|
||||
Django doesn't particularly understand REST.
|
||||
|
@ -37,3 +45,126 @@ def coerce_put_post(request):
|
|||
request.META['REQUEST_METHOD'] = 'PUT'
|
||||
|
||||
request.PUT = request.POST
|
||||
|
||||
# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
|
||||
#class object_dict(dict):
|
||||
# """object view of dict, you can
|
||||
# >>> a = object_dict()
|
||||
# >>> a.fish = 'fish'
|
||||
# >>> a['fish']
|
||||
# 'fish'
|
||||
# >>> a['water'] = 'water'
|
||||
# >>> a.water
|
||||
# 'water'
|
||||
# >>> a.test = {'value': 1}
|
||||
# >>> a.test2 = object_dict({'name': 'test2', 'value': 2})
|
||||
# >>> a.test, a.test2.name, a.test2.value
|
||||
# (1, 'test2', 2)
|
||||
# """
|
||||
# def __init__(self, initd=None):
|
||||
# if initd is None:
|
||||
# initd = {}
|
||||
# dict.__init__(self, initd)
|
||||
#
|
||||
# def __getattr__(self, item):
|
||||
# d = self.__getitem__(item)
|
||||
# # if value is the only key in object, you can omit it
|
||||
# if isinstance(d, dict) and 'value' in d and len(d) == 1:
|
||||
# return d['value']
|
||||
# else:
|
||||
# return d
|
||||
#
|
||||
# def __setattr__(self, item, value):
|
||||
# self.__setitem__(item, value)
|
||||
|
||||
|
||||
# From xml2dict
|
||||
class XML2Dict(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _parse_node(self, node):
|
||||
node_tree = {}
|
||||
# Save attrs and text, hope there will not be a child with same name
|
||||
if node.text:
|
||||
node_tree = node.text
|
||||
for (k,v) in node.attrib.items():
|
||||
k,v = self._namespace_split(k, v)
|
||||
node_tree[k] = v
|
||||
#Save childrens
|
||||
for child in node.getchildren():
|
||||
tag, tree = self._namespace_split(child.tag, self._parse_node(child))
|
||||
if tag not in node_tree: # the first time, so store it in dict
|
||||
node_tree[tag] = tree
|
||||
continue
|
||||
old = node_tree[tag]
|
||||
if not isinstance(old, list):
|
||||
node_tree.pop(tag)
|
||||
node_tree[tag] = [old] # multi times, so change old dict to a list
|
||||
node_tree[tag].append(tree) # add the new one
|
||||
|
||||
return node_tree
|
||||
|
||||
|
||||
def _namespace_split(self, tag, value):
|
||||
"""
|
||||
Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
|
||||
ns = http://cs.sfsu.edu/csc867/myscheduler
|
||||
name = patients
|
||||
"""
|
||||
result = re.compile("\{(.*)\}(.*)").search(tag)
|
||||
if result:
|
||||
print tag
|
||||
value.namespace, tag = result.groups()
|
||||
return (tag, value)
|
||||
|
||||
def parse(self, file):
|
||||
"""parse a xml file to a dict"""
|
||||
f = open(file, 'r')
|
||||
return self.fromstring(f.read())
|
||||
|
||||
def fromstring(self, s):
|
||||
"""parse a string"""
|
||||
t = ET.fromstring(s)
|
||||
unused_root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t))
|
||||
return root_tree
|
||||
|
||||
|
||||
def xml2dict(input):
|
||||
return XML2Dict().fromstring(input)
|
||||
|
||||
|
||||
# Piston:
|
||||
class XMLEmitter():
|
||||
def _to_xml(self, xml, data):
|
||||
if isinstance(data, (list, tuple)):
|
||||
for item in data:
|
||||
xml.startElement("resource", {})
|
||||
self._to_xml(xml, item)
|
||||
xml.endElement("resource")
|
||||
|
||||
elif isinstance(data, dict):
|
||||
for key, value in data.iteritems():
|
||||
xml.startElement(key, {})
|
||||
self._to_xml(xml, value)
|
||||
xml.endElement(key)
|
||||
|
||||
else:
|
||||
xml.characters(smart_unicode(data))
|
||||
|
||||
def dict2xml(self, data):
|
||||
stream = StringIO.StringIO()
|
||||
|
||||
xml = SimplerXMLGenerator(stream, "utf-8")
|
||||
xml.startDocument()
|
||||
xml.startElement("content", {})
|
||||
|
||||
self._to_xml(xml, data)
|
||||
|
||||
xml.endElement("content")
|
||||
xml.endDocument()
|
||||
return stream.getvalue()
|
||||
|
||||
def dict2xml(input):
|
||||
return XMLEmitter().dict2xml(input)
|
||||
|
|
|
@ -7,7 +7,9 @@ Replace these with more appropriate tests for your application.
|
|||
|
||||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
from testapp.views import ReadOnlyResource, MirroringWriteResource
|
||||
from testapp import views
|
||||
import json
|
||||
from rest.utils import xml2dict, dict2xml
|
||||
|
||||
class AcceptHeaderTests(TestCase):
|
||||
def assert_accept_mimetype(self, mimetype, expect=None, expect_match=True):
|
||||
|
@ -18,7 +20,7 @@ class AcceptHeaderTests(TestCase):
|
|||
if expect is None:
|
||||
expect = mimetype
|
||||
|
||||
resp = self.client.get(reverse(ReadOnlyResource), HTTP_ACCEPT=mimetype)
|
||||
resp = self.client.get(reverse(views.ReadOnlyResource), HTTP_ACCEPT=mimetype)
|
||||
|
||||
if expect_match:
|
||||
self.assertEquals(resp['content-type'], expect)
|
||||
|
@ -41,14 +43,63 @@ class AcceptHeaderTests(TestCase):
|
|||
self.assert_accept_mimetype('application/invalid', expect_match=False)
|
||||
|
||||
def test_invalid_accept_header_returns_406(self):
|
||||
resp = self.client.get(reverse(ReadOnlyResource), HTTP_ACCEPT='invalid/invalid')
|
||||
resp = self.client.get(reverse(views.ReadOnlyResource), HTTP_ACCEPT='invalid/invalid')
|
||||
self.assertEquals(resp.status_code, 406)
|
||||
|
||||
class AllowedMethodsTests(TestCase):
|
||||
def test_write_on_read_only_resource_returns_405(self):
|
||||
resp = self.client.put(reverse(ReadOnlyResource), {})
|
||||
def test_reading_read_only_allowed(self):
|
||||
resp = self.client.get(reverse(views.ReadOnlyResource))
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
def test_writing_read_only_not_allowed(self):
|
||||
resp = self.client.put(reverse(views.ReadOnlyResource), {})
|
||||
self.assertEquals(resp.status_code, 405)
|
||||
|
||||
def test_read_on_write_only_resource_returns_405(self):
|
||||
resp = self.client.get(reverse(MirroringWriteResource))
|
||||
def test_reading_write_only_not_allowed(self):
|
||||
resp = self.client.get(reverse(views.WriteOnlyResource))
|
||||
self.assertEquals(resp.status_code, 405)
|
||||
|
||||
def test_writing_write_only_allowed(self):
|
||||
resp = self.client.put(reverse(views.WriteOnlyResource), {})
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
class EncodeDecodeTests(TestCase):
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
self.input = {'a': 1, 'b': 'example'}
|
||||
|
||||
def test_encode_form_decode_json(self):
|
||||
content = self.input
|
||||
resp = self.client.put(reverse(views.WriteOnlyResource), content, HTTP_ACCEPT='application/json')
|
||||
output = json.loads(resp.content)
|
||||
self.assertEquals(self.input, output)
|
||||
|
||||
def test_encode_json_decode_json(self):
|
||||
content = json.dumps(self.input)
|
||||
resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/json')
|
||||
output = json.loads(resp.content)
|
||||
self.assertEquals(self.input, output)
|
||||
|
||||
def test_encode_xml_decode_json(self):
|
||||
content = dict2xml(self.input)
|
||||
resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/json')
|
||||
output = json.loads(resp.content)
|
||||
self.assertEquals(self.input, output)
|
||||
|
||||
def test_encode_form_decode_xml(self):
|
||||
content = self.input
|
||||
resp = self.client.put(reverse(views.WriteOnlyResource), content, HTTP_ACCEPT='application/xml')
|
||||
output = xml2dict(resp.content)
|
||||
self.assertEquals(self.input, output)
|
||||
|
||||
def test_encode_json_decode_xml(self):
|
||||
content = json.dumps(self.input)
|
||||
resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/xml')
|
||||
output = xml2dict(resp.content)
|
||||
self.assertEquals(self.input, output)
|
||||
|
||||
def test_encode_xml_decode_xml(self):
|
||||
content = dict2xml(self.input)
|
||||
resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/xml')
|
||||
output = xml2dict(resp.content)
|
||||
self.assertEquals(self.input, output)
|
|
@ -3,6 +3,6 @@ from django.conf.urls.defaults import patterns
|
|||
urlpatterns = patterns('testapp.views',
|
||||
(r'^$', 'RootResource'),
|
||||
(r'^read-only$', 'ReadOnlyResource'),
|
||||
(r'^write-only$', 'MirroringWriteResource'),
|
||||
(r'^write-only$', 'WriteOnlyResource'),
|
||||
(r'^read-write$', 'ReadWriteResource'),
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ class RootResource(Resource):
|
|||
|
||||
def read(self, headers={}, *args, **kwargs):
|
||||
return (200, {'read-only-api': self.reverse(ReadOnlyResource),
|
||||
'write-only-api': self.reverse(MirroringWriteResource),
|
||||
'write-only-api': self.reverse(WriteOnlyResource),
|
||||
'read-write-api': self.reverse(ReadWriteResource)}, {})
|
||||
|
||||
|
||||
|
@ -23,12 +23,12 @@ class ReadOnlyResource(Resource):
|
|||
'ExampleDecimal': 1.0}, {})
|
||||
|
||||
|
||||
class MirroringWriteResource(Resource):
|
||||
class WriteOnlyResource(Resource):
|
||||
"""This is my docstring
|
||||
"""
|
||||
allowed_methods = ('PUT',)
|
||||
|
||||
def create(self, data, headers={}, *args, **kwargs):
|
||||
def update(self, data, headers={}, *args, **kwargs):
|
||||
return (200, data, {})
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user