Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
django-rest-framework
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
django-rest-framework
Commits
48c7171a
Commit
48c7171a
authored
Dec 31, 2010
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
XML Parsers
parent
c10a95de
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
216 additions
and
20 deletions
+216
-20
src/rest/emitters.py
+3
-1
src/rest/resource.py
+18
-7
src/rest/utils.py
+132
-1
src/testapp/tests.py
+59
-7
src/testapp/urls.py
+1
-1
src/testapp/views.py
+3
-3
No files found.
src/rest/emitters.py
View file @
48c7171a
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'
...
...
src/rest/resource.py
View file @
48c7171a
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,
...
...
src/rest/utils.py
View file @
48c7171a
# 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
)
src/testapp/tests.py
View file @
48c7171a
...
...
@@ -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
(
MirroringWrite
Resource
))
def
test_read
ing_write_only_not_allowed
(
self
):
resp
=
self
.
client
.
get
(
reverse
(
views
.
WriteOnly
Resource
))
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
)
\ No newline at end of file
src/testapp/urls.py
View file @
48c7171a
...
...
@@ -3,6 +3,6 @@ from django.conf.urls.defaults import patterns
urlpatterns
=
patterns
(
'testapp.views'
,
(
r'^$'
,
'RootResource'
),
(
r'^read-only$'
,
'ReadOnlyResource'
),
(
r'^write-only$'
,
'
MirroringWrite
Resource'
),
(
r'^write-only$'
,
'
WriteOnly
Resource'
),
(
r'^read-write$'
,
'ReadWriteResource'
),
)
src/testapp/views.py
View file @
48c7171a
...
...
@@ -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
(
MirroringWrite
Resource
),
'write-only-api'
:
self
.
reverse
(
WriteOnly
Resource
),
'read-write-api'
:
self
.
reverse
(
ReadWriteResource
)},
{})
...
...
@@ -23,12 +23,12 @@ class ReadOnlyResource(Resource):
'ExampleDecimal'
:
1.0
},
{})
class
MirroringWrite
Resource
(
Resource
):
class
WriteOnly
Resource
(
Resource
):
"""This is my docstring
"""
allowed_methods
=
(
'PUT'
,)
def
cre
ate
(
self
,
data
,
headers
=
{},
*
args
,
**
kwargs
):
def
upd
ate
(
self
,
data
,
headers
=
{},
*
args
,
**
kwargs
):
return
(
200
,
data
,
{})
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment