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
c56e48f5
Commit
c56e48f5
authored
Dec 30, 2010
by
tom christie tom@tomchristie.com
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add parsers, form validation, etc...
parent
a78f5784
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
156 additions
and
34 deletions
+156
-34
src/rest/emitters.py
+12
-4
src/rest/parsers.py
+6
-2
src/rest/resource.py
+86
-25
src/rest/templates/emitter.html
+40
-0
src/rest/templatetags/__init__.pyc
+0
-0
src/rest/templatetags/urlize_quoted_links.pyc
+0
-0
src/testapp/urls.py
+2
-1
src/testapp/views.py
+10
-2
No files found.
src/rest/emitters.py
View file @
c56e48f5
from
django.template
import
Context
,
loader
from
django.template
import
Request
Context
,
loader
from
django.core.handlers.wsgi
import
STATUS_CODE_TEXT
import
json
class
BaseEmitter
(
object
):
def
__init__
(
self
,
resource
,
status
,
headers
):
def
__init__
(
self
,
resource
,
request
,
status
,
headers
):
self
.
request
=
request
self
.
resource
=
resource
self
.
status
=
status
self
.
headers
=
headers
...
...
@@ -12,16 +13,23 @@ class BaseEmitter(object):
return
output
class
TemplatedEmitter
(
BaseEmitter
):
template
=
None
def
emit
(
self
,
output
):
content
=
json
.
dumps
(
output
,
indent
=
4
)
template
=
loader
.
get_template
(
self
.
template
)
context
=
Context
(
{
context
=
RequestContext
(
self
.
request
,
{
'content'
:
content
,
'status'
:
self
.
status
,
'reason'
:
STATUS_CODE_TEXT
.
get
(
self
.
status
,
''
),
'headers'
:
self
.
headers
,
'resource_name'
:
self
.
resource
.
__class__
.
__name__
,
'resource_doc'
:
self
.
resource
.
__doc__
'resource_doc'
:
self
.
resource
.
__doc__
,
'create_form'
:
self
.
resource
.
create_form
and
self
.
resource
.
create_form
()
or
None
,
'update_form'
:
self
.
resource
.
update_form
and
self
.
resource
.
update_form
()
or
None
,
'allowed_methods'
:
self
.
resource
.
allowed_methods
,
'request'
:
self
.
request
,
'resource'
:
self
.
resource
,
})
return
template
.
render
(
context
)
...
...
src/rest/parsers.py
View file @
c56e48f5
import
json
class
BaseParser
(
object
):
def
__init__
(
self
,
resource
,
request
):
self
.
resource
=
resource
...
...
@@ -8,11 +10,13 @@ class BaseParser(object):
class
JSONParser
(
BaseParser
):
pass
def
parse
(
self
,
input
):
return
json
.
loads
(
input
)
class
XMLParser
(
BaseParser
):
pass
class
FormParser
(
BaseParser
):
pass
def
parse
(
self
,
input
):
return
self
.
request
.
POST
src/rest/resource.py
View file @
c56e48f5
from
django.http
import
HttpResponse
from
django.core.urlresolvers
import
reverse
from
rest
import
emitters
,
parsers
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
class
Resource
(
object
):
class
HTTPException
(
Exception
):
def
__init__
(
self
,
status
,
content
,
headers
):
self
.
status
=
status
self
.
content
=
content
self
.
headers
=
headers
class
ResourceException
(
Exception
):
def
__init__
(
self
,
status
,
content
=
''
,
headers
=
{}):
self
.
status
=
status
self
.
content
=
content
self
.
headers
=
headers
class
Resource
(
object
):
allowed_methods
=
(
'GET'
,)
...
...
@@ -27,6 +32,12 @@ class Resource(object):
'application/xml'
:
parsers
.
XMLParser
,
'application/x-www-form-urlencoded'
:
parsers
.
FormParser
}
create_form
=
None
update_form
=
None
METHOD_PARAM
=
'_method'
ACCEPT_PARAM
=
'_accept'
def
__new__
(
cls
,
request
,
*
args
,
**
kwargs
):
self
=
object
.
__new__
(
cls
)
...
...
@@ -34,15 +45,40 @@ class Resource(object):
self
.
_request
=
request
return
self
.
_handle_request
(
request
,
*
args
,
**
kwargs
)
def
__init__
(
self
):
pass
def
_determine_method
(
self
,
request
):
"""Determine the HTTP method that this request should be treated as,
allowing for PUT and DELETE tunneling via the _method parameter."""
method
=
request
.
method
if
method
==
'POST'
and
request
.
POST
.
has_key
(
self
.
METHOD_PARAM
):
method
=
request
.
POST
[
self
.
METHOD_PARAM
]
.
upper
()
return
method
def
_check_method_allowed
(
self
,
method
):
if
not
method
in
self
.
allowed_methods
:
raise
ResourceException
(
STATUS_405_METHOD_NOT_ALLOWED
,
{
'detail'
:
'Method
\'
%
s
\'
not allowed on this resource.'
%
method
})
if
not
method
in
self
.
callmap
.
keys
():
raise
ResourceException
(
STATUS_501_NOT_IMPLEMENTED
,
{
'detail'
:
'Unknown or unsupported method
\'
%
s
\'
'
%
method
})
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."""
return
self
.
parsers
.
values
()[
0
]
# TODO: Raise 415 Unsupported media type
try
:
return
self
.
parsers
[
request
.
META
[
'CONTENT_TYPE'
]]
except
:
raise
ResourceException
(
STATUS_415_UNSUPPORTED_MEDIA_TYPE
,
{
'detail'
:
'Unsupported media type'
})
def
_determine_emitter
(
self
,
request
):
"""Return the appropriate emitter for the output, given the client's 'Accept' header,
...
...
@@ -90,16 +126,40 @@ class Resource(object):
(
accept_mimetype
==
mimetype
)):
return
(
mimetype
,
emitter
)
raise
self
.
HTTPException
(
406
,
{
'status'
:
'Not Acceptable'
,
'accepts'
:
','
.
join
(
item
[
0
]
for
item
in
self
.
emitters
)},
{})
raise
ResourceException
(
STATUS_406_NOT_ACCEPTABLE
,
{
'detail'
:
'Could not statisfy the client
\'
s accepted content type'
,
'accepted_types'
:
[
item
[
0
]
for
item
in
self
.
emitters
]})
def
_validate_data
(
self
,
method
,
data
):
"""If there is an appropriate form to deal with this operation,
then validate the data and return the resulting dictionary.
"""
if
method
==
'PUT'
and
self
.
update_form
:
form
=
self
.
update_form
(
data
)
elif
method
==
'POST'
and
self
.
create_form
:
form
=
self
.
create_form
(
data
)
else
:
return
data
if
not
form
.
is_valid
():
raise
ResourceException
(
STATUS_400_BAD_REQUEST
,
{
'detail'
:
dict
((
k
,
map
(
unicode
,
v
))
for
(
k
,
v
)
in
form
.
errors
.
iteritems
())})
return
form
.
cleaned_data
def
_handle_request
(
self
,
request
,
*
args
,
**
kwargs
):
method
=
request
.
method
# Hack to ensure PUT requests get the same form treatment as POST requests
utils
.
coerce_put_post
(
request
)
# Get the request method, allowing for PUT and DELETE tunneling
method
=
self
.
_determine_method
(
request
)
try
:
if
not
method
in
self
.
allowed_methods
:
raise
self
.
HTTPException
(
405
,
{
'status'
:
'Method Not Allowed'
},
{})
self
.
_check_method_allowed
(
method
)
# Parse the HTTP Request content
func
=
getattr
(
self
,
self
.
callmap
.
get
(
method
,
''
))
...
...
@@ -107,11 +167,12 @@ class Resource(object):
if
method
in
(
'PUT'
,
'POST'
):
parser
=
self
.
_determine_parser
(
request
)
data
=
parser
(
self
,
request
)
.
parse
(
request
.
raw_post_data
)
data
=
self
.
_validate_data
(
method
,
data
)
(
status
,
ret
,
headers
)
=
func
(
data
,
request
.
META
,
*
args
,
**
kwargs
)
else
:
(
status
,
ret
,
headers
)
=
func
(
request
.
META
,
*
args
,
**
kwargs
)
except
self
.
HTTP
Exception
,
exc
:
except
Resource
Exception
,
exc
:
(
status
,
ret
,
headers
)
=
(
exc
.
status
,
exc
.
content
,
exc
.
headers
)
headers
[
'Allow'
]
=
', '
.
join
(
self
.
allowed_methods
)
...
...
@@ -119,11 +180,11 @@ class Resource(object):
# Serialize the HTTP Response content
try
:
mimetype
,
emitter
=
self
.
_determine_emitter
(
request
)
except
self
.
HTTP
Exception
,
exc
:
except
Resource
Exception
,
exc
:
(
status
,
ret
,
headers
)
=
(
exc
.
status
,
exc
.
content
,
exc
.
headers
)
mimetype
,
emitter
=
self
.
emitters
[
0
]
content
=
emitter
(
self
,
status
,
headers
)
.
emit
(
ret
)
content
=
emitter
(
self
,
request
,
status
,
headers
)
.
emit
(
ret
)
# Build the HTTP Response
resp
=
HttpResponse
(
content
,
mimetype
=
mimetype
,
status
=
status
)
...
...
@@ -134,20 +195,20 @@ class Resource(object):
def
_not_implemented
(
self
,
operation
):
resource_name
=
self
.
__class__
.
__name__
r
eturn
(
500
,
{
'status'
:
'Internal Server Error'
,
'detail'
:
'
%
s
%
s operation is permitted but has not been implemented'
%
(
resource_name
,
operation
)},
{
})
r
aise
ResourceException
(
STATUS_500_INTERNAL_SERVER_ERROR
,
{
'detail'
:
'
%
s operation on this resource has not been implemented'
%
(
operation
,
)
})
def
read
(
self
,
headers
=
{},
*
args
,
**
kwargs
):
return
self
.
_not_implemented
(
'read'
)
self
.
_not_implemented
(
'read'
)
def
create
(
self
,
data
=
None
,
headers
=
{},
*
args
,
**
kwargs
):
return
self
.
_not_implemented
(
'create'
)
self
.
_not_implemented
(
'create'
)
def
update
(
self
,
data
=
None
,
headers
=
{},
*
args
,
**
kwargs
):
return
self
.
_not_implemented
(
'update'
)
self
.
_not_implemented
(
'update'
)
def
delete
(
self
,
headers
=
{},
*
args
,
**
kwargs
):
return
self
.
_not_implemented
(
'delete'
)
self
.
_not_implemented
(
'delete'
)
def
reverse
(
self
,
view
,
*
args
,
**
kwargs
):
"""Return a fully qualified URI for a view, using the current request as the base URI.
...
...
src/rest/templates/emitter.html
View file @
c56e48f5
...
...
@@ -5,6 +5,7 @@
<head>
<style>
pre
{
border
:
1px
solid
black
;
padding
:
1em
;
background
:
#ffd
}
div
.action
{
padding
:
0.5em
1em
;
margin-bottom
:
0.5em
;
background
:
#ddf
}
</style>
</head>
<body>
...
...
@@ -14,5 +15,43 @@
{% for key, val in headers.items %}
<b>
{{ key }}:
</b>
{{ val }}
{% endfor %}
{{ content|urlize_quoted_links }}{% endautoescape %}
</pre>
{% if 'GET' in allowed_methods %}
<div
class=
'action'
>
<a
href=
'{{ request.path }}'
>
Read
</a>
</div>
{% endif %}
{% if 'POST' in resource.allowed_methods %}
<div
class=
'action'
>
<form
action=
"{{ request.path }}"
method=
"POST"
>
{% csrf_token %}
{{ create_form.as_p }}
<input
type=
"submit"
value=
"Create"
/>
</form>
</div>
{% endif %}
{% if 'PUT' in resource.allowed_methods %}
<div
class=
'action'
>
<form
action=
"{{ request.path }}"
method=
"POST"
>
<input
type=
"hidden"
name=
"{{ resource.METHOD_PARAM}}"
value=
"PUT"
/>
{% csrf_token %}
{{ create_form.as_p }}
<input
type=
"submit"
value=
"Update"
/>
</form>
</div>
{% endif %}
{% if 'DELETE' in resource.allowed_methods %}
<div
class=
'action'
>
<form
action=
"{{ request.path }}"
method=
"POST"
>
{% csrf_token %}
<input
type=
"hidden"
name=
"{{ resource.METHOD_PARAM}}"
value=
"DELETE"
/>
<input
type=
"submit"
value=
"Delete"
/>
</form>
</div>
{% endif %}
</body>
</html>
\ No newline at end of file
src/rest/templatetags/__init__.pyc
View file @
c56e48f5
No preview for this file type
src/rest/templatetags/urlize_quoted_links.pyc
View file @
c56e48f5
No preview for this file type
src/testapp/urls.py
View file @
c56e48f5
...
...
@@ -3,5 +3,6 @@ from django.conf.urls.defaults import patterns
urlpatterns
=
patterns
(
'testapp.views'
,
(
r'^$'
,
'RootResource'
),
(
r'^read-only$'
,
'ReadOnlyResource'
),
(
r'^mirroring-write$'
,
'MirroringWriteResource'
),
(
r'^write-only$'
,
'MirroringWriteResource'
),
(
r'^read-write$'
,
'ReadWriteResource'
),
)
src/testapp/views.py
View file @
c56e48f5
from
rest.resource
import
Resource
from
testapp.forms
import
ExampleForm
class
RootResource
(
Resource
):
"""This is my docstring
"""
...
...
@@ -7,7 +8,8 @@ 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
(
MirroringWriteResource
),
'read-write-api'
:
self
.
reverse
(
ReadWriteResource
)},
{})
class
ReadOnlyResource
(
Resource
):
...
...
@@ -28,3 +30,9 @@ class MirroringWriteResource(Resource):
def
create
(
self
,
data
,
headers
=
{},
*
args
,
**
kwargs
):
return
(
200
,
data
,
{})
class
ReadWriteResource
(
Resource
):
allowed_methods
=
(
'GET'
,
'PUT'
,
'DELETE'
)
create_form
=
ExampleForm
update_form
=
ExampleForm
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