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
42825e44
Commit
42825e44
authored
Jan 11, 2011
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Sample example working
parent
95ac2396
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
393 additions
and
249 deletions
+393
-249
src/rest/emitters.py
+1
-1
src/rest/resource.py
+93
-32
src/rest/templates/emitter.html
+2
-2
src/rest/templatetags/urlize_quoted_links.py
+5
-1
src/rest/templatetags/urlize_quoted_links.pyc
+0
-0
src/testapp/models.py
+62
-36
src/testapp/tests.py
+129
-113
src/testapp/urls.py
+13
-8
src/testapp/views.py
+88
-56
No files found.
src/rest/emitters.py
View file @
42825e44
...
...
@@ -18,7 +18,7 @@ class TemplatedEmitter(BaseEmitter):
template
=
None
def
emit
(
self
,
output
):
content
=
json
.
dumps
(
output
,
indent
=
4
)
content
=
json
.
dumps
(
output
,
indent
=
4
,
sort_keys
=
True
)
template
=
loader
.
get_template
(
self
.
template
)
context
=
RequestContext
(
self
.
request
,
{
'content'
:
content
,
...
...
src/rest/resource.py
View file @
42825e44
...
...
@@ -137,15 +137,20 @@ class Resource(object):
def
determine_form
(
self
,
data
=
None
):
def
determine_form
(
self
,
input_data
=
None
,
return_
data
=
None
):
"""Optionally return a Django Form instance, which may be used for validation
and/or rendered by an HTML/XHTML emitter.
The
data argument will be non Null if the form is required to be bound to some deserialized
input data, or Null if the form is required to be unbound
.
The
input_data or return_data arguments can be used to bind the form either to the deserialized input,
or to a return object
.
"""
if
self
.
form
:
return
self
.
form
(
data
)
if
input_data
:
return
self
.
form
(
input_data
)
elif
return_data
:
return
self
.
form
(
return_data
)
else
:
return
self
.
form
()
return
None
...
...
@@ -259,12 +264,13 @@ class Resource(object):
if
method
in
(
'PUT'
,
'POST'
):
parser
=
self
.
determine_parser
(
request
)
data
=
parser
(
self
,
request
)
.
parse
(
request
.
raw_post_data
)
form
=
self
.
determine_form
(
data
)
form
=
self
.
determine_form
(
input_data
=
data
)
data
=
self
.
cleanup_request
(
data
,
form
)
(
status
,
ret
,
headers
)
=
func
(
data
,
request
.
META
,
*
args
,
**
kwargs
)
else
:
(
status
,
ret
,
headers
)
=
func
(
request
.
META
,
*
args
,
**
kwargs
)
form
=
self
.
determine_form
(
return_data
=
ret
)
except
ResourceException
,
exc
:
...
...
@@ -274,7 +280,7 @@ class Resource(object):
if
emitter
is
None
:
mimetype
,
emitter
=
self
.
emitters
[
0
]
#
Use a form unbound to any data
if one has not yet been created
#
Create an unbound form
if one has not yet been created
if
form
is
None
:
form
=
self
.
determine_form
()
...
...
@@ -284,7 +290,6 @@ class Resource(object):
# Serialize the response content
ret
=
self
.
cleanup_response
(
ret
)
content
=
emitter
(
self
,
request
,
status
,
headers
,
form
)
.
emit
(
ret
)
print
content
# Build the HTTP Response
resp
=
HttpResponse
(
content
,
mimetype
=
mimetype
,
status
=
status
)
...
...
@@ -308,7 +313,7 @@ class ModelResource(Resource):
fields
=
None
form_fields
=
None
def
determine_form
(
self
,
data
=
None
):
def
determine_form
(
self
,
input_data
=
None
,
return_
data
=
None
):
"""Return a form that may be used in validation and/or rendering an html emitter"""
if
self
.
form
:
return
self
.
form
...
...
@@ -317,12 +322,14 @@ class ModelResource(Resource):
class
NewModelForm
(
ModelForm
):
class
Meta
:
model
=
self
.
model
fields
=
self
.
form_fields
if
self
.
form_fields
else
self
.
fields
fields
=
self
.
form_fields
if
self
.
form_fields
else
None
#
self.fields
if
data
is
None
:
return
NewModelForm
()
if
input_data
:
return
NewModelForm
(
input_data
)
elif
return_data
:
return
NewModelForm
(
instance
=
return_data
)
else
:
return
NewModelForm
(
data
)
return
NewModelForm
()
else
:
return
None
...
...
@@ -359,6 +366,12 @@ class ModelResource(Resource):
ret
=
_list
(
thing
)
elif
isinstance
(
thing
,
dict
):
ret
=
_dict
(
thing
)
elif
isinstance
(
thing
,
int
):
ret
=
thing
elif
isinstance
(
thing
,
bool
):
ret
=
thing
elif
isinstance
(
thing
,
type
(
None
)):
ret
=
thing
elif
isinstance
(
thing
,
decimal
.
Decimal
):
ret
=
str
(
thing
)
elif
isinstance
(
thing
,
Model
):
...
...
@@ -417,7 +430,7 @@ class ModelResource(Resource):
ret
=
{
}
#handler = self.in_typemapper(type(data), self.anonymous) # TRC
handler
=
None
# TRC
get_absolute_ur
i
=
False
get_absolute_ur
l
=
False
if
handler
or
fields
:
v
=
lambda
f
:
getattr
(
data
,
f
.
attname
)
...
...
@@ -444,12 +457,13 @@ class ModelResource(Resource):
for
field
in
get_fields
.
copy
():
if
exclude
.
match
(
field
):
get_fields
.
discard
(
field
)
get_absolute_url
=
True
else
:
get_fields
=
set
(
fields
)
if
'absolute_uri'
in
get_fields
:
# MOVED (TRC)
get_absolute_uri
=
True
if
'absolute_url'
in
get_fields
:
# MOVED (TRC)
get_absolute_url
=
True
met_fields
=
_method_fields
(
handler
,
get_fields
)
# TRC
...
...
@@ -508,14 +522,37 @@ class ModelResource(Resource):
# ret[maybe_field] = _any(handler_f(data))
else
:
# Add absolute_url if it exists
get_absolute_url
=
True
# Add all the fields
for
f
in
data
.
_meta
.
fields
:
ret
[
f
.
attname
]
=
_any
(
getattr
(
data
,
f
.
attname
))
if
f
.
attname
!=
'id'
:
ret
[
f
.
attname
]
=
_any
(
getattr
(
data
,
f
.
attname
))
fields
=
dir
(
data
.
__class__
)
+
ret
.
keys
()
add_ons
=
[
k
for
k
in
dir
(
data
)
if
k
not
in
fields
]
# Add all the propertiess
klass
=
data
.
__class__
for
attr
in
dir
(
klass
):
if
not
attr
.
startswith
(
'_'
)
and
not
attr
in
(
'pk'
,
'id'
)
and
isinstance
(
getattr
(
klass
,
attr
,
None
),
property
):
#if attr.endswith('_url') or attr.endswith('_uri'):
# ret[attr] = self.make_absolute(_any(getattr(data, attr)))
#else:
ret
[
attr
]
=
_any
(
getattr
(
data
,
attr
))
#fields = dir(data.__class__) + ret.keys()
#add_ons = [k for k in dir(data) if k not in fields and not k.startswith('_')]
#print add_ons
###print dir(data.__class__)
#from django.db.models import Model
#model_fields = dir(Model)
#for attr in dir(data):
## #if attr.startswith('_'):
## # continue
# if (attr in fields) and not (attr in model_fields) and not attr.startswith('_'):
# print attr, type(getattr(data, attr, None)), attr in fields, attr in model_fields
for
k
in
add_ons
:
ret
[
k
]
=
_any
(
getattr
(
data
,
k
))
#
for k in add_ons:
#
ret[k] = _any(getattr(data, k))
# TRC
# resouce uri
...
...
@@ -532,9 +569,13 @@ class ModelResource(Resource):
# except: pass
# absolute uri
if
hasattr
(
data
,
'get_absolute_url'
)
and
get_absolute_ur
i
:
try
:
ret
[
'absolute_ur
i
'
]
=
self
.
make_absolute
(
data
.
get_absolute_url
())
if
hasattr
(
data
,
'get_absolute_url'
)
and
get_absolute_ur
l
:
try
:
ret
[
'absolute_ur
l
'
]
=
self
.
make_absolute
(
data
.
get_absolute_url
())
except
:
pass
for
key
,
val
in
ret
.
items
():
if
key
.
endswith
(
'_url'
)
or
key
.
endswith
(
'_uri'
):
ret
[
key
]
=
self
.
make_absolute
(
val
)
return
ret
...
...
@@ -560,8 +601,9 @@ class ModelResource(Resource):
return
_any
(
data
,
self
.
fields
)
def
create
(
self
,
data
,
headers
=
{}):
instance
=
self
.
model
(
**
data
)
def
create
(
self
,
data
,
headers
=
{},
*
args
,
**
kwargs
):
all_kw_args
=
dict
(
data
.
items
()
+
kwargs
.
items
())
instance
=
self
.
model
(
**
all_kw_args
)
instance
.
save
()
headers
=
{}
if
hasattr
(
instance
,
'get_absolute_url'
):
...
...
@@ -569,17 +611,37 @@ class ModelResource(Resource):
return
(
201
,
instance
,
headers
)
def
read
(
self
,
headers
=
{},
*
args
,
**
kwargs
):
instance
=
self
.
model
.
objects
.
get
(
**
kwargs
)
try
:
instance
=
self
.
model
.
objects
.
get
(
**
kwargs
)
except
self
.
model
.
DoesNotExist
:
return
(
404
,
''
,
{})
return
(
200
,
instance
,
{})
def
update
(
self
,
data
,
headers
=
{},
*
args
,
**
kwargs
):
instance
=
self
.
model
.
objects
.
get
(
**
kwargs
)
for
(
key
,
val
)
in
data
.
items
():
setattr
(
instance
,
key
,
val
)
try
:
instance
=
self
.
model
.
objects
.
get
(
**
kwargs
)
for
(
key
,
val
)
in
data
.
items
():
setattr
(
instance
,
key
,
val
)
except
self
.
model
.
DoesNotExist
:
instance
=
self
.
model
(
**
data
)
instance
.
save
()
instance
.
save
()
return
(
200
,
instance
,
{})
def
delete
(
self
,
headers
=
{},
*
args
,
**
kwargs
):
instance
=
self
.
model
.
objects
.
get
(
**
kwargs
)
instance
.
delete
()
return
(
204
,
''
,
{})
\ No newline at end of file
return
(
204
,
''
,
{})
class
QueryModelResource
(
ModelResource
):
allowed_methods
=
(
'read'
,)
def
determine_form
(
self
,
input_data
=
None
,
return_data
=
None
):
return
None
def
read
(
self
,
headers
=
{},
*
args
,
**
kwargs
):
query
=
self
.
model
.
objects
.
all
()
return
(
200
,
query
,
{})
src/rest/templates/emitter.html
View file @
42825e44
...
...
@@ -11,10 +11,10 @@
<body>
<h1>
{{ resource_name }}
</h1>
<p>
{{ resource_doc }}
</p>
<pre>
{% autoescape off %}
<b>
{{ status }} {{ reason }}
</b>
<pre>
<b>
{{ status }} {{ reason }}
</b>
{% autoescape off %}
{% for key, val in headers.items %}
<b>
{{ key }}:
</b>
{{ val|urlize_quoted_links }}
{% endfor %}
{{ content|urlize_quoted_links }}
{% endautoescape %}
</pre>
{{ content|urlize_quoted_links }}
</pre>
{% endautoescape %}
{% if 'read' in resource.allowed_operations %}
<div
class=
'action'
>
...
...
src/rest/templatetags/urlize_quoted_links.py
View file @
42825e44
...
...
@@ -33,7 +33,7 @@ html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<st
hard_coded_bullets_re
=
re
.
compile
(
r'((?:<p>(?:
%
s).*?[a-zA-Z].*?</p>\s*)+)'
%
'|'
.
join
([
re
.
escape
(
x
)
for
x
in
DOTS
]),
re
.
DOTALL
)
trailing_empty_content_re
=
re
.
compile
(
r'(?:<p>(?: |\s|<br \/>)*?</p>\s*)+\Z'
)
def
urlize_quoted_links
(
text
,
trim_url_limit
=
None
,
nofollow
=
False
,
autoescape
=
Fals
e
):
def
urlize_quoted_links
(
text
,
trim_url_limit
=
None
,
nofollow
=
False
,
autoescape
=
Tru
e
):
"""
Converts any URLs in text into clickable links.
...
...
@@ -90,6 +90,10 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=False, autoescape=Fa
words
[
i
]
=
escape
(
word
)
return
u''
.
join
(
words
)
#urlize_quoted_links.needs_autoescape = True
urlize_quoted_links
.
is_safe
=
True
# Register urlize_quoted_links as a custom filter
# http://docs.djangoproject.com/en/dev/howto/custom-template-tags/
register
=
template
.
Library
()
...
...
src/rest/templatetags/urlize_quoted_links.pyc
View file @
42825e44
No preview for this file type
src/testapp/models.py
View file @
42825e44
from
django.db
import
models
from
django.template.defaultfilters
import
slugify
from
datetime
import
datetime
import
uuid
def
uuid_str
():
return
str
(
uuid
.
uuid1
())
class
ExampleModel
(
models
.
Model
):
num
=
models
.
IntegerField
(
default
=
2
,
choices
=
((
1
,
'one'
),
(
2
,
'two'
)))
hidden_num
=
models
.
IntegerField
(
verbose_name
=
'Something'
,
help_text
=
'HELP'
)
text
=
models
.
TextField
(
blank
=
False
)
another
=
models
.
CharField
(
max_length
=
10
)
#
class ExampleModel(models.Model):
#
num = models.IntegerField(default=2, choices=((1,'one'), (2, 'two')))
#
hidden_num = models.IntegerField(verbose_name='Something', help_text='HELP')
#
text = models.TextField(blank=False)
#
another = models.CharField(max_length=10)
class
ExampleContainer
(
models
.
Model
):
"""Container. Has a key, a name, and some internal data, and contains a set of items."""
key
=
models
.
CharField
(
primary_key
=
True
,
default
=
uuid_str
,
max_length
=
36
,
editable
=
False
)
name
=
models
.
CharField
(
max_length
=
256
)
internal
=
models
.
IntegerField
(
default
=
0
)
#class ExampleContainer(models.Model):
# """Container. Has a key, a name, and some internal data, and contains a set of items."""
# key = models.CharField(primary_key=True, default=uuid_str, max_length=36, editable=False)
# name = models.CharField(max_length=256)
# internal = models.IntegerField(default=0)
# @models.permalink
# def get_absolute_url(self):
# return ('testapp.views.ContainerInstance', [self.key])
@models.permalink
def
get_absolute_url
(
self
):
return
(
'testapp.views.ContainerInstance'
,
[
self
.
key
])
#class ExampleItem(models.Model):
# """Item. Belongs to a container and has an index number and a note.
# Items are uniquely identified by their container and index number."""
# container = models.ForeignKey(ExampleContainer, related_name='items')
# index = models.IntegerField()
# note = models.CharField(max_length=1024)
# unique_together = (container, index)
class
ExampleItem
(
models
.
Model
):
"""Item. Belongs to a container and has an index number and a note.
Items are uniquely identified by their container and index number."""
container
=
models
.
ForeignKey
(
ExampleContainer
,
related_name
=
'items'
)
index
=
models
.
IntegerField
()
note
=
models
.
CharField
(
max_length
=
1024
)
unique_together
=
(
container
,
index
)
RATING_CHOICES
=
((
0
,
'Awful'
),
(
1
,
'Poor'
),
(
2
,
'OK'
),
(
3
,
'Good'
),
(
4
,
'Excellent'
))
class
BlogPost
(
models
.
Model
):
slug
=
models
.
SlugField
(
editable
=
False
,
primary_key
=
True
,
default
=
'blah'
)
title
=
models
.
CharField
(
max_length
=
128
)
content
=
models
.
TextField
()
when
=
models
.
DateTimeField
(
editable
=
False
)
key
=
models
.
CharField
(
primary_key
=
True
,
max_length
=
64
,
default
=
uuid_str
,
editable
=
False
)
title
=
models
.
CharField
(
max_length
=
128
,
help_text
=
'The article title (Required)'
)
content
=
models
.
TextField
(
help_text
=
'The article body (Required)'
)
created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
slug
=
models
.
SlugField
(
editable
=
False
,
default
=
''
)
class
Meta
:
ordering
=
(
'created'
,)
@models.permalink
def
get_absolute_url
(
self
):
return
(
'testapp.views.BlogPostInstance'
,
(
self
.
slug
,))
return
(
'testapp.views.BlogPostInstance'
,
(
self
.
key
,))
@property
@models.permalink
def
comments_url
(
self
):
"""Link to a resource which lists all comments for this blog post."""
return
(
'testapp.views.CommentList'
,
(
self
.
key
,))
@property
@models.permalink
def
comment_url
(
self
):
"""Link to a resource which can create a comment for this blog post."""
return
(
'testapp.views.CommentCreator'
,
(
self
.
key
,))
def
__unicode__
(
self
):
return
self
.
title
def
save
(
self
,
*
args
,
**
kwargs
):
self
.
slug
=
slugify
(
self
.
title
)
self
.
when
=
datetime
.
now
()
super
(
self
.
__class__
,
self
)
.
save
(
*
args
,
**
kwargs
)
class
Comment
(
models
.
Model
):
blogpost
=
models
.
ForeignKey
(
BlogPost
,
related_name
=
'comments'
)
name
=
models
.
CharField
(
max_length
=
128
)
content
=
models
.
TextField
()
when
=
models
.
DateTimeField
(
auto_now_add
=
True
)
blogpost
=
models
.
ForeignKey
(
BlogPost
,
editable
=
False
,
related_name
=
'comments'
)
username
=
models
.
CharField
(
max_length
=
128
,
help_text
=
'Please enter a username (Required)'
)
comment
=
models
.
TextField
(
help_text
=
'Enter your comment here (Required)'
)
rating
=
models
.
IntegerField
(
blank
=
True
,
null
=
True
,
choices
=
RATING_CHOICES
,
help_text
=
'Please rate the blog post (Optional)'
)
created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
@models.permalink
def
get_absolute_url
(
self
):
return
(
'testapp.views.CommentInstance'
,
(
self
.
blogpost
.
slug
,
self
.
id
))
def
save
(
self
):
self
.
index
=
self
.
blogpost
.
comments
.
count
()
\ No newline at end of file
return
(
'testapp.views.CommentInstance'
,
(
self
.
blogpost
.
key
,
self
.
id
))
@property
@models.permalink
def
blogpost_url
(
self
):
return
(
'testapp.views.BlogPostInstance'
,
(
self
.
blogpost
.
key
,))
src/testapp/tests.py
View file @
42825e44
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
"""Test a range of REST API usage of the example application.
"""
from
django.test
import
TestCase
...
...
@@ -13,134 +9,154 @@ import json
class
AcceptHeaderTests
(
TestCase
):
def
assert_accept_mimetype
(
self
,
mimetype
,
expect
=
None
,
expect_match
=
True
):
"""
Assert that a request with given mimetype in the accept header,
gives a response with the appropriate content-type.
"""
"""Test correct behaviour of the Accept header as specified by RFC 2616:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"""
def
assert_accept_mimetype
(
self
,
mimetype
,
expect
=
None
):
"""Assert that a request with given mimetype in the accept header,
gives a response with the appropriate content-type."""
if
expect
is
None
:
expect
=
mimetype
resp
=
self
.
client
.
get
(
reverse
(
views
.
R
eadOnly
Resource
),
HTTP_ACCEPT
=
mimetype
)
resp
=
self
.
client
.
get
(
reverse
(
views
.
R
oot
Resource
),
HTTP_ACCEPT
=
mimetype
)
if
expect_match
:
self
.
assertEquals
(
resp
[
'content-type'
],
expect
)
else
:
self
.
assertNotEquals
(
resp
[
'content-type'
],
expect
)
self
.
assertEquals
(
resp
[
'content-type'
],
expect
)
def
test_accept_xml
(
self
):
self
.
assert_accept_mimetype
(
'application/xml'
)
def
test_accept_json
(
self
):
"""Ensure server responds with Content-Type of JSON when requested."""
self
.
assert_accept_mimetype
(
'application/json'
)
def
test_accept_xml_prefered_to_json
(
self
):
self
.
assert_accept_mimetype
(
'application/xml,q=0.9;application/json,q=0.1'
,
expect
=
'application/xml'
)
def
test_accept_xml
(
self
):
"""Ensure server responds with Content-Type of XML when requested."""
self
.
assert_accept_mimetype
(
'application/xml'
)
def
test_accept_json_prefered_to_xml
(
self
):
def
test_accept_json_when_prefered_to_xml
(
self
):
"""Ensure server responds with Content-Type of JSON when it is the client's prefered choice."""
self
.
assert_accept_mimetype
(
'application/json,q=0.9;application/xml,q=0.1'
,
expect
=
'application/json'
)
def
test_dont_accept_invalid
(
self
):
self
.
assert_accept_mimetype
(
'application/invalid'
,
expect_match
=
False
)
def
test_accept_xml_when_prefered_to_json
(
self
):
"""Ensure server responds with Content-Type of XML when it is the client's prefered choice."""
self
.
assert_accept_mimetype
(
'application/xml,q=0.9;application/json,q=0.1'
,
expect
=
'application/xml'
)
def
test_default_json_prefered
(
self
):
"""Ensure server responds with JSON in preference to XML."""
self
.
assert_accept_mimetype
(
'application/json;application/xml'
,
expect
=
'application/json'
)
def
test_accept_generic_subtype_format
(
self
):
"""Ensure server responds with an appropriate type, when the subtype is left generic."""
self
.
assert_accept_mimetype
(
'text/*'
,
expect
=
'text/html'
)
def
test_accept_generic_type_format
(
self
):
"""Ensure server responds with an appropriate type, when the type and subtype are left generic."""
self
.
assert_accept_mimetype
(
'*/*'
,
expect
=
'application/json'
)
def
test_invalid_accept_header_returns_406
(
self
):
resp
=
self
.
client
.
get
(
reverse
(
views
.
ReadOnlyResource
),
HTTP_ACCEPT
=
'invalid/invalid'
)
"""Ensure server returns a 406 (not acceptable) response if we set the Accept header to junk."""
resp
=
self
.
client
.
get
(
reverse
(
views
.
RootResource
),
HTTP_ACCEPT
=
'invalid/invalid'
)
self
.
assertNotEquals
(
resp
[
'content-type'
],
'invalid/invalid'
)
self
.
assertEquals
(
resp
.
status_code
,
406
)
def
test_prefer_specific
(
self
):
self
.
fail
(
"Test not implemented"
)
def
test_prefer_specific_over_generic
(
self
):
# This test is broken right now
"""More specific accept types have precedence over less specific types."""
self
.
assert_accept_mimetype
(
'application/xml;*/*'
,
expect
=
'application/xml'
)
class
AllowedMethodsTests
(
TestCase
):
def
test_reading_read_only_allowed
(
self
):
resp
=
self
.
client
.
get
(
reverse
(
views
.
ReadOnlyResource
))
"""Basic tests to check that only allowed operations may be performed on a Resource"""
def
test_reading_a_read_only_resource_is_allowed
(
self
):
"""GET requests on a read only resource should default to a 200 (OK) response"""
resp
=
self
.
client
.
get
(
reverse
(
views
.
RootResource
))
self
.
assertEquals
(
resp
.
status_code
,
200
)
def
test_writing_read_only_not_allowed
(
self
):
resp
=
self
.
client
.
put
(
reverse
(
views
.
ReadOnlyResource
),
{})
def
test_writing_to_read_only_resource_is_not_allowed
(
self
):
"""PUT requests on a read only resource should default to a 405 (method not allowed) response"""
resp
=
self
.
client
.
put
(
reverse
(
views
.
RootResource
),
{})
self
.
assertEquals
(
resp
.
status_code
,
405
)
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
)
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'
)
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)
class
ModelTests
(
TestCase
):
def
test_create_container
(
self
):
content
=
json
.
dumps
({
'name'
:
'example'
})
resp
=
self
.
client
.
post
(
reverse
(
views
.
ContainerFactory
),
content
,
'application/json'
)
output
=
json
.
loads
(
resp
.
content
)
self
.
assertEquals
(
resp
.
status_code
,
201
)
self
.
assertEquals
(
output
[
'name'
],
'example'
)
self
.
assertEquals
(
set
(
output
.
keys
()),
set
((
'absolute_uri'
,
'name'
,
'key'
)))
class
CreatedModelTests
(
TestCase
):
def
setUp
(
self
):
content
=
json
.
dumps
({
'name'
:
'example'
})
resp
=
self
.
client
.
post
(
reverse
(
views
.
ContainerFactory
),
content
,
'application/json'
,
HTTP_ACCEPT
=
'application/json'
)
self
.
container
=
json
.
loads
(
resp
.
content
)
def
test_read_container
(
self
):
resp
=
self
.
client
.
get
(
self
.
container
[
"absolute_uri"
])
self
.
assertEquals
(
resp
.
status_code
,
200
)
container
=
json
.
loads
(
resp
.
content
)
self
.
assertEquals
(
container
,
self
.
container
)
def
test_delete_container
(
self
):
resp
=
self
.
client
.
delete
(
self
.
container
[
"absolute_uri"
])
self
.
assertEquals
(
resp
.
status_code
,
204
)
self
.
assertEquals
(
resp
.
content
,
''
)
def
test_update_container
(
self
):
self
.
container
[
'name'
]
=
'new'
content
=
json
.
dumps
(
self
.
container
)
resp
=
self
.
client
.
put
(
self
.
container
[
"absolute_uri"
],
content
,
'application/json'
)
self
.
assertEquals
(
resp
.
status_code
,
200
)
container
=
json
.
loads
(
resp
.
content
)
self
.
assertEquals
(
container
,
self
.
container
)
#
#
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)
#
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')
#
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)
#
#
class ModelTests(TestCase):
#
def test_create_container(self):
#
content = json.dumps({'name': 'example'})
#
resp = self.client.post(reverse(views.ContainerFactory), content, 'application/json')
#
output = json.loads(resp.content)
#
self.assertEquals(resp.status_code, 201)
#
self.assertEquals(output['name'], 'example')
#
self.assertEquals(set(output.keys()), set(('absolute_uri', 'name', 'key')))
#
#
class CreatedModelTests(TestCase):
#
def setUp(self):
#
content = json.dumps({'name': 'example'})
#
resp = self.client.post(reverse(views.ContainerFactory), content, 'application/json', HTTP_ACCEPT='application/json')
#
self.container = json.loads(resp.content)
#
#
def test_read_container(self):
#
resp = self.client.get(self.container["absolute_uri"])
#
self.assertEquals(resp.status_code, 200)
#
container = json.loads(resp.content)
#
self.assertEquals(container, self.container)
#
#
def test_delete_container(self):
#
resp = self.client.delete(self.container["absolute_uri"])
#
self.assertEquals(resp.status_code, 204)
#
self.assertEquals(resp.content, '')
#
#
def test_update_container(self):
#
self.container['name'] = 'new'
#
content = json.dumps(self.container)
#
resp = self.client.put(self.container["absolute_uri"], content, 'application/json')
#
self.assertEquals(resp.status_code, 200)
#
container = json.loads(resp.content)
#
self.assertEquals(container, self.container)
src/testapp/urls.py
View file @
42825e44
...
...
@@ -2,13 +2,18 @@ from django.conf.urls.defaults import patterns
urlpatterns
=
patterns
(
'testapp.views'
,
(
r'^$'
,
'RootResource'
),
(
r'^read-only$'
,
'ReadOnlyResource'
),
(
r'^write-only$'
,
'WriteOnlyResource'
),
(
r'^read-write$'
,
'ReadWriteResource'
),
(
r'^model$'
,
'ModelFormResource'
),
(
r'^container$'
,
'ContainerFactory'
),
(
r'^container/((?P<key>[^/]+))$'
,
'ContainerInstance'
),
#
(r'^read-only$', 'ReadOnlyResource'),
#
(r'^write-only$', 'WriteOnlyResource'),
#
(r'^read-write$', 'ReadWriteResource'),
#
(r'^model$', 'ModelFormResource'),
#
(r'^container$', 'ContainerFactory'),
#
(r'^container/((?P<key>[^/]+))$', 'ContainerInstance'),
(
r'^blogpost/create$'
,
'BlogPostCreator'
),
(
r'^blogposts/(?P<slug>[^/]+)'
,
'BlogPostInstance'
),
(
r'^blog-posts/$'
,
'BlogPostList'
),
(
r'^blog-post/$'
,
'BlogPostCreator'
),
(
r'^blog-post/(?P<key>[^/]+)/$'
,
'BlogPostInstance'
),
(
r'^blog-post/(?P<blogpost_id>[^/]+)/comments/$'
,
'CommentList'
),
(
r'^blog-post/(?P<blogpost_id>[^/]+)/comment/$'
,
'CommentCreator'
),
(
r'^blog-post/(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$'
,
'CommentInstance'
),
)
src/testapp/views.py
View file @
42825e44
from
rest.resource
import
Resource
,
ModelResource
from
testapp.forms
import
ExampleForm
from
testapp.models
import
ExampleModel
,
ExampleContainer
,
BlogPost
,
Comment
from
rest.resource
import
Resource
,
ModelResource
,
QueryModelResource
from
testapp.models
import
BlogPost
,
Comment
class
RootResource
(
Resource
):
"""This is
my docstring
"""
"""This is
the top level resource for the API.
All the sub-resources are discoverable from here.
"""
allowed_operations
=
(
'read'
,)
def
read
(
self
,
headers
=
{},
*
args
,
**
kwargs
):
return
(
200
,
{
'read-only-api'
:
self
.
reverse
(
ReadOnlyResource
),
'write-only-api'
:
self
.
reverse
(
WriteOnlyResource
),
'read-write-api'
:
self
.
reverse
(
ReadWriteResource
),
'model-api'
:
self
.
reverse
(
ModelFormResource
),
'create-container'
:
self
.
reverse
(
ContainerFactory
),
'blog-post-creator'
:
self
.
reverse
(
BlogPostCreator
)},
{})
return
(
200
,
{
'blog-posts'
:
self
.
reverse
(
BlogPostList
),
'blog-post'
:
self
.
reverse
(
BlogPostCreator
)},
{})
class
ReadOnlyResource
(
Resource
):
"""This is my docstring
"""
allowed_operations
=
(
'read'
,)
def
read
(
self
,
headers
=
{},
*
args
,
**
kwargs
):
return
(
200
,
{
'ExampleString'
:
'Example'
,
'ExampleInt'
:
1
,
'ExampleDecimal'
:
1.0
},
{})
# Blog Post Resources
class
BlogPostList
(
QueryModelResource
):
"""A resource which lists all existing blog posts."""
allowed_operations
=
(
'read'
,
)
model
=
BlogPost
class
WriteOnlyResource
(
Resource
):
"""This is my docstring
"""
allowed_operations
=
(
'update'
,)
def
update
(
self
,
data
,
headers
=
{},
*
args
,
**
kwargs
):
return
(
200
,
data
,
{})
class
BlogPostCreator
(
ModelResource
):
"""A resource with which blog posts may be created."""
allowed_operations
=
(
'create'
,)
model
=
BlogPost
fields
=
(
'created'
,
'title'
,
'slug'
,
'content'
,
'absolute_url'
,
'comment_url'
,
'comments_url'
)
class
ReadWriteResource
(
Resource
):
class
BlogPostInstance
(
ModelResource
):
"""A resource which represents a single blog post."""
allowed_operations
=
(
'read'
,
'update'
,
'delete'
)
create_form
=
ExampleForm
update_form
=
ExampleForm
model
=
BlogPost
fields
=
(
'created'
,
'title'
,
'slug'
,
'content'
,
'absolute_url'
,
'comment_url'
,
'comments_url'
)
class
ModelFormResource
(
ModelResource
):
allowed_operations
=
(
'read'
,
'update'
,
'delete'
)
model
=
ExampleModel
# Comment Resources
# Nice things: form validation is applied to any input type
# html forms for output
# output always serialized nicely
class
ContainerFactory
(
ModelResource
):
class
CommentList
(
QueryModelResource
):
"""A resource which lists all existing comments for a given blog post."""
allowed_operations
=
(
'read'
,
)
model
=
Comment
class
CommentCreator
(
ModelResource
):
"""A resource with which blog comments may be created for a given blog post."""
allowed_operations
=
(
'create'
,)
model
=
ExampleContainer
fields
=
(
'absolute_uri'
,
'name'
,
'key'
)
form_fields
=
(
'name'
,)
model
=
Comment
fields
=
(
'username'
,
'comment'
,
'created'
,
'rating'
,
'absolute_url'
,
'blogpost_url'
)
class
ContainerInstance
(
ModelResource
):
class
CommentInstance
(
ModelResource
):
"""A resource which represents a single comment."""
allowed_operations
=
(
'read'
,
'update'
,
'delete'
)
model
=
ExampleContainer
fields
=
(
'absolute_uri'
,
'name'
,
'key'
)
form_fields
=
(
'name'
,)
model
=
Comment
fields
=
(
'username'
,
'comment'
,
'created'
,
'rating'
,
'absolute_url'
,
'blogpost_url'
)
#
#'read-only-api': self.reverse(ReadOnlyResource),
# 'write-only-api': self.reverse(WriteOnlyResource),
# 'read-write-api': self.reverse(ReadWriteResource),
# 'model-api': self.reverse(ModelFormResource),
# 'create-container': self.reverse(ContainerFactory),
#
#class ReadOnlyResource(Resource):
# """This is my docstring
# """
# allowed_operations = ('read',)
#
# def read(self, headers={}, *args, **kwargs):
# return (200, {'ExampleString': 'Example',
# 'ExampleInt': 1,
# 'ExampleDecimal': 1.0}, {})
#
#
#class WriteOnlyResource(Resource):
# """This is my docstring
# """
# allowed_operations = ('update',)
#
# def update(self, data, headers={}, *args, **kwargs):
# return (200, data, {})
#
#
#class ReadWriteResource(Resource):
# allowed_operations = ('read', 'update', 'delete')
# create_form = ExampleForm
# update_form = ExampleForm
#
#
#class ModelFormResource(ModelResource):
# allowed_operations = ('read', 'update', 'delete')
# model = ExampleModel
#
## Nice things: form validation is applied to any input type
## html forms for output
## output always serialized nicely
#class ContainerFactory(ModelResource):
# allowed_operations = ('create',)
# model = ExampleContainer
# fields = ('absolute_uri', 'name', 'key')
# form_fields = ('name',)
#
#
#class ContainerInstance(ModelResource):
# allowed_operations = ('read', 'update', 'delete')
# model = ExampleContainer
# fields = ('absolute_uri', 'name', 'key')
# form_fields = ('name',)
#######################
class
BlogPostCreator
(
ModelResource
):
"""A Resource with which blog posts may be created.
This is distinct from blog post instance so that it is discoverable by the client.
(ie the client doens't need to know how to form a blog post url in order to create a blog post)"""
allowed_operations
=
(
'create'
,)
model
=
BlogPost
class
BlogPostInstance
(
ModelResource
):
"""Represents a single Blog Post."""
allowed_operations
=
(
'read'
,
'update'
,
'delete'
)
model
=
BlogPost
\ No newline at end of file
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