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
8b89d741
Commit
8b89d741
authored
Jan 26, 2011
by
tom christie tom@tomchristie.com
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Content Type tunneling
parent
eff54c00
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
94 additions
and
37 deletions
+94
-37
examples/objectstore/views.py
+7
-7
examples/urls.py
+1
-0
flywheel/emitters.py
+57
-12
flywheel/parsers.py
+4
-4
flywheel/resource.py
+25
-14
No files found.
examples/objectstore/views.py
View file @
8b89d741
...
...
@@ -13,14 +13,14 @@ OBJECT_STORE_DIR = os.path.join(settings.MEDIA_ROOT, 'objectstore')
class
ObjectStoreRoot
(
Resource
):
"""Root of the Object Store API.
Allows the client to get a complete list of all the stored objects, or to create a new stored object."""
allowed_methods
=
(
'GET'
,
'POST'
)
allowed_methods
=
anon_allowed_methods
=
(
'GET'
,
'POST'
)
def
get
(
self
,
request
):
def
get
(
self
,
request
,
auth
):
"""Return a list of all the stored object URLs."""
keys
=
sorted
(
os
.
listdir
(
OBJECT_STORE_DIR
))
return
[
self
.
reverse
(
StoredObject
,
key
=
key
)
for
key
in
keys
]
def
post
(
self
,
request
,
content
):
def
post
(
self
,
request
,
auth
,
content
):
"""Create a new stored object, with a unique key."""
key
=
str
(
uuid
.
uuid1
())
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
...
...
@@ -31,22 +31,22 @@ class ObjectStoreRoot(Resource):
class
StoredObject
(
Resource
):
"""Represents a stored object.
The object may be any picklable content."""
allowed_methods
=
(
'GET'
,
'PUT'
,
'DELETE'
)
allowed_methods
=
anon_allowed_methods
=
(
'GET'
,
'PUT'
,
'DELETE'
)
def
get
(
self
,
request
,
key
):
def
get
(
self
,
request
,
auth
,
key
):
"""Return a stored object, by unpickling the contents of a locally stored file."""
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
if
not
os
.
path
.
exists
(
pathname
):
return
Response
(
status
.
HTTP_404_NOT_FOUND
)
return
pickle
.
load
(
open
(
pathname
,
'rb'
))
def
put
(
self
,
request
,
content
,
key
):
def
put
(
self
,
request
,
auth
,
content
,
key
):
"""Update/create a stored object, by pickling the request content to a locally stored file."""
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
pickle
.
dump
(
content
,
open
(
pathname
,
'wb'
))
return
content
def
delete
(
self
,
request
,
key
):
def
delete
(
self
,
request
,
auth
,
key
):
"""Delete a stored object, by removing it's pickled file."""
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
if
not
os
.
path
.
exists
(
pathname
):
...
...
examples/urls.py
View file @
8b89d741
...
...
@@ -4,6 +4,7 @@ from django.contrib import admin
admin
.
autodiscover
()
urlpatterns
=
patterns
(
''
,
(
r'pygments-example/'
,
include
(
'pygmentsapi.urls'
)),
(
r'^blog-post-example/'
,
include
(
'blogpost.urls'
)),
(
r'^object-store-example/'
,
include
(
'objectstore.urls'
)),
(
r'^admin/doc/'
,
include
(
'django.contrib.admindocs.urls'
)),
...
...
flywheel/emitters.py
View file @
8b89d741
from
django.template
import
RequestContext
,
loader
from
django
import
forms
from
flywheel.response
import
NoContent
from
utils
import
dict2xml
import
string
try
:
import
json
except
ImportError
:
...
...
@@ -20,25 +22,31 @@ class BaseEmitter(object):
raise
Exception
(
'emit() function on a subclass of BaseEmitter must be implemented'
)
from
django
import
forms
class
JSONForm
(
forms
.
Form
):
_contenttype
=
forms
.
CharField
(
max_length
=
256
,
initial
=
'application/json'
,
label
=
'Content Type'
)
_content
=
forms
.
CharField
(
label
=
'Content'
,
widget
=
forms
.
Textarea
)
class
DocumentingTemplateEmitter
(
BaseEmitter
):
"""Emitter used to self-document the API"""
template
=
None
def
emit
(
self
,
output
=
NoContent
):
resource
=
self
.
resource
def
_get_content
(
self
,
resource
,
output
):
"""Get the content as if it had been emitted by a non-documenting emitter.
(Typically this will be the content as it would have been if the Resource had been
requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)"""
# Find the first valid emitter and emit the content. (Don't another documenting emitter.)
# Find the first valid emitter and emit the content. (Don't
use
another documenting emitter.)
emitters
=
[
emitter
for
emitter
in
resource
.
emitters
if
not
isinstance
(
emitter
,
DocumentingTemplateEmitter
)]
if
not
emitters
:
content
=
'No emitters were found'
else
:
content
=
emitters
[
0
](
resource
)
.
emit
(
output
,
verbose
=
True
)
return
'[No emitters were found]'
content
=
emitters
[
0
](
resource
)
.
emit
(
output
,
verbose
=
True
)
if
not
all
(
char
in
string
.
printable
for
char
in
content
):
return
'[
%
d bytes of binary content]'
return
content
def
_get_form_instance
(
self
,
resource
):
# Get the form instance if we have one bound to the input
form_instance
=
resource
.
form_instance
...
...
@@ -57,8 +65,45 @@ class DocumentingTemplateEmitter(BaseEmitter):
except
:
pass
# If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types
if
not
form_instance
:
form_instance
=
JSONForm
()
form_instance
=
self
.
_get_generic_content_form
(
resource
)
return
form_instance
def
_get_generic_content_form
(
self
,
resource
):
"""Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
(Which are typically application/x-www-form-urlencoded)"""
# NB. http://jacobian.org/writing/dynamic-form-generation/
class
GenericContentForm
(
forms
.
Form
):
def
__init__
(
self
,
resource
):
"""We don't know the names of the fields we want to set until the point the form is instantiated,
as they are determined by the Resource the form is being created against.
Add the fields dynamically."""
super
(
GenericContentForm
,
self
)
.
__init__
()
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
resource
.
parsed_media_types
]
initial_contenttype
=
resource
.
default_parser
.
media_type
self
.
fields
[
resource
.
CONTENTTYPE_PARAM
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
choices
=
contenttype_choices
,
initial
=
initial_contenttype
)
self
.
fields
[
resource
.
CONTENT_PARAM
]
=
forms
.
CharField
(
label
=
'Content'
,
widget
=
forms
.
Textarea
)
# If either of these reserved parameters are turned off then content tunneling is not possible
if
self
.
resource
.
CONTENTTYPE_PARAM
is
None
or
self
.
resource
.
CONTENT_PARAM
is
None
:
return
None
# Okey doke, let's do it
return
GenericContentForm
(
resource
)
def
emit
(
self
,
output
=
NoContent
):
content
=
self
.
_get_content
(
self
.
resource
,
output
)
form_instance
=
self
.
_get_form_instance
(
self
.
resource
)
template
=
loader
.
get_template
(
self
.
template
)
context
=
RequestContext
(
self
.
resource
.
request
,
{
...
...
flywheel/parsers.py
View file @
8b89d741
...
...
@@ -8,7 +8,7 @@ except ImportError:
# TODO: Make all parsers only list a single media_type, rather than a list
class
BaseParser
(
object
):
media_type
s
=
()
media_type
=
None
def
__init__
(
self
,
resource
):
self
.
resource
=
resource
...
...
@@ -18,7 +18,7 @@ class BaseParser(object):
class
JSONParser
(
BaseParser
):
media_type
s
=
(
'application/xml'
,)
media_type
=
'application/json'
def
parse
(
self
,
input
):
try
:
...
...
@@ -27,7 +27,7 @@ class JSONParser(BaseParser):
raise
ResponseException
(
status
.
HTTP_400_BAD_REQUEST
,
{
'detail'
:
'JSON parse error -
%
s'
%
str
(
exc
)})
class
XMLParser
(
BaseParser
):
media_type
s
=
(
'application/xml'
,)
media_type
=
'application/xml'
class
FormParser
(
BaseParser
):
...
...
@@ -35,7 +35,7 @@ class FormParser(BaseParser):
Return a dict containing a single value for each non-reserved parameter.
"""
media_type
s
=
(
'application/x-www-form-urlencoded'
,)
media_type
=
'application/x-www-form-urlencoded'
def
parse
(
self
,
input
):
# The FormParser doesn't parse the input as other parsers would, since Django's already done the
...
...
flywheel/resource.py
View file @
8b89d741
...
...
@@ -120,14 +120,16 @@ class Resource(object):
(This emitter is used if the client does not send and Accept: header, or sends Accept: */*)"""
return
self
.
emitters
[
0
]
# TODO:
#def parsed_media_types(self):
# """Return an list of all the media types that this resource can emit."""
# return [parser.media_type for parser in self.parsers]
@property
def
parsed_media_types
(
self
):
"""Return an list of all the media types that this resource can emit."""
return
[
parser
.
media_type
for
parser
in
self
.
parsers
]
#def default_parser(self):
# return self.parsers[0]
@property
def
default_parser
(
self
):
"""Return the resource's most prefered emitter.
(This has no behavioural effect, but is may be used by documenting emitters)"""
return
self
.
parsers
[
0
]
def
reverse
(
self
,
view
,
*
args
,
**
kwargs
):
...
...
@@ -281,19 +283,28 @@ class Resource(object):
"""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'
)
raw_content
=
request
.
raw_post_data
split
=
content_type
.
split
(
';'
,
1
)
if
len
(
split
)
>
1
:
content_type
=
split
[
0
]
content_type
=
content_type
.
strip
()
# If CONTENTTYPE_PARAM is turned on, and this is a standard POST form then allow the content type to be overridden
if
(
content_type
==
'application/x-www-form-urlencoded'
and
request
.
method
==
'POST'
and
self
.
CONTENTTYPE_PARAM
and
self
.
CONTENT_PARAM
and
request
.
POST
.
get
(
self
.
CONTENTTYPE_PARAM
,
None
)
and
request
.
POST
.
get
(
self
.
CONTENT_PARAM
,
None
)):
raw_content
=
request
.
POST
[
self
.
CONTENT_PARAM
]
content_type
=
request
.
POST
[
self
.
CONTENTTYPE_PARAM
]
# Create a list of list of (media_type, Parser) tuples
media_type_parser_tuples
=
[[(
media_type
,
parser
)
for
media_type
in
parser
.
media_types
]
for
parser
in
self
.
parsers
]
# Flatten the list and turn it into a media_type -> Parser dict
media_type_to_parser
=
dict
(
chain
.
from_iterable
(
media_type_parser_tuples
))
media_type_to_parser
=
dict
([(
parser
.
media_type
,
parser
)
for
parser
in
self
.
parsers
])
try
:
return
media_type_to_parser
[
content_type
]
return
(
media_type_to_parser
[
content_type
],
raw_content
)
except
KeyError
:
raise
ResponseException
(
status
.
HTTP_415_UNSUPPORTED_MEDIA_TYPE
,
{
'detail'
:
'Unsupported media type
\'
%
s
\'
'
%
content_type
})
...
...
@@ -397,8 +408,8 @@ class Resource(object):
# Either generate the response data, deserializing and validating any request data
# TODO: Add support for message bodys on other HTTP methods, as it is valid.
if
method
in
(
'PUT'
,
'POST'
):
parser
=
self
.
determine_parser
(
request
)
data
=
parser
(
self
)
.
parse
(
r
equest
.
raw_post_data
)
(
parser
,
raw_content
)
=
self
.
determine_parser
(
request
)
data
=
parser
(
self
)
.
parse
(
r
aw_content
)
self
.
form_instance
=
self
.
get_form
(
data
)
data
=
self
.
cleanup_request
(
data
,
self
.
form_instance
)
response
=
func
(
request
,
auth_context
,
data
,
*
args
,
**
kwargs
)
...
...
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