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')
...
@@ -13,14 +13,14 @@ OBJECT_STORE_DIR = os.path.join(settings.MEDIA_ROOT, 'objectstore')
class
ObjectStoreRoot
(
Resource
):
class
ObjectStoreRoot
(
Resource
):
"""Root of the Object Store API.
"""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."""
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."""
"""Return a list of all the stored object URLs."""
keys
=
sorted
(
os
.
listdir
(
OBJECT_STORE_DIR
))
keys
=
sorted
(
os
.
listdir
(
OBJECT_STORE_DIR
))
return
[
self
.
reverse
(
StoredObject
,
key
=
key
)
for
key
in
keys
]
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."""
"""Create a new stored object, with a unique key."""
key
=
str
(
uuid
.
uuid1
())
key
=
str
(
uuid
.
uuid1
())
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
...
@@ -31,22 +31,22 @@ class ObjectStoreRoot(Resource):
...
@@ -31,22 +31,22 @@ class ObjectStoreRoot(Resource):
class
StoredObject
(
Resource
):
class
StoredObject
(
Resource
):
"""Represents a stored object.
"""Represents a stored object.
The object may be any picklable content."""
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."""
"""Return a stored object, by unpickling the contents of a locally stored file."""
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
if
not
os
.
path
.
exists
(
pathname
):
if
not
os
.
path
.
exists
(
pathname
):
return
Response
(
status
.
HTTP_404_NOT_FOUND
)
return
Response
(
status
.
HTTP_404_NOT_FOUND
)
return
pickle
.
load
(
open
(
pathname
,
'rb'
))
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."""
"""Update/create a stored object, by pickling the request content to a locally stored file."""
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
pickle
.
dump
(
content
,
open
(
pathname
,
'wb'
))
pickle
.
dump
(
content
,
open
(
pathname
,
'wb'
))
return
content
return
content
def
delete
(
self
,
request
,
key
):
def
delete
(
self
,
request
,
auth
,
key
):
"""Delete a stored object, by removing it's pickled file."""
"""Delete a stored object, by removing it's pickled file."""
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
pathname
=
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
if
not
os
.
path
.
exists
(
pathname
):
if
not
os
.
path
.
exists
(
pathname
):
...
...
examples/urls.py
View file @
8b89d741
...
@@ -4,6 +4,7 @@ from django.contrib import admin
...
@@ -4,6 +4,7 @@ from django.contrib import admin
admin
.
autodiscover
()
admin
.
autodiscover
()
urlpatterns
=
patterns
(
''
,
urlpatterns
=
patterns
(
''
,
(
r'pygments-example/'
,
include
(
'pygmentsapi.urls'
)),
(
r'^blog-post-example/'
,
include
(
'blogpost.urls'
)),
(
r'^blog-post-example/'
,
include
(
'blogpost.urls'
)),
(
r'^object-store-example/'
,
include
(
'objectstore.urls'
)),
(
r'^object-store-example/'
,
include
(
'objectstore.urls'
)),
(
r'^admin/doc/'
,
include
(
'django.contrib.admindocs.urls'
)),
(
r'^admin/doc/'
,
include
(
'django.contrib.admindocs.urls'
)),
...
...
flywheel/emitters.py
View file @
8b89d741
from
django.template
import
RequestContext
,
loader
from
django.template
import
RequestContext
,
loader
from
django
import
forms
from
flywheel.response
import
NoContent
from
flywheel.response
import
NoContent
from
utils
import
dict2xml
from
utils
import
dict2xml
import
string
try
:
try
:
import
json
import
json
except
ImportError
:
except
ImportError
:
...
@@ -20,25 +22,31 @@ class BaseEmitter(object):
...
@@ -20,25 +22,31 @@ class BaseEmitter(object):
raise
Exception
(
'emit() function on a subclass of BaseEmitter must be implemented'
)
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
):
class
DocumentingTemplateEmitter
(
BaseEmitter
):
"""Emitter used to self-document the API"""
"""Emitter used to self-document the API"""
template
=
None
template
=
None
def
emit
(
self
,
output
=
NoContent
):
def
_get_content
(
self
,
resource
,
output
):
resource
=
self
.
resource
"""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
)]
emitters
=
[
emitter
for
emitter
in
resource
.
emitters
if
not
isinstance
(
emitter
,
DocumentingTemplateEmitter
)]
if
not
emitters
:
if
not
emitters
:
content
=
'No emitters were found'
return
'[No emitters were found]'
else
:
content
=
emitters
[
0
](
resource
)
.
emit
(
output
,
verbose
=
True
)
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
# Get the form instance if we have one bound to the input
form_instance
=
resource
.
form_instance
form_instance
=
resource
.
form_instance
...
@@ -57,8 +65,45 @@ class DocumentingTemplateEmitter(BaseEmitter):
...
@@ -57,8 +65,45 @@ class DocumentingTemplateEmitter(BaseEmitter):
except
:
except
:
pass
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
:
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
)
template
=
loader
.
get_template
(
self
.
template
)
context
=
RequestContext
(
self
.
resource
.
request
,
{
context
=
RequestContext
(
self
.
resource
.
request
,
{
...
...
flywheel/parsers.py
View file @
8b89d741
...
@@ -8,7 +8,7 @@ except ImportError:
...
@@ -8,7 +8,7 @@ except ImportError:
# TODO: Make all parsers only list a single media_type, rather than a list
# TODO: Make all parsers only list a single media_type, rather than a list
class
BaseParser
(
object
):
class
BaseParser
(
object
):
media_type
s
=
()
media_type
=
None
def
__init__
(
self
,
resource
):
def
__init__
(
self
,
resource
):
self
.
resource
=
resource
self
.
resource
=
resource
...
@@ -18,7 +18,7 @@ class BaseParser(object):
...
@@ -18,7 +18,7 @@ class BaseParser(object):
class
JSONParser
(
BaseParser
):
class
JSONParser
(
BaseParser
):
media_type
s
=
(
'application/xml'
,)
media_type
=
'application/json'
def
parse
(
self
,
input
):
def
parse
(
self
,
input
):
try
:
try
:
...
@@ -27,7 +27,7 @@ class JSONParser(BaseParser):
...
@@ -27,7 +27,7 @@ class JSONParser(BaseParser):
raise
ResponseException
(
status
.
HTTP_400_BAD_REQUEST
,
{
'detail'
:
'JSON parse error -
%
s'
%
str
(
exc
)})
raise
ResponseException
(
status
.
HTTP_400_BAD_REQUEST
,
{
'detail'
:
'JSON parse error -
%
s'
%
str
(
exc
)})
class
XMLParser
(
BaseParser
):
class
XMLParser
(
BaseParser
):
media_type
s
=
(
'application/xml'
,)
media_type
=
'application/xml'
class
FormParser
(
BaseParser
):
class
FormParser
(
BaseParser
):
...
@@ -35,7 +35,7 @@ class FormParser(BaseParser):
...
@@ -35,7 +35,7 @@ class FormParser(BaseParser):
Return a dict containing a single value for each non-reserved parameter.
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
):
def
parse
(
self
,
input
):
# The FormParser doesn't parse the input as other parsers would, since Django's already done the
# 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):
...
@@ -120,14 +120,16 @@ class Resource(object):
(This emitter is used if the client does not send and Accept: header, or sends Accept: */*)"""
(This emitter is used if the client does not send and Accept: header, or sends Accept: */*)"""
return
self
.
emitters
[
0
]
return
self
.
emitters
[
0
]
# TODO:
@property
def
parsed_media_types
(
self
):
#def parsed_media_types(self):
"""Return an list of all the media types that this resource can emit."""
# """Return an list of all the media types that this resource can emit."""
return
[
parser
.
media_type
for
parser
in
self
.
parsers
]
# return [parser.media_type for parser in self.parsers]
#def default_parser(self):
@property
# return self.parsers[0]
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
):
def
reverse
(
self
,
view
,
*
args
,
**
kwargs
):
...
@@ -281,19 +283,28 @@ class Resource(object):
...
@@ -281,19 +283,28 @@ class Resource(object):
"""Return the appropriate parser for the input, given the client's 'Content-Type' header,
"""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."""
and the content types that this Resource knows how to parse."""
content_type
=
request
.
META
.
get
(
'CONTENT_TYPE'
,
'application/x-www-form-urlencoded'
)
content_type
=
request
.
META
.
get
(
'CONTENT_TYPE'
,
'application/x-www-form-urlencoded'
)
raw_content
=
request
.
raw_post_data
split
=
content_type
.
split
(
';'
,
1
)
split
=
content_type
.
split
(
';'
,
1
)
if
len
(
split
)
>
1
:
if
len
(
split
)
>
1
:
content_type
=
split
[
0
]
content_type
=
split
[
0
]
content_type
=
content_type
.
strip
()
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
# 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
]
media_type_to_parser
=
dict
([(
parser
.
media_type
,
parser
)
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
))
try
:
try
:
return
media_type_to_parser
[
content_type
]
return
(
media_type_to_parser
[
content_type
],
raw_content
)
except
KeyError
:
except
KeyError
:
raise
ResponseException
(
status
.
HTTP_415_UNSUPPORTED_MEDIA_TYPE
,
raise
ResponseException
(
status
.
HTTP_415_UNSUPPORTED_MEDIA_TYPE
,
{
'detail'
:
'Unsupported media type
\'
%
s
\'
'
%
content_type
})
{
'detail'
:
'Unsupported media type
\'
%
s
\'
'
%
content_type
})
...
@@ -397,8 +408,8 @@ class Resource(object):
...
@@ -397,8 +408,8 @@ class Resource(object):
# Either generate the response data, deserializing and validating any request data
# 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.
# TODO: Add support for message bodys on other HTTP methods, as it is valid.
if
method
in
(
'PUT'
,
'POST'
):
if
method
in
(
'PUT'
,
'POST'
):
parser
=
self
.
determine_parser
(
request
)
(
parser
,
raw_content
)
=
self
.
determine_parser
(
request
)
data
=
parser
(
self
)
.
parse
(
r
equest
.
raw_post_data
)
data
=
parser
(
self
)
.
parse
(
r
aw_content
)
self
.
form_instance
=
self
.
get_form
(
data
)
self
.
form_instance
=
self
.
get_form
(
data
)
data
=
self
.
cleanup_request
(
data
,
self
.
form_instance
)
data
=
self
.
cleanup_request
(
data
,
self
.
form_instance
)
response
=
func
(
request
,
auth_context
,
data
,
*
args
,
**
kwargs
)
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