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
b5b231a8
Commit
b5b231a8
authored
May 12, 2011
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
yet more API cleanup
parent
15f9e7c5
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
119 additions
and
124 deletions
+119
-124
djangorestframework/mixins.py
+57
-67
djangorestframework/renderers.py
+42
-37
djangorestframework/templates/renderer.html
+9
-9
djangorestframework/tests/accept.py
+2
-2
djangorestframework/tests/files.py
+1
-1
djangorestframework/tests/validators.py
+1
-1
djangorestframework/views.py
+7
-7
No files found.
djangorestframework/mixins.py
View file @
b5b231a8
...
...
@@ -48,23 +48,18 @@ class RequestMixin(object):
parsers
=
()
def
_get_method
(
self
):
@property
def
method
(
self
):
"""
Returns the HTTP method
for the current view
.
Returns the HTTP method.
"""
if
not
hasattr
(
self
,
'_method'
):
self
.
_method
=
self
.
request
.
method
return
self
.
_method
def
_set_method
(
self
,
method
):
"""
Set the method for the current view.
"""
self
.
_method
=
method
def
_get_content_type
(
self
):
@property
def
content_type
(
self
):
"""
Returns the content type header.
"""
...
...
@@ -73,11 +68,32 @@ class RequestMixin(object):
return
self
.
_content_type
def
_set_content_type
(
self
,
content_type
):
@property
def
DATA
(
self
):
"""
Set the content type header
.
Returns the request data
.
"""
self
.
_content_type
=
content_type
if
not
hasattr
(
self
,
'_data'
):
self
.
_load_data_and_files
()
return
self
.
_data
@property
def
FILES
(
self
):
"""
Returns the request files.
"""
if
not
hasattr
(
self
,
'_files'
):
self
.
_load_data_and_files
()
return
self
.
_files
def
_load_data_and_files
(
self
):
"""
Parse the request content into self.DATA and self.FILES.
"""
stream
=
self
.
_get_stream
()
(
self
.
_data
,
self
.
_files
)
=
self
.
_parse
(
stream
,
self
.
content_type
)
def
_get_stream
(
self
):
...
...
@@ -134,27 +150,6 @@ class RequestMixin(object):
return
self
.
_stream
def
_set_stream
(
self
,
stream
):
"""
Set the stream representing the request body.
"""
self
.
_stream
=
stream
def
_load_data_and_files
(
self
):
(
self
.
_data
,
self
.
_files
)
=
self
.
_parse
(
self
.
stream
,
self
.
content_type
)
def
_get_data
(
self
):
if
not
hasattr
(
self
,
'_data'
):
self
.
_load_data_and_files
()
return
self
.
_data
def
_get_files
(
self
):
if
not
hasattr
(
self
,
'_files'
):
self
.
_load_data_and_files
()
return
self
.
_files
# TODO: Modify this so that it happens implictly, rather than being called explicitly
# ie accessing any of .DATA, .FILES, .content_type, .method will force
# form overloading.
...
...
@@ -164,7 +159,10 @@ class RequestMixin(object):
If it is then alter self.method, self.content_type, self.CONTENT to reflect that rather than simply
delegating them to the original request.
"""
if
not
self
.
_USE_FORM_OVERLOADING
or
self
.
method
!=
'POST'
or
not
is_form_media_type
(
self
.
content_type
):
# We only need to use form overloading on form POST requests
content_type
=
self
.
request
.
META
.
get
(
'HTTP_CONTENT_TYPE'
,
self
.
request
.
META
.
get
(
'CONTENT_TYPE'
,
''
))
if
not
self
.
_USE_FORM_OVERLOADING
or
self
.
request
.
method
!=
'POST'
or
not
not
is_form_media_type
(
content_type
):
return
# Temporarily switch to using the form parsers, then parse the content
...
...
@@ -175,7 +173,7 @@ class RequestMixin(object):
# Method overloading - change the method and remove the param from the content
if
self
.
_METHOD_PARAM
in
content
:
self
.
method
=
content
[
self
.
_METHOD_PARAM
]
.
upper
()
self
.
_
method
=
content
[
self
.
_METHOD_PARAM
]
.
upper
()
del
self
.
_data
[
self
.
_METHOD_PARAM
]
# Content overloading - rewind the stream and modify the content type
...
...
@@ -207,28 +205,21 @@ class RequestMixin(object):
@property
def
parsed_media_types
(
self
):
def
_
parsed_media_types
(
self
):
"""
Return a
n
list of all the media types that this view can parse.
Return a list of all the media types that this view can parse.
"""
return
[
parser
.
media_type
for
parser
in
self
.
parsers
]
@property
def
default_parser
(
self
):
def
_
default_parser
(
self
):
"""
Return the view's most preferred parser.
(This has no behavioral effect, but is may be used by documenting renderers)
Return the view's default parser.
"""
return
self
.
parsers
[
0
]
method
=
property
(
_get_method
,
_set_method
)
content_type
=
property
(
_get_content_type
,
_set_content_type
)
stream
=
property
(
_get_stream
,
_set_stream
)
DATA
=
property
(
_get_data
)
FILES
=
property
(
_get_files
)
########## ResponseMixin ##########
...
...
@@ -240,8 +231,9 @@ class ResponseMixin(object):
Also supports overriding the content type by specifying an _accept= parameter in the URL.
Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead.
"""
ACCEPT_QUERY_PARAM
=
'_accept'
# Allow override of Accept header in URL query params
REWRITE_IE_ACCEPT_HEADER
=
True
_ACCEPT_QUERY_PARAM
=
'_accept'
# Allow override of Accept header in URL query params
_IGNORE_IE_ACCEPT_HEADER
=
True
renderers
=
()
...
...
@@ -256,7 +248,7 @@ class ResponseMixin(object):
try
:
renderer
=
self
.
_determine_renderer
(
self
.
request
)
except
ErrorResponse
,
exc
:
renderer
=
self
.
default_renderer
renderer
=
self
.
_
default_renderer
response
=
exc
.
response
# Serialize the response content
...
...
@@ -287,10 +279,10 @@ class ResponseMixin(object):
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
"""
if
self
.
ACCEPT_QUERY_PARAM
and
request
.
GET
.
get
(
self
.
ACCEPT_QUERY_PARAM
,
None
):
if
self
.
_ACCEPT_QUERY_PARAM
and
request
.
GET
.
get
(
self
.
_
ACCEPT_QUERY_PARAM
,
None
):
# Use _accept parameter override
accept_list
=
[
request
.
GET
.
get
(
self
.
ACCEPT_QUERY_PARAM
)]
elif
(
self
.
REWRIT
E_IE_ACCEPT_HEADER
and
accept_list
=
[
request
.
GET
.
get
(
self
.
_
ACCEPT_QUERY_PARAM
)]
elif
(
self
.
_IGNOR
E_IE_ACCEPT_HEADER
and
request
.
META
.
has_key
(
'HTTP_USER_AGENT'
)
and
MSIE_USER_AGENT_REGEX
.
match
(
request
.
META
[
'HTTP_USER_AGENT'
])):
accept_list
=
[
'text/html'
,
'*/*'
]
...
...
@@ -299,7 +291,7 @@ class ResponseMixin(object):
accept_list
=
request
.
META
[
"HTTP_ACCEPT"
]
.
split
(
','
)
else
:
# No accept header specified
return
self
.
default_renderer
return
self
.
_
default_renderer
# Parse the accept header into a dict of {qvalue: set of media types}
# We ignore mietype parameters
...
...
@@ -340,25 +332,24 @@ class ResponseMixin(object):
# Return default
if
'*/*'
in
accept_set
:
return
self
.
default_renderer
return
self
.
_
default_renderer
raise
ErrorResponse
(
status
.
HTTP_406_NOT_ACCEPTABLE
,
{
'detail'
:
'Could not satisfy the client
\'
s Accept header'
,
'available_types'
:
self
.
rendered_media_types
})
'available_types'
:
self
.
_
rendered_media_types
})
@property
def
rendered_media_types
(
self
):
def
_
rendered_media_types
(
self
):
"""
Return an list of all the media types that this
resource
can render.
Return an list of all the media types that this
view
can render.
"""
return
[
renderer
.
media_type
for
renderer
in
self
.
renderers
]
@property
def
default_renderer
(
self
):
def
_
default_renderer
(
self
):
"""
Return the resource's most preferred renderer.
(This renderer is used if the client does not send and Accept: header, or sends Accept: */*)
Return the view's default renderer.
"""
return
self
.
renderers
[
0
]
...
...
@@ -367,8 +358,7 @@ class ResponseMixin(object):
class
AuthMixin
(
object
):
"""
Simple mixin class to provide authentication and permission checking,
by adding a set of authentication and permission classes on a ``View``.
Simple mixin class to add authentication and permission checking to a ``View`` class.
"""
authentication
=
()
permissions
=
()
...
...
@@ -404,16 +394,16 @@ class AuthMixin(object):
########## Resource Mixin ##########
class
ResourceMixin
(
object
):
class
ResourceMixin
(
object
):
@property
def
CONTENT
(
self
):
if
not
hasattr
(
self
,
'_content'
):
self
.
_content
=
self
.
_get_content
(
self
.
DATA
,
self
.
FILES
)
self
.
_content
=
self
.
_get_content
()
return
self
.
_content
def
_get_content
(
self
,
data
,
files
):
def
_get_content
(
self
):
resource
=
self
.
resource
(
self
)
return
resource
.
validate
(
data
,
files
)
return
resource
.
validate
(
self
.
DATA
,
self
.
FILES
)
def
get_bound_form
(
self
,
content
=
None
):
resource
=
self
.
resource
(
self
)
...
...
djangorestframework/renderers.py
View file @
b5b231a8
...
...
@@ -52,7 +52,7 @@ class BaseRenderer(object):
should render the output.
EG: 'application/json; indent=4'
By default render simply returns the ouput as-is.
By default render simply returns the ou
t
put as-is.
Override this method to provide for other behavior.
"""
if
obj
is
None
:
...
...
@@ -61,6 +61,41 @@ class BaseRenderer(object):
return
str
(
obj
)
class
JSONRenderer
(
BaseRenderer
):
"""
Renderer which serializes to JSON
"""
media_type
=
'application/json'
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
if
obj
is
None
:
return
''
# If the media type looks like 'application/json; indent=4', then
# pretty print the result.
indent
=
get_media_type_params
(
media_type
)
.
get
(
'indent'
,
None
)
sort_keys
=
False
try
:
indent
=
max
(
min
(
int
(
indent
),
8
),
0
)
sort_keys
=
True
except
(
ValueError
,
TypeError
):
indent
=
None
return
json
.
dumps
(
obj
,
indent
=
indent
,
sort_keys
=
sort_keys
)
class
XMLRenderer
(
BaseRenderer
):
"""
Renderer which serializes to XML.
"""
media_type
=
'application/xml'
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
if
obj
is
None
:
return
''
return
dict2xml
(
obj
)
class
TemplateRenderer
(
BaseRenderer
):
"""
A Base class provided for convenience.
...
...
@@ -161,8 +196,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
Add the fields dynamically."""
super
(
GenericContentForm
,
self
)
.
__init__
()
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
view
.
parsed_media_types
]
initial_contenttype
=
view
.
default_parser
.
media_type
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
view
.
_
parsed_media_types
]
initial_contenttype
=
view
.
_
default_parser
.
media_type
self
.
fields
[
view
.
_CONTENTTYPE_PARAM
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
choices
=
contenttype_choices
,
...
...
@@ -204,16 +239,19 @@ class DocumentingTemplateRenderer(BaseRenderer):
template
=
loader
.
get_template
(
self
.
template
)
context
=
RequestContext
(
self
.
view
.
request
,
{
'content'
:
content
,
'
resource'
:
self
.
view
,
# TODO: rename to view
'
view'
:
self
.
view
,
'request'
:
self
.
view
.
request
,
# TODO: remove
'response'
:
self
.
view
.
response
,
'description'
:
description
,
'name'
:
name
,
'markeddown'
:
markeddown
,
'breadcrumblist'
:
breadcrumb_list
,
'available_media_types'
:
self
.
view
.
_rendered_media_types
,
'form'
:
form_instance
,
'login_url'
:
login_url
,
'logout_url'
:
logout_url
,
'ACCEPT_PARAM'
:
self
.
view
.
_ACCEPT_QUERY_PARAM
,
'METHOD_PARAM'
:
self
.
view
.
_METHOD_PARAM
,
'ADMIN_MEDIA_PREFIX'
:
settings
.
ADMIN_MEDIA_PREFIX
})
...
...
@@ -228,39 +266,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
return
ret
class
JSONRenderer
(
BaseRenderer
):
"""
Renderer which serializes to JSON
"""
media_type
=
'application/json'
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
if
obj
is
None
:
return
''
indent
=
get_media_type_params
(
media_type
)
.
get
(
'indent'
,
None
)
if
indent
is
not
None
:
try
:
indent
=
int
(
indent
)
except
ValueError
:
indent
=
None
sort_keys
=
indent
and
True
or
False
return
json
.
dumps
(
obj
,
indent
=
indent
,
sort_keys
=
sort_keys
)
class
XMLRenderer
(
BaseRenderer
):
"""
Renderer which serializes to XML.
"""
media_type
=
'application/xml'
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
if
obj
is
None
:
return
''
return
dict2xml
(
obj
)
class
DocumentingHTMLRenderer
(
DocumentingTemplateRenderer
):
"""
Renderer which provides a browsable HTML interface for an API.
...
...
djangorestframework/templates/renderer.html
View file @
b5b231a8
...
...
@@ -42,14 +42,14 @@
{% endfor %}
{{ content|urlize_quoted_links }}
</pre>
{% endautoescape %}
</div>
{% if 'GET' in
resource
.allowed_methods %}
{% if 'GET' in
view
.allowed_methods %}
<form>
<fieldset
class=
'module aligned'
>
<h2>
GET {{ name }}
</h2>
<div
class=
'submit-row'
style=
'margin: 0; border: 0'
>
<a
href=
'{{ request.path }}'
rel=
"nofollow"
style=
'float: left'
>
GET
</a>
{% for media_type in
resource.rendered
_media_types %}
{% with
resource.ACCEPT_QUERY
_PARAM|add:"="|add:media_type as param %}
{% for media_type in
available
_media_types %}
{% with
ACCEPT
_PARAM|add:"="|add:media_type as param %}
[
<a
href=
'{{ request.path|add_query_param:param }}'
rel=
"nofollow"
>
{{ media_type }}
</a>
]
{% endwith %}
{% endfor %}
...
...
@@ -63,8 +63,8 @@
*** (We could display only the POST form if method tunneling is disabled, but I think ***
*** the user experience would be confusing, so we simply turn all forms off. *** {% endcomment %}
{% if
resource.
METHOD_PARAM and form %}
{% if 'POST' in
resource
.allowed_methods %}
{% if METHOD_PARAM and form %}
{% if 'POST' in
view
.allowed_methods %}
<form
action=
"{{ request.path }}"
method=
"post"
{%
if
form
.
is_multipart
%}
enctype=
"multipart/form-data"
{%
endif
%}
>
<fieldset
class=
'module aligned'
>
<h2>
POST {{ name }}
</h2>
...
...
@@ -85,11 +85,11 @@
</form>
{% endif %}
{% if 'PUT' in
resource
.allowed_methods %}
{% if 'PUT' in
view
.allowed_methods %}
<form
action=
"{{ request.path }}"
method=
"post"
{%
if
form
.
is_multipart
%}
enctype=
"multipart/form-data"
{%
endif
%}
>
<fieldset
class=
'module aligned'
>
<h2>
PUT {{ name }}
</h2>
<input
type=
"hidden"
name=
"{{
resource.
METHOD_PARAM }}"
value=
"PUT"
/>
<input
type=
"hidden"
name=
"{{ METHOD_PARAM }}"
value=
"PUT"
/>
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
...
...
@@ -107,12 +107,12 @@
</form>
{% endif %}
{% if 'DELETE' in
resource
.allowed_methods %}
{% if 'DELETE' in
view
.allowed_methods %}
<form
action=
"{{ request.path }}"
method=
"post"
>
<fieldset
class=
'module aligned'
>
<h2>
DELETE {{ name }}
</h2>
{% csrf_token %}
<input
type=
"hidden"
name=
"{{
resource.
METHOD_PARAM }}"
value=
"DELETE"
/>
<input
type=
"hidden"
name=
"{{ METHOD_PARAM }}"
value=
"DELETE"
/>
<div
class=
'submit-row'
style=
'margin: 0; border: 0'
>
<input
type=
"submit"
value=
"DELETE"
class=
"default"
/>
</div>
...
...
djangorestframework/tests/accept.py
View file @
b5b231a8
...
...
@@ -40,9 +40,9 @@ class UserAgentMungingTest(TestCase):
self
.
assertEqual
(
resp
[
'Content-Type'
],
'text/html'
)
def
test_dont_rewrite_msie_accept_header
(
self
):
"""Turn off
REWRIT
E_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure
"""Turn off
_IGNOR
E_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure
that we get a JSON response if we set a */* accept header."""
view
=
self
.
MockView
.
as_view
(
REWRIT
E_IE_ACCEPT_HEADER
=
False
)
view
=
self
.
MockView
.
as_view
(
_IGNOR
E_IE_ACCEPT_HEADER
=
False
)
for
user_agent
in
(
MSIE_9_USER_AGENT
,
MSIE_8_USER_AGENT
,
...
...
djangorestframework/tests/files.py
View file @
b5b231a8
...
...
@@ -2,7 +2,7 @@ from django.test import TestCase
from
django
import
forms
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.views
import
BaseView
from
djangorestframework.resource
import
FormResource
from
djangorestframework.resource
s
import
FormResource
import
StringIO
class
UploadFilesTests
(
TestCase
):
...
...
djangorestframework/tests/validators.py
View file @
b5b231a8
...
...
@@ -5,7 +5,7 @@ from djangorestframework.compat import RequestFactory
from
djangorestframework.validators
import
BaseValidator
,
FormValidator
,
ModelFormValidator
from
djangorestframework.response
import
ErrorResponse
from
djangorestframework.views
import
BaseView
from
djangorestframework.resource
import
Resource
from
djangorestframework.resource
s
import
Resource
class
TestValidatorMixinInterfaces
(
TestCase
):
...
...
djangorestframework/views.py
View file @
b5b231a8
...
...
@@ -4,7 +4,7 @@ from django.views.decorators.csrf import csrf_exempt
from
djangorestframework.compat
import
View
from
djangorestframework.response
import
Response
,
ErrorResponse
from
djangorestframework.mixins
import
*
from
djangorestframework
import
resource
,
renderers
,
parsers
,
authentication
,
permissions
,
validator
s
,
status
from
djangorestframework
import
resource
s
,
renderers
,
parsers
,
authentication
,
permission
s
,
status
__all__
=
(
...
...
@@ -22,7 +22,7 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
Performs request deserialization, response serialization, authentication and input validation."""
# Use the base resource by default
resource
=
resource
.
Resource
resource
=
resource
s
.
Resource
# List of renderers the resource can serialize the response with, ordered by preference.
renderers
=
(
renderers
.
JSONRenderer
,
...
...
@@ -36,9 +36,6 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
parsers
.
FormParser
,
parsers
.
MultiPartParser
)
# List of validators to validate, cleanup and normalize the request content
validators
=
(
validators
.
FormValidator
,
)
# List of all authenticating methods to attempt.
authentication
=
(
authentication
.
UserLoggedInAuthenticaton
,
authentication
.
BasicAuthenticaton
)
...
...
@@ -54,6 +51,9 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
@property
def
allowed_methods
(
self
):
"""
Return the list of allowed HTTP methods, uppercased.
"""
return
[
method
.
upper
()
for
method
in
self
.
http_method_names
if
hasattr
(
self
,
method
)]
def
http_method_not_allowed
(
self
,
request
,
*
args
,
**
kwargs
):
...
...
@@ -61,7 +61,7 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
Return an HTTP 405 error if an operation is called which does not have a handler method.
"""
raise
ErrorResponse
(
status
.
HTTP_405_METHOD_NOT_ALLOWED
,
{
'detail'
:
'Method
\'
%
s
\'
not allowed on this resource.'
%
self
.
method
})
{
'detail'
:
'Method
\'
%
s
\'
not allowed on this resource.'
%
self
.
method
})
# Note: session based authentication is explicitly CSRF validated,
...
...
@@ -127,7 +127,7 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
class
ModelView
(
BaseView
):
"""A RESTful view that maps to a model in the database."""
validators
=
(
validators
.
ModelFormValidator
,)
resource
=
resources
.
ModelResource
class
InstanceModelView
(
ReadModelMixin
,
UpdateModelMixin
,
DeleteModelMixin
,
ModelView
):
"""A view which provides default operations for read/update/delete against a model instance."""
...
...
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