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
b3e29d95
Commit
b3e29d95
authored
Sep 14, 2012
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Moved content negotiation out of response. Nicer exception handling now.
parent
b7b8cd11
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
198 additions
and
273 deletions
+198
-273
djangorestframework/contentnegotiation.py
+63
-0
djangorestframework/exceptions.py
+9
-0
djangorestframework/renderers.py
+11
-17
djangorestframework/response.py
+17
-151
djangorestframework/settings.py
+7
-7
djangorestframework/tests/request.py
+20
-34
djangorestframework/tests/response.py
+3
-3
djangorestframework/utils/__init__.py
+0
-38
djangorestframework/views.py
+68
-23
No files found.
djangorestframework/contentnegotiation.py
0 → 100644
View file @
b3e29d95
from
djangorestframework
import
exceptions
from
djangorestframework.settings
import
api_settings
from
djangorestframework.utils.mediatypes
import
order_by_precedence
import
re
MSIE_USER_AGENT_REGEX
=
re
.
compile
(
r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )'
)
class
BaseContentNegotiation
(
object
):
def
determine_renderer
(
self
,
request
,
renderers
):
raise
NotImplementedError
(
'.determine_renderer() must be implemented'
)
class
DefaultContentNegotiation
(
object
):
settings
=
api_settings
def
negotiate
(
self
,
request
,
renderers
):
"""
Given a request and a list of renderers, return a two-tuple of:
(renderer, media type).
"""
accepts
=
self
.
get_accept_list
(
request
)
# Check the acceptable media types against each renderer,
# attempting more specific media types first
# NB. The inner loop here isn't as bad as it first looks :)
# Worst case is we're looping over len(accept_list) * len(self.renderers)
for
media_type_set
in
order_by_precedence
(
accepts
):
for
renderer
in
renderers
:
for
media_type
in
media_type_set
:
if
renderer
.
can_handle_media_type
(
media_type
):
return
renderer
,
media_type
raise
exceptions
.
NotAcceptable
(
available_renderers
=
renderers
)
def
get_accept_list
(
self
,
request
):
"""
Given the incoming request, return a tokenised list of
media type strings.
"""
if
self
.
settings
.
URL_ACCEPT_OVERRIDE
:
# URL style accept override. eg. "?accept=application/json"
override
=
request
.
GET
.
get
(
self
.
settings
.
URL_ACCEPT_OVERRIDE
)
if
override
:
return
[
override
]
if
(
self
.
settings
.
IGNORE_MSIE_ACCEPT_HEADER
and
'HTTP_USER_AGENT'
in
request
.
META
and
MSIE_USER_AGENT_REGEX
.
match
(
request
.
META
[
'HTTP_USER_AGENT'
])
and
request
.
META
.
get
(
'HTTP_X_REQUESTED_WITH'
,
''
)
.
lower
()
!=
'xmlhttprequest'
):
# Ignore MSIE's broken accept behavior except for AJAX requests
# and do something sensible instead
return
[
'text/html'
,
'*/*'
]
if
'HTTP_ACCEPT'
in
request
.
META
:
# Standard HTTP Accept negotiation
# Accept header specified
tokens
=
request
.
META
[
'HTTP_ACCEPT'
]
.
split
(
','
)
return
[
token
.
strip
()
for
token
in
tokens
]
# Standard HTTP Accept negotiation
# No accept header specified
return
[
'*/*'
]
djangorestframework/exceptions.py
View file @
b3e29d95
...
@@ -39,6 +39,15 @@ class MethodNotAllowed(APIException):
...
@@ -39,6 +39,15 @@ class MethodNotAllowed(APIException):
self
.
detail
=
(
detail
or
self
.
default_detail
)
%
method
self
.
detail
=
(
detail
or
self
.
default_detail
)
%
method
class
NotAcceptable
(
APIException
):
status_code
=
status
.
HTTP_406_NOT_ACCEPTABLE
default_detail
=
"Could not satisfy the request's Accept header"
def
__init__
(
self
,
detail
=
None
,
available_renderers
=
None
):
self
.
detail
=
detail
or
self
.
default_detail
self
.
available_renderers
=
available_renderers
class
UnsupportedMediaType
(
APIException
):
class
UnsupportedMediaType
(
APIException
):
status_code
=
status
.
HTTP_415_UNSUPPORTED_MEDIA_TYPE
status_code
=
status
.
HTTP_415_UNSUPPORTED_MEDIA_TYPE
default_detail
=
"Unsupported media type '
%
s' in request."
default_detail
=
"Unsupported media type '
%
s' in request."
...
...
djangorestframework/renderers.py
View file @
b3e29d95
...
@@ -48,28 +48,22 @@ class BaseRenderer(object):
...
@@ -48,28 +48,22 @@ class BaseRenderer(object):
def
__init__
(
self
,
view
=
None
):
def
__init__
(
self
,
view
=
None
):
self
.
view
=
view
self
.
view
=
view
def
can_handle_response
(
self
,
accept
):
def
can_handle_format
(
self
,
format
):
return
format
==
self
.
format
def
can_handle_media_type
(
self
,
media_type
):
"""
"""
Returns
:const:
`True` if this renderer is able to deal with the given
Returns `True` if this renderer is able to deal with the given
*accept*
media type.
media type.
The default implementation for this function is to check the
*accept*
The default implementation for this function is to check the
media type
argument against the
:attr:`media_type`
attribute set on the class to see if
argument against the
media_type
attribute set on the class to see if
they match.
they match.
This may be overridden to provide for other behavior, but typically
you'll
This may be overridden to provide for other behavior, but typically
instead want to just set the :attr:
`media_type` attribute on the class.
you'll instead want to just set the
`media_type` attribute on the class.
"""
"""
# TODO: format overriding must go out of here
return
media_type_matches
(
self
.
media_type
,
media_type
)
format
=
None
if
self
.
view
is
not
None
:
format
=
self
.
view
.
kwargs
.
get
(
self
.
_FORMAT_QUERY_PARAM
,
None
)
if
format
is
None
and
self
.
view
is
not
None
:
format
=
self
.
view
.
request
.
GET
.
get
(
self
.
_FORMAT_QUERY_PARAM
,
None
)
if
format
is
not
None
:
return
format
==
self
.
format
return
media_type_matches
(
self
.
media_type
,
accept
)
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
"""
"""
...
...
djangorestframework/response.py
View file @
b3e29d95
"""
The :mod:`response` module provides :class:`Response` and :class:`ImmediateResponse` classes.
`Response` is a subclass of `HttpResponse`, and can be similarly instantiated and returned
from any view. It is a bit smarter than Django's `HttpResponse`, for it renders automatically
its content to a serial format by using a list of :mod:`renderers`.
To determine the content type to which it must render, default behaviour is to use standard
HTTP Accept header content negotiation. But `Response` also supports overriding the content type
by specifying an ``_accept=`` parameter in the URL. Also, `Response` will ignore `Accept` headers
from Internet Explorer user agents and use a sensible browser `Accept` header instead.
"""
import
re
from
django.template.response
import
SimpleTemplateResponse
from
django.template.response
import
SimpleTemplateResponse
from
django.core.handlers.wsgi
import
STATUS_CODE_TEXT
from
django.core.handlers.wsgi
import
STATUS_CODE_TEXT
from
djangorestframework.settings
import
api_settings
from
djangorestframework.utils.mediatypes
import
order_by_precedence
from
djangorestframework
import
status
MSIE_USER_AGENT_REGEX
=
re
.
compile
(
r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )'
)
class
NotAcceptable
(
Exception
):
pass
class
Response
(
SimpleTemplateResponse
):
class
Response
(
SimpleTemplateResponse
):
"""
"""
An HttpResponse that may include content that hasn't yet been serialized.
An HttpResponse that allows it's data to be rendered into
arbitrary media types.
Kwargs:
- content(object). The raw content, not yet serialized.
This must be native Python data that renderers can handle.
(e.g.: `dict`, `str`, ...)
- renderer_classes(list/tuple). The renderers to use for rendering the response content.
"""
"""
_ACCEPT_QUERY_PARAM
=
api_settings
.
URL_ACCEPT_OVERRIDE
def
__init__
(
self
,
data
=
None
,
status
=
None
,
headers
=
None
,
_IGNORE_IE_ACCEPT_HEADER
=
True
renderer
=
None
,
media_type
=
None
):
"""
Alters the init arguments slightly.
For example, drop 'template_name', and instead use 'data'.
def
__init__
(
self
,
content
=
None
,
status
=
None
,
headers
=
None
,
view
=
None
,
Setting 'renderer' and 'media_type' will typically be defered,
request
=
None
,
renderer_classes
=
None
,
format
=
None
):
For example being set automatically by the `APIView`.
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
"""
# which we don't need
super
(
Response
,
self
)
.
__init__
(
None
,
status
=
status
)
super
(
Response
,
self
)
.
__init__
(
None
,
status
=
status
)
self
.
data
=
data
self
.
raw_content
=
content
self
.
has_content_body
=
content
is
not
None
self
.
headers
=
headers
and
headers
[:]
or
[]
self
.
headers
=
headers
and
headers
[:]
or
[]
self
.
view
=
view
self
.
renderer
=
renderer
self
.
request
=
request
self
.
media_type
=
media_type
self
.
renderer_classes
=
renderer_classes
self
.
format
=
format
def
get_renderers
(
self
):
"""
Instantiates and returns the list of renderers the response will use.
"""
if
self
.
renderer_classes
is
None
:
renderer_classes
=
api_settings
.
DEFAULT_RENDERERS
else
:
renderer_classes
=
self
.
renderer_classes
if
self
.
format
:
return
[
cls
(
self
.
view
)
for
cls
in
renderer_classes
if
cls
.
format
==
self
.
format
]
return
[
cls
(
self
.
view
)
for
cls
in
renderer_classes
]
@property
@property
def
rendered_content
(
self
):
def
rendered_content
(
self
):
"""
self
[
'Content-Type'
]
=
self
.
media_type
The final rendered content. Accessing this attribute triggers the
if
self
.
data
is
None
:
complete rendering cycle: selecting suitable renderer, setting
return
self
.
renderer
.
render
()
response's actual content type, rendering data.
return
self
.
renderer
.
render
(
self
.
data
,
self
.
media_type
)
"""
renderer
,
media_type
=
self
.
_determine_renderer
()
# Set the media type of the response
self
[
'Content-Type'
]
=
renderer
.
media_type
# Render the response content
if
self
.
has_content_body
:
return
renderer
.
render
(
self
.
raw_content
,
media_type
)
return
renderer
.
render
()
def
render
(
self
):
try
:
return
super
(
Response
,
self
)
.
render
()
except
NotAcceptable
:
response
=
self
.
_get_406_response
()
return
response
.
render
()
@property
@property
def
status_text
(
self
):
def
status_text
(
self
):
...
@@ -100,74 +37,3 @@ class Response(SimpleTemplateResponse):
...
@@ -100,74 +37,3 @@ class Response(SimpleTemplateResponse):
Provided for convenience.
Provided for convenience.
"""
"""
return
STATUS_CODE_TEXT
.
get
(
self
.
status_code
,
''
)
return
STATUS_CODE_TEXT
.
get
(
self
.
status_code
,
''
)
def
_determine_accept_list
(
self
):
"""
Returns a list of accepted media types. This list is determined from :
1. overload with `_ACCEPT_QUERY_PARAM`
2. `Accept` header of the request
If those are useless, a default value is returned instead.
"""
request
=
self
.
request
if
(
self
.
_ACCEPT_QUERY_PARAM
and
request
.
GET
.
get
(
self
.
_ACCEPT_QUERY_PARAM
,
None
)):
# Use _accept parameter override
return
[
request
.
GET
.
get
(
self
.
_ACCEPT_QUERY_PARAM
)]
elif
(
self
.
_IGNORE_IE_ACCEPT_HEADER
and
'HTTP_USER_AGENT'
in
request
.
META
and
MSIE_USER_AGENT_REGEX
.
match
(
request
.
META
[
'HTTP_USER_AGENT'
])
and
request
.
META
.
get
(
'HTTP_X_REQUESTED_WITH'
,
''
)
!=
'XMLHttpRequest'
):
# Ignore MSIE's broken accept behavior except for AJAX requests
# and do something sensible instead
return
[
'text/html'
,
'*/*'
]
elif
'HTTP_ACCEPT'
in
request
.
META
:
# Use standard HTTP Accept negotiation
return
[
token
.
strip
()
for
token
in
request
.
META
[
'HTTP_ACCEPT'
]
.
split
(
','
)]
else
:
# No accept header specified
return
[
'*/*'
]
def
_determine_renderer
(
self
):
"""
Determines the appropriate renderer for the output, given the list of
accepted media types, and the :attr:`renderer_classes` set on this class.
Returns a 2-tuple of `(renderer, media_type)`
See: RFC 2616, Section 14
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
"""
renderers
=
self
.
get_renderers
()
accepts
=
self
.
_determine_accept_list
()
# Not acceptable response - Ignore accept header.
if
self
.
status_code
==
406
:
return
(
renderers
[
0
],
renderers
[
0
]
.
media_type
)
# Check the acceptable media types against each renderer,
# attempting more specific media types first
# NB. The inner loop here isn't as bad as it first looks :)
# Worst case is we're looping over len(accept_list) * len(self.renderers)
for
media_type_set
in
order_by_precedence
(
accepts
):
for
renderer
in
renderers
:
for
media_type
in
media_type_set
:
if
renderer
.
can_handle_response
(
media_type
):
return
renderer
,
media_type
# No acceptable renderers were found
raise
NotAcceptable
def
_get_406_response
(
self
):
renderer
=
self
.
renderer_classes
[
0
]
return
Response
(
{
'detail'
:
'Could not satisfy the client
\'
s Accept header'
,
'available_types'
:
[
renderer
.
media_type
for
renderer
in
self
.
renderer_classes
]
},
status
=
status
.
HTTP_406_NOT_ACCEPTABLE
,
view
=
self
.
view
,
request
=
self
.
request
,
renderer_classes
=
[
renderer
])
djangorestframework/settings.py
View file @
b3e29d95
...
@@ -38,6 +38,7 @@ DEFAULTS = {
...
@@ -38,6 +38,7 @@ DEFAULTS = {
),
),
'DEFAULT_PERMISSIONS'
:
(),
'DEFAULT_PERMISSIONS'
:
(),
'DEFAULT_THROTTLES'
:
(),
'DEFAULT_THROTTLES'
:
(),
'DEFAULT_CONTENT_NEGOTIATION'
:
'djangorestframework.contentnegotiation.DefaultContentNegotiation'
,
'UNAUTHENTICATED_USER'
:
'django.contrib.auth.models.AnonymousUser'
,
'UNAUTHENTICATED_USER'
:
'django.contrib.auth.models.AnonymousUser'
,
'UNAUTHENTICATED_TOKEN'
:
None
,
'UNAUTHENTICATED_TOKEN'
:
None
,
...
@@ -45,7 +46,8 @@ DEFAULTS = {
...
@@ -45,7 +46,8 @@ DEFAULTS = {
'FORM_METHOD_OVERRIDE'
:
'_method'
,
'FORM_METHOD_OVERRIDE'
:
'_method'
,
'FORM_CONTENT_OVERRIDE'
:
'_content'
,
'FORM_CONTENT_OVERRIDE'
:
'_content'
,
'FORM_CONTENTTYPE_OVERRIDE'
:
'_content_type'
,
'FORM_CONTENTTYPE_OVERRIDE'
:
'_content_type'
,
'URL_ACCEPT_OVERRIDE'
:
'_accept'
,
'URL_ACCEPT_OVERRIDE'
:
'accept'
,
'IGNORE_MSIE_ACCEPT_HEADER'
:
True
,
'FORMAT_SUFFIX_KWARG'
:
'format'
'FORMAT_SUFFIX_KWARG'
:
'format'
}
}
...
@@ -58,8 +60,9 @@ IMPORT_STRINGS = (
...
@@ -58,8 +60,9 @@ IMPORT_STRINGS = (
'DEFAULT_AUTHENTICATION'
,
'DEFAULT_AUTHENTICATION'
,
'DEFAULT_PERMISSIONS'
,
'DEFAULT_PERMISSIONS'
,
'DEFAULT_THROTTLES'
,
'DEFAULT_THROTTLES'
,
'DEFAULT_CONTENT_NEGOTIATION'
,
'UNAUTHENTICATED_USER'
,
'UNAUTHENTICATED_USER'
,
'UNAUTHENTICATED_TOKEN'
'UNAUTHENTICATED_TOKEN'
,
)
)
...
@@ -68,7 +71,7 @@ def perform_import(val, setting):
...
@@ -68,7 +71,7 @@ def perform_import(val, setting):
If the given setting is a string import notation,
If the given setting is a string import notation,
then perform the necessary import or imports.
then perform the necessary import or imports.
"""
"""
if
val
is
None
or
setting
not
in
IMPORT_STRINGS
:
if
val
is
None
or
not
setting
in
IMPORT_STRINGS
:
return
val
return
val
if
isinstance
(
val
,
basestring
):
if
isinstance
(
val
,
basestring
):
...
@@ -88,10 +91,7 @@ def import_from_string(val, setting):
...
@@ -88,10 +91,7 @@ def import_from_string(val, setting):
module_path
,
class_name
=
'.'
.
join
(
parts
[:
-
1
]),
parts
[
-
1
]
module_path
,
class_name
=
'.'
.
join
(
parts
[:
-
1
]),
parts
[
-
1
]
module
=
importlib
.
import_module
(
module_path
)
module
=
importlib
.
import_module
(
module_path
)
return
getattr
(
module
,
class_name
)
return
getattr
(
module
,
class_name
)
except
Exception
,
e
:
except
:
import
traceback
tb
=
traceback
.
format_exc
()
import
pdb
;
pdb
.
set_trace
()
msg
=
"Could not import '
%
s' for API setting '
%
s'"
%
(
val
,
setting
)
msg
=
"Could not import '
%
s' for API setting '
%
s'"
%
(
val
,
setting
)
raise
ImportError
(
msg
)
raise
ImportError
(
msg
)
...
...
djangorestframework/tests/request.py
View file @
b3e29d95
...
@@ -7,7 +7,7 @@ from django.test import TestCase, Client
...
@@ -7,7 +7,7 @@ from django.test import TestCase, Client
from
djangorestframework
import
status
from
djangorestframework
import
status
from
djangorestframework.authentication
import
SessionAuthentication
from
djangorestframework.authentication
import
SessionAuthentication
from
djangorestframework.
utils
import
RequestFactory
from
djangorestframework.
compat
import
RequestFactory
from
djangorestframework.parsers
import
(
from
djangorestframework.parsers
import
(
FormParser
,
FormParser
,
MultiPartParser
,
MultiPartParser
,
...
@@ -22,33 +22,21 @@ factory = RequestFactory()
...
@@ -22,33 +22,21 @@ factory = RequestFactory()
class
TestMethodOverloading
(
TestCase
):
class
TestMethodOverloading
(
TestCase
):
def
test_
GET_
method
(
self
):
def
test_method
(
self
):
"""
"""
GET requests identified
.
Request methods should be same as underlying request
.
"""
"""
request
=
factory
.
get
(
'/'
)
request
=
Request
(
factory
.
get
(
'/'
)
)
self
.
assertEqual
(
request
.
method
,
'GET'
)
self
.
assertEqual
(
request
.
method
,
'GET'
)
request
=
Request
(
factory
.
post
(
'/'
))
def
test_POST_method
(
self
):
"""
POST requests identified.
"""
request
=
factory
.
post
(
'/'
)
self
.
assertEqual
(
request
.
method
,
'POST'
)
self
.
assertEqual
(
request
.
method
,
'POST'
)
def
test_HEAD_method
(
self
):
"""
HEAD requests identified.
"""
request
=
factory
.
head
(
'/'
)
self
.
assertEqual
(
request
.
method
,
'HEAD'
)
def
test_overloaded_method
(
self
):
def
test_overloaded_method
(
self
):
"""
"""
POST requests can be overloaded to another method by setting a
POST requests can be overloaded to another method by setting a
reserved form field
reserved form field
"""
"""
request
=
factory
.
post
(
'/'
,
{
Request
.
_METHOD_PARAM
:
'DELETE'
}
)
request
=
Request
(
factory
.
post
(
'/'
,
{
Request
.
_METHOD_PARAM
:
'DELETE'
})
)
self
.
assertEqual
(
request
.
method
,
'DELETE'
)
self
.
assertEqual
(
request
.
method
,
'DELETE'
)
...
@@ -57,14 +45,14 @@ class TestContentParsing(TestCase):
...
@@ -57,14 +45,14 @@ class TestContentParsing(TestCase):
"""
"""
Ensure request.DATA returns None for GET request with no content.
Ensure request.DATA returns None for GET request with no content.
"""
"""
request
=
factory
.
get
(
'/'
)
request
=
Request
(
factory
.
get
(
'/'
)
)
self
.
assertEqual
(
request
.
DATA
,
None
)
self
.
assertEqual
(
request
.
DATA
,
None
)
def
test_standard_behaviour_determines_no_content_HEAD
(
self
):
def
test_standard_behaviour_determines_no_content_HEAD
(
self
):
"""
"""
Ensure request.DATA returns None for HEAD request.
Ensure request.DATA returns None for HEAD request.
"""
"""
request
=
factory
.
head
(
'/'
)
request
=
Request
(
factory
.
head
(
'/'
)
)
self
.
assertEqual
(
request
.
DATA
,
None
)
self
.
assertEqual
(
request
.
DATA
,
None
)
def
test_standard_behaviour_determines_form_content_POST
(
self
):
def
test_standard_behaviour_determines_form_content_POST
(
self
):
...
@@ -72,8 +60,8 @@ class TestContentParsing(TestCase):
...
@@ -72,8 +60,8 @@ class TestContentParsing(TestCase):
Ensure request.DATA returns content for POST request with form content.
Ensure request.DATA returns content for POST request with form content.
"""
"""
data
=
{
'qwerty'
:
'uiop'
}
data
=
{
'qwerty'
:
'uiop'
}
parsers
=
(
FormParser
,
MultiPartParser
)
request
=
Request
(
factory
.
post
(
'/'
,
data
)
)
request
=
factory
.
post
(
'/'
,
data
,
parser
=
parsers
)
request
.
parser_classes
=
(
FormParser
,
MultiPartParser
)
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
def
test_standard_behaviour_determines_non_form_content_POST
(
self
):
def
test_standard_behaviour_determines_non_form_content_POST
(
self
):
...
@@ -83,9 +71,8 @@ class TestContentParsing(TestCase):
...
@@ -83,9 +71,8 @@ class TestContentParsing(TestCase):
"""
"""
content
=
'qwerty'
content
=
'qwerty'
content_type
=
'text/plain'
content_type
=
'text/plain'
parsers
=
(
PlainTextParser
,)
request
=
Request
(
factory
.
post
(
'/'
,
content
,
content_type
=
content_type
))
request
=
factory
.
post
(
'/'
,
content
,
content_type
=
content_type
,
request
.
parser_classes
=
(
PlainTextParser
,)
parsers
=
parsers
)
self
.
assertEqual
(
request
.
DATA
,
content
)
self
.
assertEqual
(
request
.
DATA
,
content
)
def
test_standard_behaviour_determines_form_content_PUT
(
self
):
def
test_standard_behaviour_determines_form_content_PUT
(
self
):
...
@@ -93,17 +80,17 @@ class TestContentParsing(TestCase):
...
@@ -93,17 +80,17 @@ class TestContentParsing(TestCase):
Ensure request.DATA returns content for PUT request with form content.
Ensure request.DATA returns content for PUT request with form content.
"""
"""
data
=
{
'qwerty'
:
'uiop'
}
data
=
{
'qwerty'
:
'uiop'
}
parsers
=
(
FormParser
,
MultiPartParser
)
from
django
import
VERSION
from
django
import
VERSION
if
VERSION
>=
(
1
,
5
):
if
VERSION
>=
(
1
,
5
):
from
django.test.client
import
MULTIPART_CONTENT
,
BOUNDARY
,
encode_multipart
from
django.test.client
import
MULTIPART_CONTENT
,
BOUNDARY
,
encode_multipart
request
=
factory
.
put
(
'/'
,
encode_multipart
(
BOUNDARY
,
data
),
parsers
=
parsers
,
request
=
Request
(
factory
.
put
(
'/'
,
encode_multipart
(
BOUNDARY
,
data
)
,
content_type
=
MULTIPART_CONTENT
)
content_type
=
MULTIPART_CONTENT
)
)
else
:
else
:
request
=
factory
.
put
(
'/'
,
data
,
parsers
=
parsers
)
request
=
Request
(
factory
.
put
(
'/'
,
data
)
)
request
.
parser_classes
=
(
FormParser
,
MultiPartParser
)
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
def
test_standard_behaviour_determines_non_form_content_PUT
(
self
):
def
test_standard_behaviour_determines_non_form_content_PUT
(
self
):
...
@@ -113,9 +100,8 @@ class TestContentParsing(TestCase):
...
@@ -113,9 +100,8 @@ class TestContentParsing(TestCase):
"""
"""
content
=
'qwerty'
content
=
'qwerty'
content_type
=
'text/plain'
content_type
=
'text/plain'
parsers
=
(
PlainTextParser
,
)
request
=
Request
(
factory
.
put
(
'/'
,
content
,
content_type
=
content_type
))
request
=
factory
.
put
(
'/'
,
content
,
content_type
=
content_type
,
request
.
parser_classes
=
(
PlainTextParser
,
)
parsers
=
parsers
)
self
.
assertEqual
(
request
.
DATA
,
content
)
self
.
assertEqual
(
request
.
DATA
,
content
)
def
test_overloaded_behaviour_allows_content_tunnelling
(
self
):
def
test_overloaded_behaviour_allows_content_tunnelling
(
self
):
...
@@ -128,8 +114,8 @@ class TestContentParsing(TestCase):
...
@@ -128,8 +114,8 @@ class TestContentParsing(TestCase):
Request
.
_CONTENT_PARAM
:
content
,
Request
.
_CONTENT_PARAM
:
content
,
Request
.
_CONTENTTYPE_PARAM
:
content_type
Request
.
_CONTENTTYPE_PARAM
:
content_type
}
}
parsers
=
(
PlainTextParser
,
)
request
=
Request
(
factory
.
post
(
'/'
,
data
)
)
request
=
factory
.
post
(
'/'
,
data
,
parsers
=
parsers
)
request
.
parser_classes
=
(
PlainTextParser
,
)
self
.
assertEqual
(
request
.
DATA
,
content
)
self
.
assertEqual
(
request
.
DATA
,
content
)
# def test_accessing_post_after_data_form(self):
# def test_accessing_post_after_data_form(self):
...
...
djangorestframework/tests/response.py
View file @
b3e29d95
...
@@ -4,10 +4,10 @@ import unittest
...
@@ -4,10 +4,10 @@ import unittest
from
django.conf.urls.defaults
import
patterns
,
url
,
include
from
django.conf.urls.defaults
import
patterns
,
url
,
include
from
django.test
import
TestCase
from
django.test
import
TestCase
from
djangorestframework.response
import
Response
,
NotAcceptable
from
djangorestframework.response
import
Response
from
djangorestframework.views
import
APIView
from
djangorestframework.views
import
APIView
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework
import
status
from
djangorestframework
import
status
,
exceptions
from
djangorestframework.renderers
import
(
from
djangorestframework.renderers
import
(
BaseRenderer
,
BaseRenderer
,
JSONRenderer
,
JSONRenderer
,
...
@@ -91,7 +91,7 @@ class TestResponseDetermineRenderer(TestCase):
...
@@ -91,7 +91,7 @@ class TestResponseDetermineRenderer(TestCase):
accept_list
=
[
'application/json'
]
accept_list
=
[
'application/json'
]
renderer_classes
=
(
MockPickleRenderer
,
)
renderer_classes
=
(
MockPickleRenderer
,
)
response
=
self
.
get_response
(
accept_list
=
accept_list
,
renderer_classes
=
renderer_classes
)
response
=
self
.
get_response
(
accept_list
=
accept_list
,
renderer_classes
=
renderer_classes
)
self
.
assertRaises
(
NotAcceptable
,
response
.
_determine_renderer
)
self
.
assertRaises
(
exceptions
.
NotAcceptable
,
response
.
_determine_renderer
)
class
TestResponseRenderContent
(
TestCase
):
class
TestResponseRenderContent
(
TestCase
):
...
...
djangorestframework/utils/__init__.py
View file @
b3e29d95
from
django.utils.encoding
import
smart_unicode
from
django.utils.encoding
import
smart_unicode
from
django.utils.xmlutils
import
SimplerXMLGenerator
from
django.utils.xmlutils
import
SimplerXMLGenerator
from
djangorestframework.compat
import
StringIO
from
djangorestframework.compat
import
StringIO
from
djangorestframework.compat
import
RequestFactory
as
DjangoRequestFactory
from
djangorestframework.request
import
Request
import
re
import
re
import
xml.etree.ElementTree
as
ET
import
xml.etree.ElementTree
as
ET
...
@@ -102,38 +99,3 @@ class XMLRenderer():
...
@@ -102,38 +99,3 @@ class XMLRenderer():
def
dict2xml
(
input
):
def
dict2xml
(
input
):
return
XMLRenderer
()
.
dict2xml
(
input
)
return
XMLRenderer
()
.
dict2xml
(
input
)
class
RequestFactory
(
DjangoRequestFactory
):
"""
Replicate RequestFactory, but return Request, not HttpRequest.
"""
def
get
(
self
,
*
args
,
**
kwargs
):
parsers
=
kwargs
.
pop
(
'parsers'
,
None
)
request
=
super
(
RequestFactory
,
self
)
.
get
(
*
args
,
**
kwargs
)
return
Request
(
request
,
parsers
)
def
post
(
self
,
*
args
,
**
kwargs
):
parsers
=
kwargs
.
pop
(
'parsers'
,
None
)
request
=
super
(
RequestFactory
,
self
)
.
post
(
*
args
,
**
kwargs
)
return
Request
(
request
,
parsers
)
def
put
(
self
,
*
args
,
**
kwargs
):
parsers
=
kwargs
.
pop
(
'parsers'
,
None
)
request
=
super
(
RequestFactory
,
self
)
.
put
(
*
args
,
**
kwargs
)
return
Request
(
request
,
parsers
)
def
delete
(
self
,
*
args
,
**
kwargs
):
parsers
=
kwargs
.
pop
(
'parsers'
,
None
)
request
=
super
(
RequestFactory
,
self
)
.
delete
(
*
args
,
**
kwargs
)
return
Request
(
request
,
parsers
)
def
head
(
self
,
*
args
,
**
kwargs
):
parsers
=
kwargs
.
pop
(
'parsers'
,
None
)
request
=
super
(
RequestFactory
,
self
)
.
head
(
*
args
,
**
kwargs
)
return
Request
(
request
,
parsers
)
def
options
(
self
,
*
args
,
**
kwargs
):
parsers
=
kwargs
.
pop
(
'parsers'
,
None
)
request
=
super
(
RequestFactory
,
self
)
.
options
(
*
args
,
**
kwargs
)
return
Request
(
request
,
parsers
)
djangorestframework/views.py
View file @
b3e29d95
...
@@ -54,11 +54,14 @@ def _camelcase_to_spaces(content):
...
@@ -54,11 +54,14 @@ def _camelcase_to_spaces(content):
class
APIView
(
_View
):
class
APIView
(
_View
):
settings
=
api_settings
renderer_classes
=
api_settings
.
DEFAULT_RENDERERS
renderer_classes
=
api_settings
.
DEFAULT_RENDERERS
parser_classes
=
api_settings
.
DEFAULT_PARSERS
parser_classes
=
api_settings
.
DEFAULT_PARSERS
authentication_classes
=
api_settings
.
DEFAULT_AUTHENTICATION
authentication_classes
=
api_settings
.
DEFAULT_AUTHENTICATION
throttle_classes
=
api_settings
.
DEFAULT_THROTTLES
throttle_classes
=
api_settings
.
DEFAULT_THROTTLES
permission_classes
=
api_settings
.
DEFAULT_PERMISSIONS
permission_classes
=
api_settings
.
DEFAULT_PERMISSIONS
content_negotiation_class
=
api_settings
.
DEFAULT_CONTENT_NEGOTIATION
@classmethod
@classmethod
def
as_view
(
cls
,
**
initkwargs
):
def
as_view
(
cls
,
**
initkwargs
):
...
@@ -169,6 +172,27 @@ class APIView(_View):
...
@@ -169,6 +172,27 @@ class APIView(_View):
"""
"""
return
self
.
renderer_classes
[
0
]
return
self
.
renderer_classes
[
0
]
def
get_format_suffix
(
self
,
**
kwargs
):
"""
Determine if the request includes a '.json' style format suffix
"""
if
self
.
settings
.
FORMAT_SUFFIX_KWARG
:
return
kwargs
.
get
(
self
.
settings
.
FORMAT_SUFFIX_KWARG
)
def
get_renderers
(
self
,
format
=
None
):
"""
Instantiates and returns the list of renderers that this view can use.
"""
return
[
renderer
(
self
)
for
renderer
in
self
.
renderer_classes
]
def
filter_renderers
(
self
,
renderers
,
format
=
None
):
"""
If format suffix such as '.json' is supplied, filter the
list of valid renderers for this request.
"""
return
[
renderer
for
renderer
in
renderers
if
renderer
.
can_handle_format
(
format
)]
def
get_permissions
(
self
):
def
get_permissions
(
self
):
"""
"""
Instantiates and returns the list of permissions that this view requires.
Instantiates and returns the list of permissions that this view requires.
...
@@ -177,10 +201,28 @@ class APIView(_View):
...
@@ -177,10 +201,28 @@ class APIView(_View):
def
get_throttles
(
self
):
def
get_throttles
(
self
):
"""
"""
Instantiates and returns the list of thottles that this view
requir
es.
Instantiates and returns the list of thottles that this view
us
es.
"""
"""
return
[
throttle
(
self
)
for
throttle
in
self
.
throttle_classes
]
return
[
throttle
(
self
)
for
throttle
in
self
.
throttle_classes
]
def
content_negotiation
(
self
,
request
):
"""
Determine which renderer and media type to use render the response.
"""
renderers
=
self
.
get_renderers
()
if
self
.
format
:
# If there is a '.json' style format suffix, only use
# renderers that accept that format.
fallback
=
renderers
[
0
]
renderers
=
self
.
filter_renderers
(
renderers
,
self
.
format
)
if
not
renderers
:
self
.
format404
=
True
return
(
fallback
,
fallback
.
media_type
)
conneg
=
self
.
content_negotiation_class
()
return
conneg
.
negotiate
(
request
,
renderers
)
def
check_permissions
(
self
,
request
,
obj
=
None
):
def
check_permissions
(
self
,
request
,
obj
=
None
):
"""
"""
Check if request should be permitted.
Check if request should be permitted.
...
@@ -204,35 +246,43 @@ class APIView(_View):
...
@@ -204,35 +246,43 @@ class APIView(_View):
return
Request
(
request
,
parser_classes
=
self
.
parser_classes
,
return
Request
(
request
,
parser_classes
=
self
.
parser_classes
,
authentication_classes
=
self
.
authentication_classes
)
authentication_classes
=
self
.
authentication_classes
)
def
initial
(
self
,
request
,
*
args
,
**
kwargs
):
"""
Runs anything that needs to occur prior to calling the method handlers.
"""
self
.
format
=
self
.
get_format_suffix
(
**
kwargs
)
self
.
renderer
,
self
.
media_type
=
self
.
content_negotiation
(
request
)
self
.
check_permissions
(
request
)
self
.
check_throttles
(
request
)
# If the request included a non-existant .format URL suffix,
# raise 404, but only after first making permission checks.
if
getattr
(
self
,
'format404'
,
None
):
raise
Http404
()
def
finalize_response
(
self
,
request
,
response
,
*
args
,
**
kwargs
):
def
finalize_response
(
self
,
request
,
response
,
*
args
,
**
kwargs
):
"""
"""
Returns the final response object.
Returns the final response object.
"""
"""
if
isinstance
(
response
,
Response
):
if
isinstance
(
response
,
Response
):
response
.
view
=
self
response
.
renderer
=
self
.
renderer
response
.
request
=
request
response
.
media_type
=
self
.
media_type
response
.
renderer_classes
=
self
.
renderer_classes
if
api_settings
.
FORMAT_SUFFIX_KWARG
:
response
.
format
=
kwargs
.
get
(
api_settings
.
FORMAT_SUFFIX_KWARG
,
None
)
for
key
,
value
in
self
.
headers
.
items
():
for
key
,
value
in
self
.
headers
.
items
():
response
[
key
]
=
value
response
[
key
]
=
value
return
response
return
response
def
initial
(
self
,
request
,
*
args
,
**
kwargs
):
"""
Runs anything that needs to occur prior to calling the method handlers.
"""
self
.
check_permissions
(
request
)
self
.
check_throttles
(
request
)
def
handle_exception
(
self
,
exc
):
def
handle_exception
(
self
,
exc
):
"""
"""
Handle any exception that occurs, by returning an appropriate response,
Handle any exception that occurs, by returning an appropriate response,
or re-raising the error.
or re-raising the error.
"""
"""
if
isinstance
(
exc
,
exceptions
.
Throttled
):
if
isinstance
(
exc
,
exceptions
.
NotAcceptable
):
# Fall back to default renderer
self
.
renderer
=
exc
.
available_renderers
[
0
]
self
.
media_type
=
exc
.
available_renderers
[
0
]
.
media_type
elif
isinstance
(
exc
,
exceptions
.
Throttled
):
# Throttle wait header
self
.
headers
[
'X-Throttle-Wait-Seconds'
]
=
'
%
d'
%
exc
.
wait
self
.
headers
[
'X-Throttle-Wait-Seconds'
]
=
'
%
d'
%
exc
.
wait
if
isinstance
(
exc
,
exceptions
.
APIException
):
if
isinstance
(
exc
,
exceptions
.
APIException
):
...
@@ -250,14 +300,8 @@ class APIView(_View):
...
@@ -250,14 +300,8 @@ class APIView(_View):
@csrf_exempt
@csrf_exempt
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
"""
"""
`APIView.dispatch()` is pretty much the same as Django's regular
`.dispatch()` is pretty much the same as Django's regular dispatch,
`View.dispatch()`, except that it includes hooks to:
but with extra hooks for startup, finalize, and exception handling.
* Initialize the request object.
* Finalize the response object.
* Handle exceptions that occur in the handler method.
* An initial hook for code such as permission checking that should
occur prior to running the method handlers.
"""
"""
request
=
self
.
initialize_request
(
request
,
*
args
,
**
kwargs
)
request
=
self
.
initialize_request
(
request
,
*
args
,
**
kwargs
)
self
.
request
=
request
self
.
request
=
request
...
@@ -270,7 +314,8 @@ class APIView(_View):
...
@@ -270,7 +314,8 @@ class APIView(_View):
# Get the appropriate handler method
# Get the appropriate handler method
if
request
.
method
.
lower
()
in
self
.
http_method_names
:
if
request
.
method
.
lower
()
in
self
.
http_method_names
:
handler
=
getattr
(
self
,
request
.
method
.
lower
(),
self
.
http_method_not_allowed
)
handler
=
getattr
(
self
,
request
.
method
.
lower
(),
self
.
http_method_not_allowed
)
else
:
else
:
handler
=
self
.
http_method_not_allowed
handler
=
self
.
http_method_not_allowed
...
...
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