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
1cde31c8
Commit
1cde31c8
authored
Feb 25, 2012
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Massive merge
parent
5fd4c639
Show whitespace changes
Inline
Side-by-side
Showing
45 changed files
with
786 additions
and
782 deletions
+786
-782
AUTHORS
+1
-0
djangorestframework/__init__.py
+1
-1
djangorestframework/authentication.py
+0
-2
djangorestframework/compat.py
+6
-10
djangorestframework/mixins.py
+25
-82
djangorestframework/parsers.py
+21
-24
djangorestframework/renderers.py
+15
-28
djangorestframework/request.py
+83
-74
djangorestframework/resources.py
+10
-54
djangorestframework/response.py
+52
-56
djangorestframework/reverse.py
+8
-11
djangorestframework/runtests/settings.py
+0
-5
djangorestframework/templates/djangorestframework/base.html
+9
-2
djangorestframework/templates/djangorestframework/login.html
+1
-1
djangorestframework/tests/__init__.py
+0
-1
djangorestframework/tests/accept.py
+13
-2
djangorestframework/tests/modelviews.py
+4
-1
djangorestframework/tests/oauthentication.py
+1
-1
djangorestframework/tests/parsers.py
+4
-2
djangorestframework/tests/renderers.py
+167
-23
djangorestframework/tests/request.py
+130
-119
djangorestframework/tests/response.py
+18
-25
djangorestframework/tests/reverse.py
+2
-1
djangorestframework/tests/views.py
+26
-28
djangorestframework/urls.py
+7
-4
djangorestframework/utils/__init__.py
+38
-66
djangorestframework/utils/staticviews.py
+0
-61
djangorestframework/views.py
+33
-33
docs/howto/setup.rst
+8
-8
docs/index.rst
+6
-0
examples/blogpost/models.py
+2
-1
examples/blogpost/resources.py
+11
-2
examples/mixin/urls.py
+3
-3
examples/modelresourceexample/models.py
+1
-2
examples/modelresourceexample/resources.py
+7
-0
examples/modelresourceexample/urls.py
+5
-2
examples/objectstore/views.py
+36
-16
examples/pygments_api/forms.py
+1
-2
examples/pygments_api/tests.py
+0
-3
examples/pygments_api/views.py
+14
-9
examples/requestexample/views.py
+1
-2
examples/resourceexample/forms.py
+1
-0
examples/resourceexample/views.py
+4
-2
examples/sandbox/views.py
+9
-9
examples/urls.py
+2
-4
No files found.
AUTHORS
View file @
1cde31c8
...
@@ -33,6 +33,7 @@ Camille Harang <mammique>
...
@@ -33,6 +33,7 @@ Camille Harang <mammique>
Paul Oswald <poswald>
Paul Oswald <poswald>
Sean C. Farley <scfarley>
Sean C. Farley <scfarley>
Daniel Izquierdo <izquierdo>
Daniel Izquierdo <izquierdo>
Can Yavuz <tschan>
THANKS TO:
THANKS TO:
...
...
djangorestframework/__init__.py
View file @
1cde31c8
__version__
=
'0.
3.3
'
__version__
=
'0.
4.0-dev
'
VERSION
=
__version__
# synonym
VERSION
=
__version__
# synonym
djangorestframework/authentication.py
View file @
1cde31c8
...
@@ -87,8 +87,6 @@ class UserLoggedInAuthentication(BaseAuthentication):
...
@@ -87,8 +87,6 @@ class UserLoggedInAuthentication(BaseAuthentication):
Returns a :obj:`User` if the request session currently has a logged in user.
Returns a :obj:`User` if the request session currently has a logged in user.
Otherwise returns :const:`None`.
Otherwise returns :const:`None`.
"""
"""
request
.
DATA
# Make sure our generic parsing runs first
if
getattr
(
request
,
'user'
,
None
)
and
request
.
user
.
is_active
:
if
getattr
(
request
,
'user'
,
None
)
and
request
.
user
.
is_active
:
# Enforce CSRF validation for session based authentication.
# Enforce CSRF validation for session based authentication.
resp
=
CsrfViewMiddleware
()
.
process_view
(
request
,
None
,
(),
{})
resp
=
CsrfViewMiddleware
()
.
process_view
(
request
,
None
,
(),
{})
...
...
djangorestframework/compat.py
View file @
1cde31c8
...
@@ -214,18 +214,15 @@ else:
...
@@ -214,18 +214,15 @@ else:
REASON_NO_CSRF_COOKIE
=
"CSRF cookie not set."
REASON_NO_CSRF_COOKIE
=
"CSRF cookie not set."
REASON_BAD_TOKEN
=
"CSRF token missing or incorrect."
REASON_BAD_TOKEN
=
"CSRF token missing or incorrect."
def
_get_failure_view
():
def
_get_failure_view
():
"""
"""
Returns the view to be used for CSRF rejections
Returns the view to be used for CSRF rejections
"""
"""
return
get_callable
(
settings
.
CSRF_FAILURE_VIEW
)
return
get_callable
(
settings
.
CSRF_FAILURE_VIEW
)
def
_get_new_csrf_key
():
def
_get_new_csrf_key
():
return
hashlib
.
md5
(
"
%
s
%
s"
%
(
randrange
(
0
,
_MAX_CSRF_KEY
),
settings
.
SECRET_KEY
))
.
hexdigest
()
return
hashlib
.
md5
(
"
%
s
%
s"
%
(
randrange
(
0
,
_MAX_CSRF_KEY
),
settings
.
SECRET_KEY
))
.
hexdigest
()
def
get_token
(
request
):
def
get_token
(
request
):
"""
"""
Returns the the CSRF token required for a POST form. The token is an
Returns the the CSRF token required for a POST form. The token is an
...
@@ -239,7 +236,6 @@ else:
...
@@ -239,7 +236,6 @@ else:
request
.
META
[
"CSRF_COOKIE_USED"
]
=
True
request
.
META
[
"CSRF_COOKIE_USED"
]
=
True
return
request
.
META
.
get
(
"CSRF_COOKIE"
,
None
)
return
request
.
META
.
get
(
"CSRF_COOKIE"
,
None
)
def
_sanitize_token
(
token
):
def
_sanitize_token
(
token
):
# Allow only alphanum, and ensure we return a 'str' for the sake of the post
# Allow only alphanum, and ensure we return a 'str' for the sake of the post
# processing middleware.
# processing middleware.
...
@@ -432,6 +428,7 @@ try:
...
@@ -432,6 +428,7 @@ try:
except
ImportError
:
except
ImportError
:
yaml
=
None
yaml
=
None
import
unittest
import
unittest
try
:
try
:
import
unittest.skip
import
unittest.skip
...
@@ -458,10 +455,9 @@ except ImportError: # python < 2.7
...
@@ -458,10 +455,9 @@ except ImportError: # python < 2.7
unittest
.
skip
=
skip
unittest
.
skip
=
skip
# reverse_lazy (Django 1.4 onwards)
# xml.etree.parse only throws ParseError for python >= 2.7
try
:
try
:
from
django.core.urlresolvers
import
reverse_lazy
from
xml.etree
import
ParseError
as
ETParseError
except
:
except
ImportError
:
# python < 2.7
from
django.core.urlresolvers
import
reverse
ETParseError
=
None
from
django.utils.functional
import
lazy
reverse_lazy
=
lazy
(
reverse
,
str
)
djangorestframework/mixins.py
View file @
1cde31c8
...
@@ -21,14 +21,13 @@ __all__ = (
...
@@ -21,14 +21,13 @@ __all__ = (
'ResponseMixin'
,
'ResponseMixin'
,
'AuthMixin'
,
'AuthMixin'
,
'ResourceMixin'
,
'ResourceMixin'
,
# Reverse URL lookup behavior
'InstanceMixin'
,
# Model behavior mixins
# Model behavior mixins
'ReadModelMixin'
,
'ReadModelMixin'
,
'CreateModelMixin'
,
'CreateModelMixin'
,
'UpdateModelMixin'
,
'UpdateModelMixin'
,
'DeleteModelMixin'
,
'DeleteModelMixin'
,
'ListModelMixin'
'ListModelMixin'
,
'PaginatorMixin'
)
)
...
@@ -39,38 +38,32 @@ class RequestMixin(object):
...
@@ -39,38 +38,32 @@ class RequestMixin(object):
`Mixin` class enabling the use of :class:`request.Request` in your views.
`Mixin` class enabling the use of :class:`request.Request` in your views.
"""
"""
parser_classes
=
()
"""
The set of parsers that the view can handle.
Should be a tuple/list of classes as described in the :mod:`parsers` module.
"""
request_class
=
Request
request_class
=
Request
"""
"""
The class to use as a wrapper for the original request object.
The class to use as a wrapper for the original request object.
"""
"""
def
get_parsers
(
self
):
"""
Instantiates and returns the list of parsers the request will use.
"""
return
[
p
(
self
)
for
p
in
self
.
parser_classes
]
def
create_request
(
self
,
request
):
def
create_request
(
self
,
request
):
"""
"""
Creates and returns an instance of :class:`request.Request`.
Creates and returns an instance of :class:`request.Request`.
This new instance wraps the `request` passed as a parameter, and use
the
This new instance wraps the `request` passed as a parameter, and use
parsers set on the view.
the
parsers set on the view.
"""
"""
parsers
=
self
.
get_parsers
()
return
self
.
request_class
(
request
,
parsers
=
self
.
parsers
)
return
self
.
request_class
(
request
,
parsers
=
parsers
)
@property
@property
def
_parsed_media_types
(
self
):
def
_parsed_media_types
(
self
):
"""
"""
Returns a 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
):
"""
Return the view's default parser class.
"""
"""
return
[
p
.
media_type
for
p
in
self
.
parser_classes
]
return
self
.
parsers
[
0
]
########## ResponseMixin ##########
########## ResponseMixin ##########
...
@@ -80,58 +73,32 @@ class ResponseMixin(object):
...
@@ -80,58 +73,32 @@ class ResponseMixin(object):
`Mixin` class enabling the use of :class:`response.Response` in your views.
`Mixin` class enabling the use of :class:`response.Response` in your views.
"""
"""
renderer
_classe
s
=
()
renderers
=
()
"""
"""
The set of response renderers that the view can handle.
The set of response renderers that the view can handle.
Should be a tuple/list of classes as described in the :mod:`renderers` module.
Should be a tuple/list of classes as described in the :mod:`renderers` module.
"""
"""
def
get_renderers
(
self
):
"""
Instantiates and returns the list of renderers the response will use.
"""
return
[
r
(
self
)
for
r
in
self
.
renderer_classes
]
def
prepare_response
(
self
,
response
):
"""
Prepares and returns `response`.
This has no effect if the response is not an instance of :class:`response.Response`.
"""
if
hasattr
(
response
,
'request'
)
and
response
.
request
is
None
:
response
.
request
=
self
.
request
# set all the cached headers
for
name
,
value
in
self
.
headers
.
items
():
response
[
name
]
=
value
# set the views renderers on the response
response
.
renderers
=
self
.
get_renderers
()
return
response
@property
@property
def
header
s
(
self
):
def
_rendered_media_type
s
(
self
):
"""
"""
Dictionary of headers to set on the response.
Return an list of all the media types that this response can render.
This is useful when the response doesn't exist yet, but you
want to memorize some headers to set on it when it will exist.
"""
"""
if
not
hasattr
(
self
,
'_headers'
):
return
[
renderer
.
media_type
for
renderer
in
self
.
renderers
]
self
.
_headers
=
{}
return
self
.
_headers
@property
@property
def
_rendered_
media_type
s
(
self
):
def
_rendered_
format
s
(
self
):
"""
"""
Return a
n list of all the media types that this view
can render.
Return a
list of all the formats that this response
can render.
"""
"""
return
[
renderer
.
media_type
for
renderer
in
self
.
get_renderers
()
]
return
[
renderer
.
format
for
renderer
in
self
.
renderers
]
@property
@property
def
_
rendered_formats
(
self
):
def
_
default_renderer
(
self
):
"""
"""
Return
a list of all the formats that this view can render
.
Return
the response's default renderer class
.
"""
"""
return
[
renderer
.
format
for
renderer
in
self
.
get_renderers
()
]
return
self
.
renderers
[
0
]
########## Auth Mixin ##########
########## Auth Mixin ##########
...
@@ -254,30 +221,6 @@ class ResourceMixin(object):
...
@@ -254,30 +221,6 @@ class ResourceMixin(object):
else
:
else
:
return
None
return
None
##########
class
InstanceMixin
(
object
):
"""
`Mixin` class that is used to identify a `View` class as being the canonical identifier
for the resources it is mapped to.
"""
@classmethod
def
as_view
(
cls
,
**
initkwargs
):
"""
Store the callable object on the resource class that has been associated with this view.
"""
view
=
super
(
InstanceMixin
,
cls
)
.
as_view
(
**
initkwargs
)
resource
=
getattr
(
cls
(
**
initkwargs
),
'resource'
,
None
)
if
resource
:
# We do a little dance when we store the view callable...
# we need to store it wrapped in a 1-tuple, so that inspect will treat it
# as a function when we later look it up (rather than turning it into a method).
# This makes sure our URL reversing works ok.
resource
.
view_callable
=
(
view
,)
return
view
########## Model Mixins ##########
########## Model Mixins ##########
...
@@ -411,7 +354,7 @@ class CreateModelMixin(ModelMixin):
...
@@ -411,7 +354,7 @@ class CreateModelMixin(ModelMixin):
response
=
Response
(
instance
,
status
=
status
.
HTTP_201_CREATED
)
response
=
Response
(
instance
,
status
=
status
.
HTTP_201_CREATED
)
# Set headers
# Set headers
if
hasattr
(
instance
,
'get_absolute_
url'
):
if
hasattr
(
self
.
resource
,
'
url'
):
response
[
'Location'
]
=
self
.
resource
(
self
)
.
url
(
instance
)
response
[
'Location'
]
=
self
.
resource
(
self
)
.
url
(
instance
)
return
response
return
response
...
...
djangorestframework/parsers.py
View file @
1cde31c8
...
@@ -20,6 +20,8 @@ from djangorestframework.compat import yaml
...
@@ -20,6 +20,8 @@ from djangorestframework.compat import yaml
from
djangorestframework.response
import
ImmediateResponse
from
djangorestframework.response
import
ImmediateResponse
from
djangorestframework.utils.mediatypes
import
media_type_matches
from
djangorestframework.utils.mediatypes
import
media_type_matches
from
xml.etree
import
ElementTree
as
ET
from
xml.etree
import
ElementTree
as
ET
from
djangorestframework.compat
import
ETParseError
from
xml.parsers.expat
import
ExpatError
import
datetime
import
datetime
import
decimal
import
decimal
...
@@ -43,13 +45,6 @@ class BaseParser(object):
...
@@ -43,13 +45,6 @@ class BaseParser(object):
media_type
=
None
media_type
=
None
def
__init__
(
self
,
view
=
None
):
"""
Initialize the parser with the ``View`` instance as state,
in case the parser needs to access any metadata on the :obj:`View` object.
"""
self
.
view
=
view
def
can_handle_request
(
self
,
content_type
):
def
can_handle_request
(
self
,
content_type
):
"""
"""
Returns :const:`True` if this parser is able to deal with the given *content_type*.
Returns :const:`True` if this parser is able to deal with the given *content_type*.
...
@@ -63,12 +58,12 @@ class BaseParser(object):
...
@@ -63,12 +58,12 @@ class BaseParser(object):
"""
"""
return
media_type_matches
(
self
.
media_type
,
content_type
)
return
media_type_matches
(
self
.
media_type
,
content_type
)
def
parse
(
self
,
stream
):
def
parse
(
self
,
stream
,
meta
,
upload_handlers
):
"""
"""
Given a *stream* to read from, return the deserialized output.
Given a *stream* to read from, return the deserialized output.
Should return a 2-tuple of (data, files).
Should return a 2-tuple of (data, files).
"""
"""
raise
NotImplementedError
(
"
BaseParser
.parse() Must be overridden to be implemented."
)
raise
NotImplementedError
(
".parse() Must be overridden to be implemented."
)
class
JSONParser
(
BaseParser
):
class
JSONParser
(
BaseParser
):
...
@@ -78,7 +73,7 @@ class JSONParser(BaseParser):
...
@@ -78,7 +73,7 @@ class JSONParser(BaseParser):
media_type
=
'application/json'
media_type
=
'application/json'
def
parse
(
self
,
stream
):
def
parse
(
self
,
stream
,
meta
,
upload_handlers
):
"""
"""
Returns a 2-tuple of `(data, files)`.
Returns a 2-tuple of `(data, files)`.
...
@@ -93,15 +88,14 @@ class JSONParser(BaseParser):
...
@@ -93,15 +88,14 @@ class JSONParser(BaseParser):
status
=
status
.
HTTP_400_BAD_REQUEST
)
status
=
status
.
HTTP_400_BAD_REQUEST
)
if
yaml
:
class
YAMLParser
(
BaseParser
):
class
YAMLParser
(
BaseParser
):
"""
"""
Parses YAML-serialized data.
Parses YAML-serialized data.
"""
"""
media_type
=
'application/yaml'
media_type
=
'application/yaml'
def
parse
(
self
,
stream
):
def
parse
(
self
,
stream
,
meta
,
upload_handlers
):
"""
"""
Returns a 2-tuple of `(data, files)`.
Returns a 2-tuple of `(data, files)`.
...
@@ -114,8 +108,6 @@ if yaml:
...
@@ -114,8 +108,6 @@ if yaml:
raise
ImmediateResponse
(
raise
ImmediateResponse
(
{
'detail'
:
'YAML parse error -
%
s'
%
unicode
(
exc
)},
{
'detail'
:
'YAML parse error -
%
s'
%
unicode
(
exc
)},
status
=
status
.
HTTP_400_BAD_REQUEST
)
status
=
status
.
HTTP_400_BAD_REQUEST
)
else
:
YAMLParser
=
None
class
PlainTextParser
(
BaseParser
):
class
PlainTextParser
(
BaseParser
):
...
@@ -125,7 +117,7 @@ class PlainTextParser(BaseParser):
...
@@ -125,7 +117,7 @@ class PlainTextParser(BaseParser):
media_type
=
'text/plain'
media_type
=
'text/plain'
def
parse
(
self
,
stream
):
def
parse
(
self
,
stream
,
meta
,
upload_handlers
):
"""
"""
Returns a 2-tuple of `(data, files)`.
Returns a 2-tuple of `(data, files)`.
...
@@ -142,7 +134,7 @@ class FormParser(BaseParser):
...
@@ -142,7 +134,7 @@ class FormParser(BaseParser):
media_type
=
'application/x-www-form-urlencoded'
media_type
=
'application/x-www-form-urlencoded'
def
parse
(
self
,
stream
):
def
parse
(
self
,
stream
,
meta
,
upload_handlers
):
"""
"""
Returns a 2-tuple of `(data, files)`.
Returns a 2-tuple of `(data, files)`.
...
@@ -160,21 +152,20 @@ class MultiPartParser(BaseParser):
...
@@ -160,21 +152,20 @@ class MultiPartParser(BaseParser):
media_type
=
'multipart/form-data'
media_type
=
'multipart/form-data'
def
parse
(
self
,
stream
):
def
parse
(
self
,
stream
,
meta
,
upload_handlers
):
"""
"""
Returns a 2-tuple of `(data, files)`.
Returns a 2-tuple of `(data, files)`.
`data` will be a :class:`QueryDict` containing all the form parameters.
`data` will be a :class:`QueryDict` containing all the form parameters.
`files` will be a :class:`QueryDict` containing all the form files.
`files` will be a :class:`QueryDict` containing all the form files.
"""
"""
upload_handlers
=
self
.
view
.
request
.
_get_upload_handlers
()
try
:
try
:
django_parser
=
DjangoMultiPartParser
(
self
.
view
.
request
.
META
,
stream
,
upload_handlers
)
parser
=
DjangoMultiPartParser
(
meta
,
stream
,
upload_handlers
)
return
parser
.
parse
()
except
MultiPartParserError
,
exc
:
except
MultiPartParserError
,
exc
:
raise
ImmediateResponse
(
raise
ImmediateResponse
(
{
'detail'
:
'multipart parse error -
%
s'
%
unicode
(
exc
)},
{
'detail'
:
'multipart parse error -
%
s'
%
unicode
(
exc
)},
status
=
status
.
HTTP_400_BAD_REQUEST
)
status
=
status
.
HTTP_400_BAD_REQUEST
)
return
django_parser
.
parse
()
class
XMLParser
(
BaseParser
):
class
XMLParser
(
BaseParser
):
...
@@ -184,14 +175,18 @@ class XMLParser(BaseParser):
...
@@ -184,14 +175,18 @@ class XMLParser(BaseParser):
media_type
=
'application/xml'
media_type
=
'application/xml'
def
parse
(
self
,
stream
):
def
parse
(
self
,
stream
,
meta
,
upload_handlers
):
"""
"""
Returns a 2-tuple of `(data, files)`.
Returns a 2-tuple of `(data, files)`.
`data` will simply be a string representing the body of the request.
`data` will simply be a string representing the body of the request.
`files` will always be `None`.
`files` will always be `None`.
"""
"""
try
:
tree
=
ET
.
parse
(
stream
)
tree
=
ET
.
parse
(
stream
)
except
(
ExpatError
,
ETParseError
,
ValueError
),
exc
:
content
=
{
'detail'
:
'XML parse error -
%
s'
%
unicode
(
exc
)}
raise
ImmediateResponse
(
content
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
data
=
self
.
_xml_convert
(
tree
.
getroot
())
data
=
self
.
_xml_convert
(
tree
.
getroot
())
return
(
data
,
None
)
return
(
data
,
None
)
...
@@ -251,5 +246,7 @@ DEFAULT_PARSERS = (
...
@@ -251,5 +246,7 @@ DEFAULT_PARSERS = (
XMLParser
XMLParser
)
)
if
YAMLParser
:
if
yaml
:
DEFAULT_PARSERS
+=
(
YAMLParser
,)
DEFAULT_PARSERS
+=
(
YAMLParser
,
)
else
:
YAMLParser
=
None
djangorestframework/renderers.py
View file @
1cde31c8
...
@@ -6,20 +6,18 @@ by serializing the output along with documentation regarding the View, output st
...
@@ -6,20 +6,18 @@ by serializing the output along with documentation regarding the View, output st
and providing forms and links depending on the allowed methods, renderers and parsers on the View.
and providing forms and links depending on the allowed methods, renderers and parsers on the View.
"""
"""
from
django
import
forms
from
django
import
forms
from
django.conf
import
settings
from
django.core.serializers.json
import
DateTimeAwareJSONEncoder
from
django.core.serializers.json
import
DateTimeAwareJSONEncoder
from
django.template
import
RequestContext
,
loader
from
django.template
import
RequestContext
,
loader
from
django.utils
import
simplejson
as
json
from
django.utils
import
simplejson
as
json
from
djangorestframework.compat
import
yaml
from
djangorestframework.compat
import
yaml
from
djangorestframework.utils
import
dict2xml
,
url_resolves
,
allowed_methods
from
djangorestframework.utils
import
dict2xml
from
djangorestframework.utils.breadcrumbs
import
get_breadcrumbs
from
djangorestframework.utils.breadcrumbs
import
get_breadcrumbs
from
djangorestframework.utils.mediatypes
import
get_media_type_params
,
add_media_type_param
,
media_type_matches
from
djangorestframework.utils.mediatypes
import
get_media_type_params
,
add_media_type_param
,
media_type_matches
from
djangorestframework
import
VERSION
from
djangorestframework
import
VERSION
import
string
import
string
from
urllib
import
quote_plus
__all__
=
(
__all__
=
(
'BaseRenderer'
,
'BaseRenderer'
,
...
@@ -156,8 +154,7 @@ class XMLRenderer(BaseRenderer):
...
@@ -156,8 +154,7 @@ class XMLRenderer(BaseRenderer):
return
dict2xml
(
obj
)
return
dict2xml
(
obj
)
if
yaml
:
class
YAMLRenderer
(
BaseRenderer
):
class
YAMLRenderer
(
BaseRenderer
):
"""
"""
Renderer which serializes to YAML.
Renderer which serializes to YAML.
"""
"""
...
@@ -173,8 +170,6 @@ if yaml:
...
@@ -173,8 +170,6 @@ if yaml:
return
''
return
''
return
yaml
.
safe_dump
(
obj
)
return
yaml
.
safe_dump
(
obj
)
else
:
YAMLRenderer
=
None
class
TemplateRenderer
(
BaseRenderer
):
class
TemplateRenderer
(
BaseRenderer
):
...
@@ -218,7 +213,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
...
@@ -218,7 +213,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
"""
"""
# Find the first valid renderer and render the content. (Don't use another documenting renderer.)
# Find the first valid renderer and render the content. (Don't use another documenting renderer.)
renderers
=
[
renderer
for
renderer
in
view
.
renderer
_classes
renderers
=
[
renderer
for
renderer
in
view
.
renderer
s
if
not
issubclass
(
renderer
,
DocumentingTemplateRenderer
)]
if
not
issubclass
(
renderer
,
DocumentingTemplateRenderer
)]
if
not
renderers
:
if
not
renderers
:
return
'[No renderers were found]'
return
'[No renderers were found]'
...
@@ -278,14 +273,14 @@ class DocumentingTemplateRenderer(BaseRenderer):
...
@@ -278,14 +273,14 @@ class DocumentingTemplateRenderer(BaseRenderer):
# NB. http://jacobian.org/writing/dynamic-form-generation/
# NB. http://jacobian.org/writing/dynamic-form-generation/
class
GenericContentForm
(
forms
.
Form
):
class
GenericContentForm
(
forms
.
Form
):
def
__init__
(
self
,
request
):
def
__init__
(
self
,
view
,
request
):
"""We don't know the names of the fields we want to set until the point the form is instantiated,
"""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.
as they are determined by the Resource the form is being created against.
Add the fields dynamically."""
Add the fields dynamically."""
super
(
GenericContentForm
,
self
)
.
__init__
()
super
(
GenericContentForm
,
self
)
.
__init__
()
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
request
.
_parsed_media_types
]
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
view
.
_parsed_media_types
]
initial_contenttype
=
request
.
_default_parser
.
media_type
initial_contenttype
=
view
.
_default_parser
.
media_type
self
.
fields
[
request
.
_CONTENTTYPE_PARAM
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
self
.
fields
[
request
.
_CONTENTTYPE_PARAM
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
choices
=
contenttype_choices
,
choices
=
contenttype_choices
,
...
@@ -298,7 +293,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
...
@@ -298,7 +293,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
return
None
return
None
# Okey doke, let's do it
# Okey doke, let's do it
return
GenericContentForm
(
view
.
request
)
return
GenericContentForm
(
view
,
view
.
request
)
def
get_name
(
self
):
def
get_name
(
self
):
try
:
try
:
...
@@ -327,13 +322,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
...
@@ -327,13 +322,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
put_form_instance
=
self
.
_get_form_instance
(
self
.
view
,
'put'
)
put_form_instance
=
self
.
_get_form_instance
(
self
.
view
,
'put'
)
post_form_instance
=
self
.
_get_form_instance
(
self
.
view
,
'post'
)
post_form_instance
=
self
.
_get_form_instance
(
self
.
view
,
'post'
)
if
url_resolves
(
settings
.
LOGIN_URL
)
and
url_resolves
(
settings
.
LOGOUT_URL
):
login_url
=
"
%
s?next=
%
s"
%
(
settings
.
LOGIN_URL
,
quote_plus
(
self
.
view
.
request
.
path
))
logout_url
=
"
%
s?next=
%
s"
%
(
settings
.
LOGOUT_URL
,
quote_plus
(
self
.
view
.
request
.
path
))
else
:
login_url
=
None
logout_url
=
None
name
=
self
.
get_name
()
name
=
self
.
get_name
()
description
=
self
.
get_description
()
description
=
self
.
get_description
()
...
@@ -343,21 +331,18 @@ class DocumentingTemplateRenderer(BaseRenderer):
...
@@ -343,21 +331,18 @@ class DocumentingTemplateRenderer(BaseRenderer):
context
=
RequestContext
(
self
.
view
.
request
,
{
context
=
RequestContext
(
self
.
view
.
request
,
{
'content'
:
content
,
'content'
:
content
,
'view'
:
self
.
view
,
'view'
:
self
.
view
,
'request'
:
self
.
view
.
request
,
# TODO: remove
'request'
:
self
.
view
.
request
,
'response'
:
self
.
view
.
response
,
'response'
:
self
.
view
.
response
,
'description'
:
description
,
'description'
:
description
,
'name'
:
name
,
'name'
:
name
,
'version'
:
VERSION
,
'version'
:
VERSION
,
'breadcrumblist'
:
breadcrumb_list
,
'breadcrumblist'
:
breadcrumb_list
,
'allowed_methods'
:
allowed_methods
(
self
.
view
)
,
'allowed_methods'
:
self
.
view
.
allowed_methods
,
'available_formats'
:
self
.
view
.
_rendered_formats
,
'available_formats'
:
self
.
view
.
_rendered_formats
,
'put_form'
:
put_form_instance
,
'put_form'
:
put_form_instance
,
'post_form'
:
post_form_instance
,
'post_form'
:
post_form_instance
,
'login_url'
:
login_url
,
'logout_url'
:
logout_url
,
'FORMAT_PARAM'
:
self
.
_FORMAT_QUERY_PARAM
,
'FORMAT_PARAM'
:
self
.
_FORMAT_QUERY_PARAM
,
'METHOD_PARAM'
:
getattr
(
self
.
view
.
request
,
'_METHOD_PARAM'
,
None
),
'METHOD_PARAM'
:
getattr
(
self
.
view
,
'_METHOD_PARAM'
,
None
),
'ADMIN_MEDIA_PREFIX'
:
getattr
(
settings
,
'ADMIN_MEDIA_PREFIX'
,
None
),
})
})
ret
=
template
.
render
(
context
)
ret
=
template
.
render
(
context
)
...
@@ -415,5 +400,7 @@ DEFAULT_RENDERERS = (
...
@@ -415,5 +400,7 @@ DEFAULT_RENDERERS = (
XMLRenderer
XMLRenderer
)
)
if
YAMLRenderer
:
if
yaml
:
DEFAULT_RENDERERS
+=
(
YAMLRenderer
,)
DEFAULT_RENDERERS
+=
(
YAMLRenderer
,
)
else
:
YAMLRenderer
=
None
djangorestframework/request.py
View file @
1cde31c8
...
@@ -4,15 +4,14 @@ object received in all the views.
...
@@ -4,15 +4,14 @@ object received in all the views.
The wrapped request then offers a richer API, in particular :
The wrapped request then offers a richer API, in particular :
- content automatically parsed according to `Content-Type` header, and available as :meth:`.DATA<Request.DATA>`
- content automatically parsed according to `Content-Type` header,
and available as :meth:`.DATA<Request.DATA>`
- full support of PUT method, including support for file uploads
- full support of PUT method, including support for file uploads
- form overloading of HTTP method, content type and content
- form overloading of HTTP method, content type and content
"""
"""
from
djangorestframework.response
import
ImmediateResponse
from
djangorestframework
import
status
from
djangorestframework
import
status
from
djangorestframework.utils.mediatypes
import
is_form_media_type
from
djangorestframework.utils.mediatypes
import
is_form_media_type
from
djangorestframework.utils
import
as_tuple
from
StringIO
import
StringIO
from
StringIO
import
StringIO
...
@@ -20,6 +19,14 @@ from StringIO import StringIO
...
@@ -20,6 +19,14 @@ from StringIO import StringIO
__all__
=
(
'Request'
,)
__all__
=
(
'Request'
,)
class
Empty
:
pass
def
_hasattr
(
obj
,
name
):
return
not
getattr
(
obj
,
name
)
is
Empty
class
Request
(
object
):
class
Request
(
object
):
"""
"""
Wrapper allowing to enhance a standard `HttpRequest` instance.
Wrapper allowing to enhance a standard `HttpRequest` instance.
...
@@ -35,19 +42,29 @@ class Request(object):
...
@@ -35,19 +42,29 @@ class Request(object):
_CONTENT_PARAM
=
'_content'
_CONTENT_PARAM
=
'_content'
def
__init__
(
self
,
request
=
None
,
parsers
=
None
):
def
__init__
(
self
,
request
=
None
,
parsers
=
None
):
self
.
request
=
request
self
.
_request
=
request
if
parsers
is
not
None
:
self
.
parsers
=
parsers
or
()
self
.
parsers
=
parsers
self
.
_data
=
Empty
self
.
_files
=
Empty
self
.
_method
=
Empty
self
.
_content_type
=
Empty
self
.
_stream
=
Empty
def
get_parsers
(
self
):
"""
Instantiates and returns the list of parsers the request will use.
"""
return
[
parser
()
for
parser
in
self
.
parsers
]
@property
@property
def
method
(
self
):
def
method
(
self
):
"""
"""
Returns the HTTP method.
Returns the HTTP method.
This allows the `method` to be overridden by using a hidden `form`
field
This allows the `method` to be overridden by using a hidden `form`
on a form POST request.
field
on a form POST request.
"""
"""
if
not
hasattr
(
self
,
'_method'
):
if
not
_
hasattr
(
self
,
'_method'
):
self
.
_load_method_and_content_type
()
self
.
_load_method_and_content_type
()
return
self
.
_method
return
self
.
_method
...
@@ -60,11 +77,20 @@ class Request(object):
...
@@ -60,11 +77,20 @@ class Request(object):
as it allows the content type to be overridden by using a hidden form
as it allows the content type to be overridden by using a hidden form
field on a form POST request.
field on a form POST request.
"""
"""
if
not
hasattr
(
self
,
'_content_type'
):
if
not
_
hasattr
(
self
,
'_content_type'
):
self
.
_load_method_and_content_type
()
self
.
_load_method_and_content_type
()
return
self
.
_content_type
return
self
.
_content_type
@property
@property
def
stream
(
self
):
"""
Returns an object that may be used to stream the request content.
"""
if
not
_hasattr
(
self
,
'_stream'
):
self
.
_load_stream
()
return
self
.
_stream
@property
def
DATA
(
self
):
def
DATA
(
self
):
"""
"""
Parses the request body and returns the data.
Parses the request body and returns the data.
...
@@ -72,7 +98,7 @@ class Request(object):
...
@@ -72,7 +98,7 @@ class Request(object):
Similar to ``request.POST``, except that it handles arbitrary parsers,
Similar to ``request.POST``, except that it handles arbitrary parsers,
and also works on methods other than POST (eg PUT).
and also works on methods other than POST (eg PUT).
"""
"""
if
not
hasattr
(
self
,
'_data'
):
if
not
_
hasattr
(
self
,
'_data'
):
self
.
_load_data_and_files
()
self
.
_load_data_and_files
()
return
self
.
_data
return
self
.
_data
...
@@ -83,7 +109,7 @@ class Request(object):
...
@@ -83,7 +109,7 @@ class Request(object):
Similar to ``request.FILES``, except that it handles arbitrary parsers,
Similar to ``request.FILES``, except that it handles arbitrary parsers,
and also works on methods other than POST (eg PUT).
and also works on methods other than POST (eg PUT).
"""
"""
if
not
hasattr
(
self
,
'_files'
):
if
not
_
hasattr
(
self
,
'_files'
):
self
.
_load_data_and_files
()
self
.
_load_data_and_files
()
return
self
.
_files
return
self
.
_files
...
@@ -91,11 +117,11 @@ class Request(object):
...
@@ -91,11 +117,11 @@ class Request(object):
"""
"""
Parses the request content into self.DATA and self.FILES.
Parses the request content into self.DATA and self.FILES.
"""
"""
if
not
hasattr
(
self
,
'_content_type'
):
if
not
_
hasattr
(
self
,
'_content_type'
):
self
.
_load_method_and_content_type
()
self
.
_load_method_and_content_type
()
if
not
hasattr
(
self
,
'_data'
):
if
not
_
hasattr
(
self
,
'_data'
):
(
self
.
_data
,
self
.
_files
)
=
self
.
_parse
(
self
.
_get_stream
(),
self
.
_content_type
)
(
self
.
_data
,
self
.
_files
)
=
self
.
_parse
()
def
_load_method_and_content_type
(
self
):
def
_load_method_and_content_type
(
self
):
"""
"""
...
@@ -104,100 +130,83 @@ class Request(object):
...
@@ -104,100 +130,83 @@ class Request(object):
self
.
_content_type
=
self
.
META
.
get
(
'HTTP_CONTENT_TYPE'
,
self
.
META
.
get
(
'CONTENT_TYPE'
,
''
))
self
.
_content_type
=
self
.
META
.
get
(
'HTTP_CONTENT_TYPE'
,
self
.
META
.
get
(
'CONTENT_TYPE'
,
''
))
self
.
_perform_form_overloading
()
self
.
_perform_form_overloading
()
# if the HTTP method was not overloaded, we take the raw HTTP method
# if the HTTP method was not overloaded, we take the raw HTTP method
if
not
hasattr
(
self
,
'_method'
):
if
not
_hasattr
(
self
,
'_method'
):
self
.
_method
=
self
.
request
.
method
self
.
_method
=
self
.
_request
.
method
def
_get_stream
(
self
):
"""
Returns an object that may be used to stream the request content.
"""
def
_load_stream
(
self
):
try
:
try
:
content_length
=
int
(
self
.
META
.
get
(
'CONTENT_LENGTH'
,
self
.
META
.
get
(
'HTTP_CONTENT_LENGTH'
)))
content_length
=
int
(
self
.
META
.
get
(
'CONTENT_LENGTH'
,
self
.
META
.
get
(
'HTTP_CONTENT_LENGTH'
)))
except
(
ValueError
,
TypeError
):
except
(
ValueError
,
TypeError
):
content_length
=
0
content_length
=
0
# TODO: Add 1.3's LimitedStream to compat and use that.
# NOTE: Currently only supports parsing request body as a stream with 1.3
if
content_length
==
0
:
if
content_length
==
0
:
return
None
self
.
_stream
=
None
elif
hasattr
(
self
,
'read'
):
elif
hasattr
(
self
.
_request
,
'read'
):
return
self
self
.
_stream
=
self
.
_request
return
StringIO
(
self
.
raw_post_data
)
else
:
self
.
_stream
=
StringIO
(
self
.
raw_post_data
)
def
_perform_form_overloading
(
self
):
def
_perform_form_overloading
(
self
):
"""
"""
If this is a form POST request, then we need to check if the method and content/content_type have been
If this is a form POST request, then we need to check if the method and
overridden by setting them in hidden form fields or not.
content/content_type have been overridden by setting them in hidden
form fields or not.
"""
"""
# We only need to use form overloading on form POST requests.
# We only need to use form overloading on form POST requests.
if
(
not
self
.
_USE_FORM_OVERLOADING
or
self
.
request
.
method
!=
'POST'
if
(
not
self
.
_USE_FORM_OVERLOADING
or
self
.
_request
.
method
!=
'POST'
or
not
is_form_media_type
(
self
.
_content_type
)):
or
not
is_form_media_type
(
self
.
_content_type
)):
return
return
# At this point we're committed to parsing the request as form data.
# At this point we're committed to parsing the request as form data.
self
.
_data
=
data
=
self
.
POST
.
copy
()
self
.
_data
=
self
.
_request
.
POST
self
.
_files
=
self
.
FILES
self
.
_files
=
self
.
_request
.
FILES
# Method overloading - change the method and remove the param from the content.
# Method overloading - change the method and remove the param from the content.
if
self
.
_METHOD_PARAM
in
data
:
if
self
.
_METHOD_PARAM
in
self
.
_
data
:
# NOTE:
unlike `get`, `pop` on a `QueryDict` seems to return
a list of values.
# NOTE:
`pop` on a `QueryDict` returns
a list of values.
self
.
_method
=
self
.
_data
.
pop
(
self
.
_METHOD_PARAM
)[
0
]
.
upper
()
self
.
_method
=
self
.
_data
.
pop
(
self
.
_METHOD_PARAM
)[
0
]
.
upper
()
# Content overloading - modify the content type, and re-parse.
# Content overloading - modify the content type, and re-parse.
if
self
.
_CONTENT_PARAM
in
data
and
self
.
_CONTENTTYPE_PARAM
in
data
:
if
(
self
.
_CONTENT_PARAM
in
self
.
_data
and
self
.
_CONTENTTYPE_PARAM
in
self
.
_data
):
self
.
_content_type
=
self
.
_data
.
pop
(
self
.
_CONTENTTYPE_PARAM
)[
0
]
self
.
_content_type
=
self
.
_data
.
pop
(
self
.
_CONTENTTYPE_PARAM
)[
0
]
stream
=
StringIO
(
self
.
_data
.
pop
(
self
.
_CONTENT_PARAM
)[
0
])
s
elf
.
_s
tream
=
StringIO
(
self
.
_data
.
pop
(
self
.
_CONTENT_PARAM
)[
0
])
(
self
.
_data
,
self
.
_files
)
=
self
.
_parse
(
stream
,
self
.
_content_type
)
(
self
.
_data
,
self
.
_files
)
=
self
.
_parse
()
def
_parse
(
self
,
stream
,
content_type
):
def
_parse
(
self
):
"""
"""
Parse the request content.
Parse the request content.
May raise a 415 ImmediateResponse (Unsupported Media Type), or a 400 ImmediateResponse (Bad Request).
May raise a 415 ImmediateResponse (Unsupported Media Type), or a
400 ImmediateResponse (Bad Request).
"""
"""
if
s
tream
is
None
or
content_type
is
None
:
if
s
elf
.
stream
is
None
or
self
.
content_type
is
None
:
return
(
None
,
None
)
return
(
None
,
None
)
for
parser
in
as_tuple
(
self
.
parsers
):
for
parser
in
self
.
get_parsers
(
):
if
parser
.
can_handle_request
(
content_type
):
if
parser
.
can_handle_request
(
self
.
content_type
):
return
parser
.
parse
(
s
tream
)
return
parser
.
parse
(
s
elf
.
stream
,
self
.
META
,
self
.
upload_handlers
)
raise
ImmediateResponse
({
self
.
_raise_415_response
(
self
.
_content_type
)
'error'
:
'Unsupported media type in request
\'
%
s
\'
.'
%
content_type
},
status
=
status
.
HTTP_415_UNSUPPORTED_MEDIA_TYPE
)
@property
def
_parsed_media_types
(
self
):
"""
Return a list of all the media types that this view can parse.
"""
return
[
parser
.
media_type
for
parser
in
self
.
parsers
]
@property
def
_raise_415_response
(
self
,
content_type
):
def
_default_parser
(
self
):
"""
"""
R
eturn the view's default parser class
.
R
aise a 415 response if we cannot parse the given content type
.
"""
"""
return
self
.
parsers
[
0
]
from
djangorestframework.response
import
ImmediateResponse
def
_get_parsers
(
self
):
if
hasattr
(
self
,
'_parsers'
):
return
self
.
_parsers
return
()
def
_set_parsers
(
self
,
value
):
raise
ImmediateResponse
(
self
.
_parsers
=
value
{
'error'
:
'Unsupported media type in request
\'
%
s
\'
.'
parsers
=
property
(
_get_parsers
,
_set_parsers
)
%
content_type
},
status
=
status
.
HTTP_415_UNSUPPORTED_MEDIA_TYPE
)
def
__getattr__
(
self
,
name
):
def
__getattr__
(
self
,
name
):
"""
"""
When an attribute is not present on the calling instance, try to get it
Proxy other attributes to the underlying HttpRequest object.
from the original request.
"""
"""
if
hasattr
(
self
.
request
,
name
):
return
getattr
(
self
.
_request
,
name
)
return
getattr
(
self
.
request
,
name
)
else
:
return
super
(
Request
,
self
)
.
__getattribute__
(
name
)
djangorestframework/resources.py
View file @
1cde31c8
from
django
import
forms
from
django
import
forms
from
django.core.urlresolvers
import
get_urlconf
,
get_resolver
,
NoReverseMatch
from
django.db
import
models
from
djangorestframework.response
import
ImmediateResponse
from
djangorestframework.response
import
ImmediateResponse
from
djangorestframework.reverse
import
reverse
from
djangorestframework.serializer
import
Serializer
from
djangorestframework.serializer
import
Serializer
,
_SkipField
from
djangorestframework.utils
import
as_tuple
from
djangorestframework.utils
import
as_tuple
,
reverse
class
BaseResource
(
Serializer
):
class
BaseResource
(
Serializer
):
"""
"""
Base class for all Resource classes, which simply defines the interface they provide.
Base class for all Resource classes, which simply defines the interface
they provide.
"""
"""
fields
=
None
fields
=
None
include
=
None
include
=
None
...
@@ -19,11 +16,13 @@ class BaseResource(Serializer):
...
@@ -19,11 +16,13 @@ class BaseResource(Serializer):
def
__init__
(
self
,
view
=
None
,
depth
=
None
,
stack
=
[],
**
kwargs
):
def
__init__
(
self
,
view
=
None
,
depth
=
None
,
stack
=
[],
**
kwargs
):
super
(
BaseResource
,
self
)
.
__init__
(
depth
,
stack
,
**
kwargs
)
super
(
BaseResource
,
self
)
.
__init__
(
depth
,
stack
,
**
kwargs
)
self
.
view
=
view
self
.
view
=
view
self
.
request
=
getattr
(
view
,
'request'
,
None
)
def
validate_request
(
self
,
data
,
files
=
None
):
def
validate_request
(
self
,
data
,
files
=
None
):
"""
"""
Given the request content return the cleaned, validated content.
Given the request content return the cleaned, validated content.
Typically raises a :exc:`response.ImmediateResponse` with status code 400 (Bad Request) on failure.
Typically raises a :exc:`response.ImmediateResponse` with status code
400 (Bad Request) on failure.
"""
"""
return
data
return
data
...
@@ -37,7 +36,8 @@ class BaseResource(Serializer):
...
@@ -37,7 +36,8 @@ class BaseResource(Serializer):
class
Resource
(
BaseResource
):
class
Resource
(
BaseResource
):
"""
"""
A Resource determines how a python object maps to some serializable data.
A Resource determines how a python object maps to some serializable data.
Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets.
Objects that a resource can act on include plain Python object instances,
Django Models, and Django QuerySets.
"""
"""
# The model attribute refers to the Django Model which this Resource maps to.
# The model attribute refers to the Django Model which this Resource maps to.
...
@@ -220,9 +220,6 @@ class ModelResource(FormResource):
...
@@ -220,9 +220,6 @@ class ModelResource(FormResource):
Also provides a :meth:`get_bound_form` method which may be used by some renderers.
Also provides a :meth:`get_bound_form` method which may be used by some renderers.
"""
"""
# Auto-register new ModelResource classes into _model_to_resource
#__metaclass__ = _RegisterModelResource
form
=
None
form
=
None
"""
"""
The form class that should be used for request validation.
The form class that should be used for request validation.
...
@@ -256,7 +253,7 @@ class ModelResource(FormResource):
...
@@ -256,7 +253,7 @@ class ModelResource(FormResource):
The list of fields to exclude. This is only used if :attr:`fields` is not set.
The list of fields to exclude. This is only used if :attr:`fields` is not set.
"""
"""
include
=
(
'url'
,
)
include
=
()
"""
"""
The list of extra fields to include. This is only used if :attr:`fields` is not set.
The list of extra fields to include. This is only used if :attr:`fields` is not set.
"""
"""
...
@@ -319,47 +316,6 @@ class ModelResource(FormResource):
...
@@ -319,47 +316,6 @@ class ModelResource(FormResource):
return
form
()
return
form
()
def
url
(
self
,
instance
):
"""
Attempts to reverse resolve the url of the given model *instance* for this resource.
Requires a ``View`` with :class:`mixins.InstanceMixin` to have been created for this resource.
This method can be overridden if you need to set the resource url reversing explicitly.
"""
if
not
hasattr
(
self
,
'view_callable'
):
raise
_SkipField
# dis does teh magicks...
urlconf
=
get_urlconf
()
resolver
=
get_resolver
(
urlconf
)
possibilities
=
resolver
.
reverse_dict
.
getlist
(
self
.
view_callable
[
0
])
for
tuple_item
in
possibilities
:
possibility
=
tuple_item
[
0
]
# pattern = tuple_item[1]
# Note: defaults = tuple_item[2] for django >= 1.3
for
result
,
params
in
possibility
:
#instance_attrs = dict([ (param, getattr(instance, param)) for param in params if hasattr(instance, param) ])
instance_attrs
=
{}
for
param
in
params
:
if
not
hasattr
(
instance
,
param
):
continue
attr
=
getattr
(
instance
,
param
)
if
isinstance
(
attr
,
models
.
Model
):
instance_attrs
[
param
]
=
attr
.
pk
else
:
instance_attrs
[
param
]
=
attr
try
:
return
reverse
(
self
.
view_callable
[
0
],
self
.
view
.
request
,
kwargs
=
instance_attrs
)
except
NoReverseMatch
:
pass
raise
_SkipField
@property
@property
def
_model_fields_set
(
self
):
def
_model_fields_set
(
self
):
"""
"""
...
...
djangorestframework/response.py
View file @
1cde31c8
...
@@ -27,6 +27,10 @@ from djangorestframework import status
...
@@ -27,6 +27,10 @@ from djangorestframework import status
__all__
=
(
'Response'
,
'ImmediateResponse'
)
__all__
=
(
'Response'
,
'ImmediateResponse'
)
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 may include content that hasn't yet been serialized.
...
@@ -40,25 +44,30 @@ class Response(SimpleTemplateResponse):
...
@@ -40,25 +44,30 @@ class Response(SimpleTemplateResponse):
_ACCEPT_QUERY_PARAM
=
'_accept'
# Allow override of Accept header in URL query params
_ACCEPT_QUERY_PARAM
=
'_accept'
# Allow override of Accept header in URL query params
_IGNORE_IE_ACCEPT_HEADER
=
True
_IGNORE_IE_ACCEPT_HEADER
=
True
def
__init__
(
self
,
content
=
None
,
status
=
None
,
request
=
None
,
renderers
=
None
,
head
ers
=
None
):
def
__init__
(
self
,
content
=
None
,
status
=
None
,
headers
=
None
,
view
=
None
,
request
=
None
,
render
ers
=
None
):
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
# which we don't need
# which we don't need
super
(
Response
,
self
)
.
__init__
(
None
,
status
=
status
)
super
(
Response
,
self
)
.
__init__
(
None
,
status
=
status
)
# We need to store our content in raw content to avoid overriding HttpResponse's
# `content` property
self
.
raw_content
=
content
self
.
raw_content
=
content
self
.
has_content_body
=
content
is
not
None
self
.
has_content_body
=
content
is
not
None
self
.
request
=
request
self
.
headers
=
headers
and
headers
[:]
or
[]
self
.
headers
=
headers
and
headers
[:]
or
[]
if
renderers
is
not
None
:
self
.
view
=
view
self
.
request
=
request
self
.
renderers
=
renderers
self
.
renderers
=
renderers
def
get_renderers
(
self
):
"""
Instantiates and returns the list of renderers the response will use.
"""
return
[
renderer
(
self
.
view
)
for
renderer
in
self
.
renderers
]
@property
@property
def
rendered_content
(
self
):
def
rendered_content
(
self
):
"""
"""
The final rendered content. Accessing this attribute triggers the complete rendering cycle :
The final rendered content. Accessing this attribute triggers the
selecting suitable renderer, setting response's actual content type, rendering data.
complete rendering cycle: selecting suitable renderer, setting
response's actual content type, rendering data.
"""
"""
renderer
,
media_type
=
self
.
_determine_renderer
()
renderer
,
media_type
=
self
.
_determine_renderer
()
...
@@ -70,6 +79,13 @@ class Response(SimpleTemplateResponse):
...
@@ -70,6 +79,13 @@ class Response(SimpleTemplateResponse):
return
renderer
.
render
(
self
.
raw_content
,
media_type
)
return
renderer
.
render
(
self
.
raw_content
,
media_type
)
return
renderer
.
render
()
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
):
"""
"""
...
@@ -88,8 +104,6 @@ class Response(SimpleTemplateResponse):
...
@@ -88,8 +104,6 @@ class Response(SimpleTemplateResponse):
If those are useless, a default value is returned instead.
If those are useless, a default value is returned instead.
"""
"""
request
=
self
.
request
request
=
self
.
request
if
request
is
None
:
return
[
'*/*'
]
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
# Use _accept parameter override
...
@@ -108,70 +122,52 @@ class Response(SimpleTemplateResponse):
...
@@ -108,70 +122,52 @@ class Response(SimpleTemplateResponse):
def
_determine_renderer
(
self
):
def
_determine_renderer
(
self
):
"""
"""
Determines the appropriate renderer for the output, given the list of
accepted media types,
Determines the appropriate renderer for the output, given the list of
and the :attr:`renderers` set on this class.
a
ccepted media types, a
nd the :attr:`renderers` set on this class.
Returns a 2-tuple of `(renderer, media_type)`
Returns a 2-tuple of `(renderer, media_type)`
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
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,
# Check the acceptable media types against each renderer,
# attempting more specific media types first
# attempting more specific media types first
# NB. The inner loop here isn't as bad as it first looks :)
# 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)
# Worst case is we're looping over len(accept_list) * len(self.renderers)
for
media_type_list
in
order_by_precedence
(
self
.
_determine_accept_list
()
):
for
media_type_list
in
order_by_precedence
(
accepts
):
for
renderer
in
self
.
renderers
:
for
renderer
in
renderers
:
for
media_type
in
media_type_list
:
for
media_type
in
media_type_list
:
if
renderer
.
can_handle_response
(
media_type
):
if
renderer
.
can_handle_response
(
media_type
):
return
renderer
,
media_type
return
renderer
,
media_type
# No acceptable renderers were found
# No acceptable renderers were found
raise
ImmediateResponse
({
'detail'
:
'Could not satisfy the client
\'
s Accept header'
,
raise
NotAcceptable
'available_types'
:
self
.
_rendered_media_types
},
def
_get_406_response
(
self
):
renderer
=
self
.
renderers
[
0
]
return
Response
(
{
'detail'
:
'Could not satisfy the client
\'
s Accept header'
,
'available_types'
:
[
renderer
.
media_type
for
renderer
in
self
.
renderers
]
},
status
=
status
.
HTTP_406_NOT_ACCEPTABLE
,
status
=
status
.
HTTP_406_NOT_ACCEPTABLE
,
renderers
=
self
.
renderers
)
view
=
self
.
view
,
request
=
self
.
request
,
renderers
=
[
renderer
])
def
_get_renderers
(
self
):
if
hasattr
(
self
,
'_renderers'
):
return
self
.
_renderers
return
()
def
_set_renderers
(
self
,
value
):
self
.
_renderers
=
value
renderers
=
property
(
_get_renderers
,
_set_renderers
)
@property
def
_rendered_media_types
(
self
):
"""
Return an list of all the media types that this response can render.
"""
return
[
renderer
.
media_type
for
renderer
in
self
.
renderers
]
@property
def
_rendered_formats
(
self
):
"""
Return a list of all the formats that this response can render.
"""
return
[
renderer
.
format
for
renderer
in
self
.
renderers
]
@property
def
_default_renderer
(
self
):
"""
Return the response's default renderer class.
"""
return
self
.
renderers
[
0
]
class
ImmediateResponse
(
Response
,
Exception
):
class
ImmediateResponse
(
Response
,
Exception
):
"""
"""
A subclass of :class:`Response` used to abort the current request handling.
An exception representing an Response that should be returned immediately.
Any content should be serialized as-is, without being filtered.
"""
"""
def
__str__
(
self
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
"""
self
.
response
=
Response
(
*
args
,
**
kwargs
)
Since this class is also an exception it has to provide a sensible
representation for the cases when it is treated as an exception.
"""
return
(
'
%
s must be caught in try/except block, '
'and returned as a normal HttpResponse'
%
self
.
__class__
.
__name__
)
djangorestframework/reverse.py
View file @
1cde31c8
...
@@ -2,22 +2,19 @@
...
@@ -2,22 +2,19 @@
Provide reverse functions that return fully qualified URLs
Provide reverse functions that return fully qualified URLs
"""
"""
from
django.core.urlresolvers
import
reverse
as
django_reverse
from
django.core.urlresolvers
import
reverse
as
django_reverse
from
django
restframework.compat
import
reverse_lazy
as
django_reverse_
lazy
from
django
.utils.functional
import
lazy
def
reverse
(
viewname
,
request
,
*
args
,
**
kwargs
):
def
reverse
(
viewname
,
*
args
,
**
kwargs
):
"""
"""
Do the same as `django.core.urlresolvers.reverse` but using
Same as `django.core.urlresolvers.reverse`, but optionally takes a request
*request* to build a fully qualified
URL.
and returns a fully qualified URL, using the request to get the base
URL.
"""
"""
request
=
kwargs
.
pop
(
'request'
,
None
)
url
=
django_reverse
(
viewname
,
*
args
,
**
kwargs
)
url
=
django_reverse
(
viewname
,
*
args
,
**
kwargs
)
if
request
:
return
request
.
build_absolute_uri
(
url
)
return
request
.
build_absolute_uri
(
url
)
return
url
def
reverse_lazy
(
viewname
,
request
,
*
args
,
**
kwargs
):
reverse_lazy
=
lazy
(
reverse
,
str
)
"""
Do the same as `django.core.urlresolvers.reverse_lazy` but using
*request* to build a fully qualified URL.
"""
url
=
django_reverse_lazy
(
viewname
,
*
args
,
**
kwargs
)
return
request
.
build_absolute_uri
(
url
)
djangorestframework/runtests/settings.py
View file @
1cde31c8
...
@@ -53,11 +53,6 @@ MEDIA_ROOT = ''
...
@@ -53,11 +53,6 @@ MEDIA_ROOT = ''
# Examples: "http://media.lawrence.com", "http://example.com/media/"
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL
=
''
MEDIA_URL
=
''
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX
=
'/media/'
# Make this unique, and don't share it with anybody.
# Make this unique, and don't share it with anybody.
SECRET_KEY
=
'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!
%
cy'
SECRET_KEY
=
'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!
%
cy'
...
...
djangorestframework/templates/djangorestframework/base.html
View file @
1cde31c8
...
@@ -20,8 +20,15 @@
...
@@ -20,8 +20,15 @@
<h1
id=
"site-name"
>
{% block branding %}
<a
href=
'http://django-rest-framework.org'
>
Django REST framework
</a>
<span
class=
"version"
>
v {{ version }}
</span>
{% endblock %}
</h1>
<h1
id=
"site-name"
>
{% block branding %}
<a
href=
'http://django-rest-framework.org'
>
Django REST framework
</a>
<span
class=
"version"
>
v {{ version }}
</span>
{% endblock %}
</h1>
</div>
</div>
<div
id=
"user-tools"
>
<div
id=
"user-tools"
>
{% if user.is_active %}Welcome, {{ user }}.{% if logout_url %}
<a
href=
'{{ logout_url }}'
>
Log out
</a>
{% endif %}{% else %}Anonymous {% if login_url %}
<a
href=
'{{ login_url }}'
>
Log in
</a>
{% endif %}{% endif %}
{% block userlinks %}
{% block userlinks %}{% endblock %}
{% if user.is_active %}
Welcome, {{ user }}.
<a
href=
'{% url djangorestframework:logout %}?next={{ request.path }}'
>
Log out
</a>
{% else %}
Anonymous
<a
href=
'{% url djangorestframework:login %}?next={{ request.path }}'
>
Log in
</a>
{% endif %}
{% endblock %}
</div>
</div>
{% block nav-global %}{% endblock %}
{% block nav-global %}{% endblock %}
</div>
</div>
...
...
djangorestframework/templates/djangorestframework/login.html
View file @
1cde31c8
...
@@ -17,7 +17,7 @@
...
@@ -17,7 +17,7 @@
<div
id=
"content"
class=
"colM"
>
<div
id=
"content"
class=
"colM"
>
<div
id=
"content-main"
>
<div
id=
"content-main"
>
<form
method=
"post"
action=
"{% url djangorestframework
.utils.staticviews.api_
login %}"
id=
"login-form"
>
<form
method=
"post"
action=
"{% url djangorestframework
:
login %}"
id=
"login-form"
>
{% csrf_token %}
{% csrf_token %}
<div
class=
"form-row"
>
<div
class=
"form-row"
>
<label
for=
"id_username"
>
Username:
</label>
{{ form.username }}
<label
for=
"id_username"
>
Username:
</label>
{{ form.username }}
...
...
djangorestframework/tests/__init__.py
View file @
1cde31c8
...
@@ -10,4 +10,3 @@ for module in modules:
...
@@ -10,4 +10,3 @@ for module in modules:
exec
(
"from djangorestframework.tests.
%
s import __doc__ as module_doc"
%
module
)
exec
(
"from djangorestframework.tests.
%
s import __doc__ as module_doc"
%
module
)
exec
(
"from djangorestframework.tests.
%
s import *"
%
module
)
exec
(
"from djangorestframework.tests.
%
s import *"
%
module
)
__test__
[
module
]
=
module_doc
or
""
__test__
[
module
]
=
module_doc
or
""
djangorestframework/tests/accept.py
View file @
1cde31c8
from
django.conf.urls.defaults
import
patterns
,
url
,
include
from
django.test
import
TestCase
from
django.test
import
TestCase
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.compat
import
RequestFactory
...
@@ -15,9 +16,19 @@ SAFARI_5_0_USER_AGENT = 'Mozilla/5.0 (X11; U; Linux x86_64; en-ca) AppleWebKit/5
...
@@ -15,9 +16,19 @@ SAFARI_5_0_USER_AGENT = 'Mozilla/5.0 (X11; U; Linux x86_64; en-ca) AppleWebKit/5
OPERA_11_0_MSIE_USER_AGENT
=
'Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00'
OPERA_11_0_MSIE_USER_AGENT
=
'Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00'
OPERA_11_0_OPERA_USER_AGENT
=
'Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00'
OPERA_11_0_OPERA_USER_AGENT
=
'Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00'
urlpatterns
=
patterns
(
''
,
url
(
r'^api'
,
include
(
'djangorestframework.urls'
,
namespace
=
'djangorestframework'
))
)
class
UserAgentMungingTest
(
TestCase
):
class
UserAgentMungingTest
(
TestCase
):
"""We need to fake up the accept headers when we deal with MSIE. Blergh.
"""
http://www.gethifi.com/blog/browser-rest-http-accept-headers"""
We need to fake up the accept headers when we deal with MSIE. Blergh.
http://www.gethifi.com/blog/browser-rest-http-accept-headers
"""
urls
=
'djangorestframework.tests.accept'
def
setUp
(
self
):
def
setUp
(
self
):
...
...
djangorestframework/tests/modelviews.py
View file @
1cde31c8
from
django.conf.urls.defaults
import
patterns
,
url
from
django.conf.urls.defaults
import
patterns
,
url
from
django.test
import
TestCase
from
django.forms
import
ModelForm
from
django.forms
import
ModelForm
from
django.contrib.auth.models
import
Group
,
User
from
django.contrib.auth.models
import
Group
,
User
from
djangorestframework.resources
import
ModelResource
from
djangorestframework.resources
import
ModelResource
...
@@ -7,18 +6,22 @@ from djangorestframework.views import ListOrCreateModelView, InstanceModelView
...
@@ -7,18 +6,22 @@ from djangorestframework.views import ListOrCreateModelView, InstanceModelView
from
djangorestframework.tests.models
import
CustomUser
from
djangorestframework.tests.models
import
CustomUser
from
djangorestframework.tests.testcases
import
TestModelsTestCase
from
djangorestframework.tests.testcases
import
TestModelsTestCase
class
GroupResource
(
ModelResource
):
class
GroupResource
(
ModelResource
):
model
=
Group
model
=
Group
class
UserForm
(
ModelForm
):
class
UserForm
(
ModelForm
):
class
Meta
:
class
Meta
:
model
=
User
model
=
User
exclude
=
(
'last_login'
,
'date_joined'
)
exclude
=
(
'last_login'
,
'date_joined'
)
class
UserResource
(
ModelResource
):
class
UserResource
(
ModelResource
):
model
=
User
model
=
User
form
=
UserForm
form
=
UserForm
class
CustomUserResource
(
ModelResource
):
class
CustomUserResource
(
ModelResource
):
model
=
CustomUser
model
=
CustomUser
...
...
djangorestframework/tests/oauthentication.py
View file @
1cde31c8
...
@@ -27,7 +27,7 @@ else:
...
@@ -27,7 +27,7 @@ else:
urlpatterns
=
patterns
(
''
,
urlpatterns
=
patterns
(
''
,
url
(
r'^$'
,
oauth_required
(
ClientView
.
as_view
())),
url
(
r'^$'
,
oauth_required
(
ClientView
.
as_view
())),
url
(
r'^oauth/'
,
include
(
'oauth_provider.urls'
)),
url
(
r'^oauth/'
,
include
(
'oauth_provider.urls'
)),
url
(
r'^
accounts/login/$'
,
'djangorestframework.utils.staticviews.api_login'
),
url
(
r'^
restframework/'
,
include
(
'djangorestframework.urls'
,
namespace
=
'djangorestframework'
)
),
)
)
...
...
djangorestframework/tests/parsers.py
View file @
1cde31c8
...
@@ -132,17 +132,18 @@
...
@@ -132,17 +132,18 @@
# self.assertEqual(files['file1'].read(), 'blablabla')
# self.assertEqual(files['file1'].read(), 'blablabla')
from
StringIO
import
StringIO
from
StringIO
import
StringIO
from
cgi
import
parse_qs
from
django
import
forms
from
django
import
forms
from
django.test
import
TestCase
from
django.test
import
TestCase
from
djangorestframework.parsers
import
FormParser
from
djangorestframework.parsers
import
FormParser
from
djangorestframework.parsers
import
XMLParser
from
djangorestframework.parsers
import
XMLParser
import
datetime
import
datetime
class
Form
(
forms
.
Form
):
class
Form
(
forms
.
Form
):
field1
=
forms
.
CharField
(
max_length
=
3
)
field1
=
forms
.
CharField
(
max_length
=
3
)
field2
=
forms
.
CharField
()
field2
=
forms
.
CharField
()
class
TestFormParser
(
TestCase
):
class
TestFormParser
(
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
self
.
string
=
"field1=abc&field2=defghijk"
self
.
string
=
"field1=abc&field2=defghijk"
...
@@ -152,10 +153,11 @@ class TestFormParser(TestCase):
...
@@ -152,10 +153,11 @@ class TestFormParser(TestCase):
parser
=
FormParser
(
None
)
parser
=
FormParser
(
None
)
stream
=
StringIO
(
self
.
string
)
stream
=
StringIO
(
self
.
string
)
(
data
,
files
)
=
parser
.
parse
(
stream
)
(
data
,
files
)
=
parser
.
parse
(
stream
,
{},
[]
)
self
.
assertEqual
(
Form
(
data
)
.
is_valid
(),
True
)
self
.
assertEqual
(
Form
(
data
)
.
is_valid
(),
True
)
class
TestXMLParser
(
TestCase
):
class
TestXMLParser
(
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
self
.
_input
=
StringIO
(
self
.
_input
=
StringIO
(
...
...
djangorestframework/tests/renderers.py
View file @
1cde31c8
import
re
import
re
from
django.conf.urls.defaults
import
patterns
,
url
,
include
from
django.test
import
TestCase
from
django.test
import
TestCase
from
django.conf.urls.defaults
import
patterns
,
url
from
djangorestframework
import
status
from
django.test
import
TestCase
from
djangorestframework.compat
import
View
as
DjangoView
from
djangorestframework.response
import
Response
from
djangorestframework.response
import
Response
from
djangorestframework.mixins
import
ResponseMixin
from
djangorestframework.views
import
View
from
djangorestframework.views
import
View
from
djangorestframework.renderers
import
BaseRenderer
,
JSONRenderer
,
YAMLRenderer
,
\
from
djangorestframework.renderers
import
BaseRenderer
,
JSONRenderer
,
YAMLRenderer
,
\
XMLRenderer
,
JSONPRenderer
,
DocumentingHTMLRenderer
XMLRenderer
,
JSONPRenderer
,
DocumentingHTMLRenderer
from
djangorestframework.parsers
import
JSONParser
,
YAMLParser
,
XMLParser
from
djangorestframework.parsers
import
YAMLParser
,
XMLParser
from
StringIO
import
StringIO
from
StringIO
import
StringIO
import
datetime
import
datetime
from
decimal
import
Decimal
from
decimal
import
Decimal
DUMMYSTATUS
=
status
.
HTTP_200_OK
DUMMYCONTENT
=
'dummycontent'
RENDERER_A_SERIALIZER
=
lambda
x
:
'Renderer A:
%
s'
%
x
RENDERER_B_SERIALIZER
=
lambda
x
:
'Renderer B:
%
s'
%
x
class
RendererA
(
BaseRenderer
):
media_type
=
'mock/renderera'
format
=
"formata"
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
return
RENDERER_A_SERIALIZER
(
obj
)
class
RendererB
(
BaseRenderer
):
media_type
=
'mock/rendererb'
format
=
"formatb"
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
return
RENDERER_B_SERIALIZER
(
obj
)
class
MockView
(
ResponseMixin
,
DjangoView
):
renderers
=
(
RendererA
,
RendererB
)
def
get
(
self
,
request
,
**
kwargs
):
response
=
Response
(
DUMMYSTATUS
,
DUMMYCONTENT
)
return
self
.
render
(
response
)
class
MockGETView
(
View
):
def
get
(
self
,
request
,
**
kwargs
):
return
{
'foo'
:
[
'bar'
,
'baz'
]}
class
HTMLView
(
View
):
renderers
=
(
DocumentingHTMLRenderer
,
)
def
get
(
self
,
request
,
**
kwargs
):
return
'text'
class
HTMLView1
(
View
):
renderers
=
(
DocumentingHTMLRenderer
,
JSONRenderer
)
def
get
(
self
,
request
,
**
kwargs
):
return
'text'
urlpatterns
=
patterns
(
''
,
url
(
r'^.*\.(?P<format>.+)$'
,
MockView
.
as_view
(
renderers
=
[
RendererA
,
RendererB
])),
url
(
r'^$'
,
MockView
.
as_view
(
renderers
=
[
RendererA
,
RendererB
])),
url
(
r'^jsonp/jsonrenderer$'
,
MockGETView
.
as_view
(
renderers
=
[
JSONRenderer
,
JSONPRenderer
])),
url
(
r'^jsonp/nojsonrenderer$'
,
MockGETView
.
as_view
(
renderers
=
[
JSONPRenderer
])),
url
(
r'^html$'
,
HTMLView
.
as_view
()),
url
(
r'^html1$'
,
HTMLView1
.
as_view
()),
url
(
r'^api'
,
include
(
'djangorestframework.urls'
,
namespace
=
'djangorestframework'
))
)
class
RendererIntegrationTests
(
TestCase
):
"""
End-to-end testing of renderers using an RendererMixin on a generic view.
"""
urls
=
'djangorestframework.tests.renderers'
def
test_default_renderer_serializes_content
(
self
):
"""If the Accept header is not set the default renderer should serialize the response."""
resp
=
self
.
client
.
get
(
'/'
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererA
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_A_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_head_method_serializes_no_content
(
self
):
"""No response must be included in HEAD requests."""
resp
=
self
.
client
.
head
(
'/'
)
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererA
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
''
)
def
test_default_renderer_serializes_content_on_accept_any
(
self
):
"""If the Accept header is set to */* the default renderer should serialize the response."""
resp
=
self
.
client
.
get
(
'/'
,
HTTP_ACCEPT
=
'*/*'
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererA
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_A_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_specified_renderer_serializes_content_default_case
(
self
):
"""If the Accept header is set the specified renderer should serialize the response.
(In this case we check that works for the default renderer)"""
resp
=
self
.
client
.
get
(
'/'
,
HTTP_ACCEPT
=
RendererA
.
media_type
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererA
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_A_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_specified_renderer_serializes_content_non_default_case
(
self
):
"""If the Accept header is set the specified renderer should serialize the response.
(In this case we check that works for a non-default renderer)"""
resp
=
self
.
client
.
get
(
'/'
,
HTTP_ACCEPT
=
RendererB
.
media_type
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererB
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_specified_renderer_serializes_content_on_accept_query
(
self
):
"""The '_accept' query string should behave in the same way as the Accept header."""
resp
=
self
.
client
.
get
(
'/?_accept=
%
s'
%
RendererB
.
media_type
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererB
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_unsatisfiable_accept_header_on_request_returns_406_status
(
self
):
"""If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
resp
=
self
.
client
.
get
(
'/'
,
HTTP_ACCEPT
=
'foo/bar'
)
self
.
assertEquals
(
resp
.
status_code
,
status
.
HTTP_406_NOT_ACCEPTABLE
)
def
test_specified_renderer_serializes_content_on_format_query
(
self
):
"""If a 'format' query is specified, the renderer with the matching
format attribute should serialize the response."""
resp
=
self
.
client
.
get
(
'/?format=
%
s'
%
RendererB
.
format
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererB
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_specified_renderer_serializes_content_on_format_kwargs
(
self
):
"""If a 'format' keyword arg is specified, the renderer with the matching
format attribute should serialize the response."""
resp
=
self
.
client
.
get
(
'/something.formatb'
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererB
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_specified_renderer_is_used_on_format_query_with_matching_accept
(
self
):
"""If both a 'format' query and a matching Accept header specified,
the renderer with the matching format attribute should serialize the response."""
resp
=
self
.
client
.
get
(
'/?format=
%
s'
%
RendererB
.
format
,
HTTP_ACCEPT
=
RendererB
.
media_type
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererB
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_conflicting_format_query_and_accept_ignores_accept
(
self
):
"""If a 'format' query is specified that does not match the Accept
header, we should only honor the 'format' query string."""
resp
=
self
.
client
.
get
(
'/?format=
%
s'
%
RendererB
.
format
,
HTTP_ACCEPT
=
'dummy'
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererB
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_bla
(
self
):
# What the f***?
resp
=
self
.
client
.
get
(
'/?format=formatb'
,
HTTP_ACCEPT
=
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererB
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
_flat_repr
=
'{"foo": ["bar", "baz"]}'
_flat_repr
=
'{"foo": ["bar", "baz"]}'
_indented_repr
=
'{
\n
"foo": [
\n
"bar",
\n
"baz"
\n
]
\n
}'
_indented_repr
=
'{
\n
"foo": [
\n
"bar",
\n
"baz"
\n
]
\n
}'
...
@@ -27,6 +186,7 @@ def strip_trailing_whitespace(content):
...
@@ -27,6 +186,7 @@ def strip_trailing_whitespace(content):
"""
"""
return
re
.
sub
(
' +
\n
'
,
'
\n
'
,
content
)
return
re
.
sub
(
' +
\n
'
,
'
\n
'
,
content
)
class
JSONRendererTests
(
TestCase
):
class
JSONRendererTests
(
TestCase
):
"""
"""
Tests specific to the JSON Renderer
Tests specific to the JSON Renderer
...
@@ -51,30 +211,16 @@ class JSONRendererTests(TestCase):
...
@@ -51,30 +211,16 @@ class JSONRendererTests(TestCase):
content
=
renderer
.
render
(
obj
,
'application/json; indent=2'
)
content
=
renderer
.
render
(
obj
,
'application/json; indent=2'
)
self
.
assertEquals
(
strip_trailing_whitespace
(
content
),
_indented_repr
)
self
.
assertEquals
(
strip_trailing_whitespace
(
content
),
_indented_repr
)
def
test_render_and_parse
(
self
):
"""
Test rendering and then parsing returns the original object.
IE obj -> render -> parse -> obj.
"""
obj
=
{
'foo'
:
[
'bar'
,
'baz'
]}
renderer
=
JSONRenderer
(
None
)
parser
=
JSONParser
(
None
)
content
=
renderer
.
render
(
obj
,
'application/json'
)
(
data
,
files
)
=
parser
.
parse
(
StringIO
(
content
))
self
.
assertEquals
(
obj
,
data
)
class
MockGETView
(
View
):
class
MockGETView
(
View
):
def
get
(
self
,
request
,
**
kwargs
):
def
get
(
self
,
request
,
*
args
,
*
*
kwargs
):
return
Response
({
'foo'
:
[
'bar'
,
'baz'
]})
return
Response
({
'foo'
:
[
'bar'
,
'baz'
]})
urlpatterns
=
patterns
(
''
,
urlpatterns
=
patterns
(
''
,
url
(
r'^jsonp/jsonrenderer$'
,
MockGETView
.
as_view
(
renderer
_classe
s
=
[
JSONRenderer
,
JSONPRenderer
])),
url
(
r'^jsonp/jsonrenderer$'
,
MockGETView
.
as_view
(
renderers
=
[
JSONRenderer
,
JSONPRenderer
])),
url
(
r'^jsonp/nojsonrenderer$'
,
MockGETView
.
as_view
(
renderer
_classe
s
=
[
JSONPRenderer
])),
url
(
r'^jsonp/nojsonrenderer$'
,
MockGETView
.
as_view
(
renderers
=
[
JSONPRenderer
])),
)
)
...
@@ -149,7 +295,6 @@ if YAMLRenderer:
...
@@ -149,7 +295,6 @@ if YAMLRenderer:
self
.
assertEquals
(
obj
,
data
)
self
.
assertEquals
(
obj
,
data
)
class
XMLRendererTestCase
(
TestCase
):
class
XMLRendererTestCase
(
TestCase
):
"""
"""
Tests specific to the XML Renderer
Tests specific to the XML Renderer
...
@@ -245,4 +390,3 @@ class XMLRendererTestCase(TestCase):
...
@@ -245,4 +390,3 @@ class XMLRendererTestCase(TestCase):
self
.
assertTrue
(
xml
.
startswith
(
'<?xml version="1.0" encoding="utf-8"?>
\n
<root>'
))
self
.
assertTrue
(
xml
.
startswith
(
'<?xml version="1.0" encoding="utf-8"?>
\n
<root>'
))
self
.
assertTrue
(
xml
.
endswith
(
'</root>'
))
self
.
assertTrue
(
xml
.
endswith
(
'</root>'
))
self
.
assertTrue
(
string
in
xml
,
'
%
r not in
%
r'
%
(
string
,
xml
))
self
.
assertTrue
(
string
in
xml
,
'
%
r not in
%
r'
%
(
string
,
xml
))
djangorestframework/tests/request.py
View file @
1cde31c8
...
@@ -4,205 +4,214 @@ Tests for content parsing, and form-overloaded content parsing.
...
@@ -4,205 +4,214 @@ Tests for content parsing, and form-overloaded content parsing.
from
django.conf.urls.defaults
import
patterns
from
django.conf.urls.defaults
import
patterns
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.test
import
TestCase
,
Client
from
django.test
import
TestCase
,
Client
from
django.utils
import
simplejson
as
json
from
djangorestframework
import
status
from
djangorestframework
import
status
from
djangorestframework.authentication
import
UserLoggedInAuthentication
from
djangorestframework.authentication
import
UserLoggedInAuthentication
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.utils
import
RequestFactory
from
djangorestframework.mixins
import
RequestMixin
from
djangorestframework.parsers
import
(
from
djangorestframework.parsers
import
FormParser
,
MultiPartParser
,
\
FormParser
,
PlainTextParser
,
JSONParser
MultiPartParser
,
PlainTextParser
,
JSONParser
)
from
djangorestframework.request
import
Request
from
djangorestframework.request
import
Request
from
djangorestframework.response
import
Response
from
djangorestframework.response
import
Response
from
djangorestframework.request
import
Request
from
djangorestframework.views
import
View
from
djangorestframework.views
import
View
class
RequestTestCase
(
TestCase
):
factory
=
RequestFactory
()
def
build_request
(
self
,
method
,
*
args
,
**
kwargs
):
factory
=
RequestFactory
()
method
=
getattr
(
factory
,
method
)
original_request
=
method
(
*
args
,
**
kwargs
)
return
Request
(
original_request
)
class
TestMethodOverloading
(
RequestTestCase
):
def
test_standard_behaviour_determines_GET
(
self
):
class
TestMethodOverloading
(
TestCase
):
"""GET requests identified"""
def
test_GET_method
(
self
):
request
=
self
.
build_request
(
'get'
,
'/'
)
"""
GET requests identified.
"""
request
=
factory
.
get
(
'/'
)
self
.
assertEqual
(
request
.
method
,
'GET'
)
self
.
assertEqual
(
request
.
method
,
'GET'
)
def
test_standard_behaviour_determines_POST
(
self
):
def
test_POST_method
(
self
):
"""POST requests identified"""
"""
request
=
self
.
build_request
(
'post'
,
'/'
)
POST requests identified.
"""
request
=
factory
.
post
(
'/'
)
self
.
assertEqual
(
request
.
method
,
'POST'
)
self
.
assertEqual
(
request
.
method
,
'POST'
)
def
test_overloaded_POST_behaviour_determines_overloaded_method
(
self
):
def
test_HEAD_method
(
self
):
"""POST requests can be overloaded to another method by setting a reserved form field"""
"""
request
=
self
.
build_request
(
'post'
,
'/'
,
{
Request
.
_METHOD_PARAM
:
'DELETE'
})
HEAD requests identified.
self
.
assertEqual
(
request
.
method
,
'DELETE'
)
"""
request
=
factory
.
head
(
'/'
)
def
test_HEAD_is_a_valid_method
(
self
):
"""HEAD requests identified"""
request
=
request
=
self
.
build_request
(
'head'
,
'/'
)
self
.
assertEqual
(
request
.
method
,
'HEAD'
)
self
.
assertEqual
(
request
.
method
,
'HEAD'
)
def
test_overloaded_method
(
self
):
"""
POST requests can be overloaded to another method by setting a
reserved form field
"""
request
=
factory
.
post
(
'/'
,
{
Request
.
_METHOD_PARAM
:
'DELETE'
})
self
.
assertEqual
(
request
.
method
,
'DELETE'
)
class
TestContentParsing
(
RequestTestCase
):
def
build_request
(
self
,
method
,
*
args
,
**
kwargs
):
factory
=
RequestFactory
()
parsers
=
kwargs
.
pop
(
'parsers'
,
None
)
method
=
getattr
(
factory
,
method
)
original_request
=
method
(
*
args
,
**
kwargs
)
rkwargs
=
{}
if
parsers
is
not
None
:
rkwargs
[
'parsers'
]
=
parsers
request
=
Request
(
original_request
,
**
rkwargs
)
# TODO: Just a hack because the parsers need a view. This will be fixed in the future
class
Obj
(
object
):
pass
obj
=
Obj
()
obj
.
request
=
request
for
p
in
request
.
parsers
:
p
.
view
=
obj
return
request
class
TestContentParsing
(
TestCase
):
def
test_standard_behaviour_determines_no_content_GET
(
self
):
def
test_standard_behaviour_determines_no_content_GET
(
self
):
"""Ensure request.DATA returns None for GET request with no content."""
"""
request
=
self
.
build_request
(
'get'
,
'/'
)
Ensure request.DATA returns None for GET request with no content.
"""
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."""
"""
request
=
self
.
build_request
(
'head'
,
'/'
)
Ensure request.DATA returns None for 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
):
"""Ensure request.DATA returns content for POST request with form content."""
"""
form_data
=
{
'qwerty'
:
'uiop'
}
Ensure request.DATA returns content for POST request with form content.
parsers
=
(
FormParser
(),
MultiPartParser
())
"""
data
=
{
'qwerty'
:
'uiop'
}
request
=
self
.
build_request
(
'post'
,
'/'
,
data
=
form_data
,
parsers
=
parsers
)
parsers
=
(
FormParser
,
MultiPartParser
)
self
.
assertEqual
(
request
.
DATA
.
items
(),
form_data
.
items
())
request
=
factory
.
post
(
'/'
,
data
,
parser
=
parsers
)
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
):
"""Ensure request.DATA returns content for POST request with non-form content."""
"""
Ensure request.DATA returns content for POST request with
non-form content.
"""
content
=
'qwerty'
content
=
'qwerty'
content_type
=
'text/plain'
content_type
=
'text/plain'
parsers
=
(
PlainTextParser
()
,)
parsers
=
(
PlainTextParser
,)
request
=
factory
.
post
(
'/'
,
content
,
content_type
=
content_type
,
request
=
self
.
build_request
(
'post'
,
'/'
,
content
,
content_type
=
content_type
,
parsers
=
parsers
)
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
):
"""Ensure request.DATA returns content for PUT request with form content."""
"""
form_data
=
{
'qwerty'
:
'uiop'
}
Ensure request.DATA returns content for PUT request with form content.
parsers
=
(
FormParser
(),
MultiPartParser
())
"""
data
=
{
'qwerty'
:
'uiop'
}
request
=
self
.
build_request
(
'put'
,
'/'
,
data
=
form_data
,
parsers
=
parsers
)
parsers
=
(
FormParser
,
MultiPartParser
)
self
.
assertEqual
(
request
.
DATA
.
items
(),
form_data
.
items
())
request
=
factory
.
put
(
'/'
,
data
,
parsers
=
parsers
)
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
):
"""Ensure request.DATA returns content for PUT request with non-form content."""
"""
Ensure request.DATA returns content for PUT request with
non-form content.
"""
content
=
'qwerty'
content
=
'qwerty'
content_type
=
'text/plain'
content_type
=
'text/plain'
parsers
=
(
PlainTextParser
(),
)
parsers
=
(
PlainTextParser
,
)
request
=
factory
.
put
(
'/'
,
content
,
content_type
=
content_type
,
request
=
self
.
build_request
(
'put'
,
'/'
,
content
,
content_type
=
content_type
,
parsers
=
parsers
)
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
):
"""Ensure request.DATA returns content for overloaded POST request"""
"""
Ensure request.DATA returns content for overloaded POST request.
"""
content
=
'qwerty'
content
=
'qwerty'
content_type
=
'text/plain'
content_type
=
'text/plain'
form_data
=
{
Request
.
_CONTENT_PARAM
:
content
,
data
=
{
Request
.
_CONTENTTYPE_PARAM
:
content_type
}
Request
.
_CONTENT_PARAM
:
content
,
parsers
=
(
PlainTextParser
(),)
Request
.
_CONTENTTYPE_PARAM
:
content_type
}
request
=
self
.
build_request
(
'post'
,
'/'
,
form_data
,
parsers
=
parsers
)
parsers
=
(
PlainTextParser
,
)
request
=
factory
.
post
(
'/'
,
data
,
parsers
=
parsers
)
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
):
"""Ensures request.POST can be accessed after request.DATA in form request"""
"""
form_data
=
{
'qwerty'
:
'uiop'
}
Ensures request.POST can be accessed after request.DATA in
parsers
=
(
FormParser
(),
MultiPartParser
())
form request.
"""
request
=
self
.
build_request
(
'post'
,
'/'
,
data
=
form_data
)
data
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
request
.
DATA
.
items
(),
form_data
.
items
())
request
=
factory
.
post
(
'/'
,
data
=
data
)
self
.
assertEqual
(
request
.
POST
.
items
(),
form_data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
POST
.
items
(),
data
.
items
())
def
test_accessing_post_after_data_for_json
(
self
):
def
test_accessing_post_after_data_for_json
(
self
):
"""Ensures request.POST can be accessed after request.DATA in json request"""
"""
from
django.utils
import
simplejson
as
json
Ensures request.POST can be accessed after request.DATA in
json request.
"""
data
=
{
'qwerty'
:
'uiop'
}
data
=
{
'qwerty'
:
'uiop'
}
content
=
json
.
dumps
(
data
)
content
=
json
.
dumps
(
data
)
content_type
=
'application/json'
content_type
=
'application/json'
parsers
=
(
JSONParser
(),
)
parsers
=
(
JSONParser
,
)
request
=
self
.
build_request
(
'post'
,
'/'
,
content
,
content_type
=
content_type
,
parsers
=
parsers
)
request
=
factory
.
post
(
'/'
,
content
,
content_type
=
content_type
,
parsers
=
parsers
)
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
POST
.
items
(),
[])
self
.
assertEqual
(
request
.
POST
.
items
(),
[])
def
test_accessing_post_after_data_for_overloaded_json
(
self
):
def
test_accessing_post_after_data_for_overloaded_json
(
self
):
"""Ensures request.POST can be accessed after request.DATA in overloaded json request"""
"""
from
django.utils
import
simplejson
as
json
Ensures request.POST can be accessed after request.DATA in overloaded
json request.
"""
data
=
{
'qwerty'
:
'uiop'
}
data
=
{
'qwerty'
:
'uiop'
}
content
=
json
.
dumps
(
data
)
content
=
json
.
dumps
(
data
)
content_type
=
'application/json'
content_type
=
'application/json'
parsers
=
(
JSONParser
(),
)
parsers
=
(
JSONParser
,
)
form_data
=
{
Request
.
_CONTENT_PARAM
:
content
,
form_data
=
{
Request
.
_CONTENT_PARAM
:
content
,
Request
.
_CONTENTTYPE_PARAM
:
content_type
}
Request
.
_CONTENTTYPE_PARAM
:
content_type
}
request
=
self
.
build_request
(
'post'
,
'/'
,
data
=
form_data
,
parsers
=
parsers
)
request
=
factory
.
post
(
'/'
,
form_data
,
parsers
=
parsers
)
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
POST
.
items
(),
form_data
.
items
())
self
.
assertEqual
(
request
.
POST
.
items
(),
form_data
.
items
())
def
test_accessing_data_after_post_form
(
self
):
def
test_accessing_data_after_post_form
(
self
):
"""Ensures request.DATA can be accessed after request.POST in form request"""
"""
form_data
=
{
'qwerty'
:
'uiop'
}
Ensures request.DATA can be accessed after request.POST in
form request.
"""
data
=
{
'qwerty'
:
'uiop'
}
parsers
=
(
FormParser
,
MultiPartParser
)
parsers
=
(
FormParser
,
MultiPartParser
)
request
=
self
.
build_request
(
'post'
,
'/'
,
data
=
form_
data
,
parsers
=
parsers
)
request
=
factory
.
post
(
'/'
,
data
,
parsers
=
parsers
)
self
.
assertEqual
(
request
.
POST
.
items
(),
form_
data
.
items
())
self
.
assertEqual
(
request
.
POST
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
form_
data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
def
test_accessing_data_after_post_for_json
(
self
):
def
test_accessing_data_after_post_for_json
(
self
):
"""Ensures request.DATA can be accessed after request.POST in json request"""
"""
from
django.utils
import
simplejson
as
json
Ensures request.DATA can be accessed after request.POST in
json request.
"""
data
=
{
'qwerty'
:
'uiop'
}
data
=
{
'qwerty'
:
'uiop'
}
content
=
json
.
dumps
(
data
)
content
=
json
.
dumps
(
data
)
content_type
=
'application/json'
content_type
=
'application/json'
parsers
=
(
JSONParser
(),)
parsers
=
(
JSONParser
,
)
request
=
factory
.
post
(
'/'
,
content
,
content_type
=
content_type
,
request
=
self
.
build_request
(
'post'
,
'/'
,
content
,
content_type
=
content_type
,
parsers
=
parsers
)
parsers
=
parsers
)
post_items
=
request
.
POST
.
items
()
self
.
assertEqual
(
request
.
POST
.
items
(),
[])
self
.
assertEqual
(
len
(
post_items
),
1
)
self
.
assertEqual
(
len
(
post_items
[
0
]),
2
)
self
.
assertEqual
(
post_items
[
0
][
0
],
content
)
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
def
test_accessing_data_after_post_for_overloaded_json
(
self
):
def
test_accessing_data_after_post_for_overloaded_json
(
self
):
"""Ensures request.DATA can be accessed after request.POST in overloaded json request"""
"""
from
django.utils
import
simplejson
as
json
Ensures request.DATA can be accessed after request.POST in overloaded
json request
"""
data
=
{
'qwerty'
:
'uiop'
}
data
=
{
'qwerty'
:
'uiop'
}
content
=
json
.
dumps
(
data
)
content
=
json
.
dumps
(
data
)
content_type
=
'application/json'
content_type
=
'application/json'
parsers
=
(
JSONParser
(),
)
parsers
=
(
JSONParser
,
)
form_data
=
{
Request
.
_CONTENT_PARAM
:
content
,
form_data
=
{
Request
.
_CONTENT_PARAM
:
content
,
Request
.
_CONTENTTYPE_PARAM
:
content_type
}
Request
.
_CONTENTTYPE_PARAM
:
content_type
}
request
=
self
.
build_request
(
'post'
,
'/'
,
data
=
form_data
,
parsers
=
parsers
)
request
=
factory
.
post
(
'/'
,
form_data
,
parsers
=
parsers
)
self
.
assertEqual
(
request
.
POST
.
items
(),
form_data
.
items
())
self
.
assertEqual
(
request
.
POST
.
items
(),
form_data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
self
.
assertEqual
(
request
.
DATA
.
items
(),
data
.
items
())
class
MockView
(
View
):
class
MockView
(
View
):
authentication
=
(
UserLoggedInAuthentication
,)
authentication
=
(
UserLoggedInAuthentication
,)
def
post
(
self
,
request
):
def
post
(
self
,
request
):
if
request
.
POST
.
get
(
'example'
)
is
not
None
:
if
request
.
POST
.
get
(
'example'
)
is
not
None
:
return
Response
(
status
=
status
.
HTTP_200_OK
)
return
Response
(
status
=
status
.
HTTP_200_OK
)
...
@@ -223,17 +232,19 @@ class TestContentParsingWithAuthentication(TestCase):
...
@@ -223,17 +232,19 @@ class TestContentParsingWithAuthentication(TestCase):
self
.
email
=
'lennon@thebeatles.com'
self
.
email
=
'lennon@thebeatles.com'
self
.
password
=
'password'
self
.
password
=
'password'
self
.
user
=
User
.
objects
.
create_user
(
self
.
username
,
self
.
email
,
self
.
password
)
self
.
user
=
User
.
objects
.
create_user
(
self
.
username
,
self
.
email
,
self
.
password
)
self
.
req
=
RequestFactory
()
def
test_user_logged_in_authentication_has_post_when_not_logged_in
(
self
):
def
test_user_logged_in_authentication_has_POST_when_not_logged_in
(
self
):
"""Ensures request.POST exists after UserLoggedInAuthentication when user doesn't log in"""
"""
Ensures request.POST exists after UserLoggedInAuthentication when user
doesn't log in.
"""
content
=
{
'example'
:
'example'
}
content
=
{
'example'
:
'example'
}
response
=
self
.
client
.
post
(
'/'
,
content
)
response
=
self
.
client
.
post
(
'/'
,
content
)
self
.
assertEqual
(
status
.
HTTP_200_OK
,
response
.
status_code
,
"POST data is malformed"
)
self
.
assertEqual
(
status
.
HTTP_200_OK
,
response
.
status_code
)
response
=
self
.
csrf_client
.
post
(
'/'
,
content
)
response
=
self
.
csrf_client
.
post
(
'/'
,
content
)
self
.
assertEqual
(
status
.
HTTP_200_OK
,
response
.
status_code
,
"POST data is malformed"
)
self
.
assertEqual
(
status
.
HTTP_200_OK
,
response
.
status_code
)
# def test_user_logged_in_authentication_has_post_when_logged_in(self):
# def test_user_logged_in_authentication_has_post_when_logged_in(self):
# """Ensures request.POST exists after UserLoggedInAuthentication when user does log in"""
# """Ensures request.POST exists after UserLoggedInAuthentication when user does log in"""
...
...
djangorestframework/tests/response.py
View file @
1cde31c8
import
json
import
json
import
unittest
import
unittest
from
django.conf.urls.defaults
import
patterns
,
url
from
django.conf.urls.defaults
import
patterns
,
url
,
include
from
django.test
import
TestCase
from
django.test
import
TestCase
from
djangorestframework.response
import
Response
,
ImmediateResponse
from
djangorestframework.response
import
Response
,
ImmediateResponse
from
djangorestframework.mixins
import
ResponseMixin
from
djangorestframework.views
import
View
from
djangorestframework.views
import
View
from
djangorestframework.compat
import
View
as
DjangoView
from
djangorestframework.renderers
import
BaseRenderer
,
DEFAULT_RENDERERS
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework
import
status
from
djangorestframework
import
status
from
djangorestframework.renderers
import
BaseRenderer
,
JSONRenderer
,
YAMLRenderer
,
\
from
djangorestframework.renderers
import
(
XMLRenderer
,
JSONPRenderer
,
DocumentingHTMLRenderer
BaseRenderer
,
JSONRenderer
,
DocumentingHTMLRenderer
,
DEFAULT_RENDERERS
)
class
TestResponseDetermineRenderer
(
TestCase
):
class
TestResponseDetermineRenderer
(
TestCase
):
...
@@ -20,7 +21,7 @@ class TestResponseDetermineRenderer(TestCase):
...
@@ -20,7 +21,7 @@ class TestResponseDetermineRenderer(TestCase):
def
get_response
(
self
,
url
=
''
,
accept_list
=
[],
renderers
=
[]):
def
get_response
(
self
,
url
=
''
,
accept_list
=
[],
renderers
=
[]):
kwargs
=
{}
kwargs
=
{}
if
accept_list
is
not
None
:
if
accept_list
is
not
None
:
kwargs
[
'HTTP_ACCEPT'
]
=
HTTP_ACCEPT
=
','
.
join
(
accept_list
)
kwargs
[
'HTTP_ACCEPT'
]
=
','
.
join
(
accept_list
)
request
=
RequestFactory
()
.
get
(
url
,
**
kwargs
)
request
=
RequestFactory
()
.
get
(
url
,
**
kwargs
)
return
Response
(
request
=
request
,
renderers
=
renderers
)
return
Response
(
request
=
request
,
renderers
=
renderers
)
...
@@ -134,34 +135,33 @@ class RendererB(BaseRenderer):
...
@@ -134,34 +135,33 @@ class RendererB(BaseRenderer):
return
RENDERER_B_SERIALIZER
(
obj
)
return
RENDERER_B_SERIALIZER
(
obj
)
class
MockView
(
ResponseMixin
,
Django
View
):
class
MockView
(
View
):
renderer
_classe
s
=
(
RendererA
,
RendererB
)
renderers
=
(
RendererA
,
RendererB
)
def
get
(
self
,
request
,
**
kwargs
):
def
get
(
self
,
request
,
**
kwargs
):
response
=
Response
(
DUMMYCONTENT
,
status
=
DUMMYSTATUS
)
return
Response
(
DUMMYCONTENT
,
status
=
DUMMYSTATUS
)
self
.
response
=
self
.
prepare_response
(
response
)
return
self
.
response
class
HTMLView
(
View
):
class
HTMLView
(
View
):
renderer
_classe
s
=
(
DocumentingHTMLRenderer
,
)
renderers
=
(
DocumentingHTMLRenderer
,
)
def
get
(
self
,
request
,
**
kwargs
):
def
get
(
self
,
request
,
**
kwargs
):
return
Response
(
'text'
)
return
Response
(
'text'
)
class
HTMLView1
(
View
):
class
HTMLView1
(
View
):
renderer
_classe
s
=
(
DocumentingHTMLRenderer
,
JSONRenderer
)
renderers
=
(
DocumentingHTMLRenderer
,
JSONRenderer
)
def
get
(
self
,
request
,
**
kwargs
):
def
get
(
self
,
request
,
**
kwargs
):
return
Response
(
'text'
)
return
Response
(
'text'
)
urlpatterns
=
patterns
(
''
,
urlpatterns
=
patterns
(
''
,
url
(
r'^.*\.(?P<format>.+)$'
,
MockView
.
as_view
(
renderer
_classe
s
=
[
RendererA
,
RendererB
])),
url
(
r'^.*\.(?P<format>.+)$'
,
MockView
.
as_view
(
renderers
=
[
RendererA
,
RendererB
])),
url
(
r'^$'
,
MockView
.
as_view
(
renderer
_classe
s
=
[
RendererA
,
RendererB
])),
url
(
r'^$'
,
MockView
.
as_view
(
renderers
=
[
RendererA
,
RendererB
])),
url
(
r'^html$'
,
HTMLView
.
as_view
()),
url
(
r'^html$'
,
HTMLView
.
as_view
()),
url
(
r'^html1$'
,
HTMLView1
.
as_view
()),
url
(
r'^html1$'
,
HTMLView1
.
as_view
()),
url
(
r'^restframework'
,
include
(
'djangorestframework.urls'
,
namespace
=
'djangorestframework'
))
)
)
...
@@ -257,13 +257,6 @@ class RendererIntegrationTests(TestCase):
...
@@ -257,13 +257,6 @@ class RendererIntegrationTests(TestCase):
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
def
test_bla
(
self
):
resp
=
self
.
client
.
get
(
'/?format=formatb'
,
HTTP_ACCEPT
=
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
)
self
.
assertEquals
(
resp
[
'Content-Type'
],
RendererB
.
media_type
)
self
.
assertEquals
(
resp
.
content
,
RENDERER_B_SERIALIZER
(
DUMMYCONTENT
))
self
.
assertEquals
(
resp
.
status_code
,
DUMMYSTATUS
)
class
Issue122Tests
(
TestCase
):
class
Issue122Tests
(
TestCase
):
"""
"""
...
@@ -275,10 +268,10 @@ class Issue122Tests(TestCase):
...
@@ -275,10 +268,10 @@ class Issue122Tests(TestCase):
"""
"""
Test if no infinite recursion occurs.
Test if no infinite recursion occurs.
"""
"""
resp
=
self
.
client
.
get
(
'/html'
)
self
.
client
.
get
(
'/html'
)
def
test_html_renderer_is_first
(
self
):
def
test_html_renderer_is_first
(
self
):
"""
"""
Test if no infinite recursion occurs.
Test if no infinite recursion occurs.
"""
"""
resp
=
self
.
client
.
get
(
'/html1'
)
self
.
client
.
get
(
'/html1'
)
djangorestframework/tests/reverse.py
View file @
1cde31c8
...
@@ -16,7 +16,8 @@ class MyView(View):
...
@@ -16,7 +16,8 @@ class MyView(View):
renderers
=
(
JSONRenderer
,
)
renderers
=
(
JSONRenderer
,
)
def
get
(
self
,
request
):
def
get
(
self
,
request
):
return
Response
(
reverse
(
'another'
,
request
))
return
Response
(
reverse
(
'myview'
,
request
=
request
))
urlpatterns
=
patterns
(
''
,
urlpatterns
=
patterns
(
''
,
url
(
r'^myview$'
,
MyView
.
as_view
(),
name
=
'myview'
),
url
(
r'^myview$'
,
MyView
.
as_view
(),
name
=
'myview'
),
...
...
djangorestframework/tests/views.py
View file @
1cde31c8
from
django.conf.urls.defaults
import
patterns
,
url
from
django.core.urlresolvers
import
reverse
from
django.conf.urls.defaults
import
patterns
,
url
,
include
from
django.http
import
HttpResponse
from
django.http
import
HttpResponse
from
django.test
import
TestCase
from
django.test
import
TestCase
from
django.test
import
Client
from
django
import
forms
from
django
import
forms
from
django.db
import
models
from
django.db
import
models
from
django.utils
import
simplejson
as
json
from
djangorestframework.views
import
View
from
djangorestframework.parsers
import
JSONParser
from
djangorestframework.resources
import
ModelResource
from
djangorestframework.resources
import
ModelResource
from
djangorestframework.views
import
ListOrCreateModelView
,
InstanceModelView
from
djangorestframework.views
import
(
View
,
from
StringIO
import
StringIO
ListOrCreateModelView
,
InstanceModelView
)
class
MockView
(
View
):
class
MockView
(
View
):
...
@@ -24,6 +25,7 @@ class MockViewFinal(View):
...
@@ -24,6 +25,7 @@ class MockViewFinal(View):
def
final
(
self
,
request
,
response
,
*
args
,
**
kwargs
):
def
final
(
self
,
request
,
response
,
*
args
,
**
kwargs
):
return
HttpResponse
(
'{"test": "passed"}'
,
content_type
=
"application/json"
)
return
HttpResponse
(
'{"test": "passed"}'
,
content_type
=
"application/json"
)
class
ResourceMockView
(
View
):
class
ResourceMockView
(
View
):
"""This is a resource-based mock view"""
"""This is a resource-based mock view"""
...
@@ -34,6 +36,7 @@ class ResourceMockView(View):
...
@@ -34,6 +36,7 @@ class ResourceMockView(View):
form
=
MockForm
form
=
MockForm
class
MockResource
(
ModelResource
):
class
MockResource
(
ModelResource
):
"""This is a mock model-based resource"""
"""This is a mock model-based resource"""
...
@@ -45,16 +48,16 @@ class MockResource(ModelResource):
...
@@ -45,16 +48,16 @@ class MockResource(ModelResource):
model
=
MockResourceModel
model
=
MockResourceModel
fields
=
(
'foo'
,
'bar'
,
'baz'
)
fields
=
(
'foo'
,
'bar'
,
'baz'
)
urlpatterns
=
patterns
(
'djangorestframework.utils.staticviews'
,
urlpatterns
=
patterns
(
''
,
url
(
r'^accounts/login$'
,
'api_login'
),
url
(
r'^accounts/logout$'
,
'api_logout'
),
url
(
r'^mock/$'
,
MockView
.
as_view
()),
url
(
r'^mock/$'
,
MockView
.
as_view
()),
url
(
r'^mock/final/$'
,
MockViewFinal
.
as_view
()),
url
(
r'^mock/final/$'
,
MockViewFinal
.
as_view
()),
url
(
r'^resourcemock/$'
,
ResourceMockView
.
as_view
()),
url
(
r'^resourcemock/$'
,
ResourceMockView
.
as_view
()),
url
(
r'^model/$'
,
ListOrCreateModelView
.
as_view
(
resource
=
MockResource
)),
url
(
r'^model/$'
,
ListOrCreateModelView
.
as_view
(
resource
=
MockResource
)),
url
(
r'^model/(?P<pk>[^/]+)/$'
,
InstanceModelView
.
as_view
(
resource
=
MockResource
)),
url
(
r'^model/(?P<pk>[^/]+)/$'
,
InstanceModelView
.
as_view
(
resource
=
MockResource
)),
url
(
r'^restframework/'
,
include
(
'djangorestframework.urls'
,
namespace
=
'djangorestframework'
)),
)
)
class
BaseViewTests
(
TestCase
):
class
BaseViewTests
(
TestCase
):
"""Test the base view class of djangorestframework"""
"""Test the base view class of djangorestframework"""
urls
=
'djangorestframework.tests.views'
urls
=
'djangorestframework.tests.views'
...
@@ -62,8 +65,7 @@ class BaseViewTests(TestCase):
...
@@ -62,8 +65,7 @@ class BaseViewTests(TestCase):
def
test_view_call_final
(
self
):
def
test_view_call_final
(
self
):
response
=
self
.
client
.
options
(
'/mock/final/'
)
response
=
self
.
client
.
options
(
'/mock/final/'
)
self
.
assertEqual
(
response
[
'Content-Type'
]
.
split
(
';'
)[
0
],
"application/json"
)
self
.
assertEqual
(
response
[
'Content-Type'
]
.
split
(
';'
)[
0
],
"application/json"
)
parser
=
JSONParser
(
None
)
data
=
json
.
loads
(
response
.
content
)
(
data
,
files
)
=
parser
.
parse
(
StringIO
(
response
.
content
))
self
.
assertEqual
(
data
[
'test'
],
'passed'
)
self
.
assertEqual
(
data
[
'test'
],
'passed'
)
def
test_options_method_simple_view
(
self
):
def
test_options_method_simple_view
(
self
):
...
@@ -77,9 +79,9 @@ class BaseViewTests(TestCase):
...
@@ -77,9 +79,9 @@ class BaseViewTests(TestCase):
self
.
_verify_options_response
(
response
,
self
.
_verify_options_response
(
response
,
name
=
'Resource Mock'
,
name
=
'Resource Mock'
,
description
=
'This is a resource-based mock view'
,
description
=
'This is a resource-based mock view'
,
fields
=
{
'foo'
:
'BooleanField'
,
fields
=
{
'foo'
:
'BooleanField'
,
'bar'
:
'IntegerField'
,
'bar'
:
'IntegerField'
,
'baz'
:
'CharField'
,
'baz'
:
'CharField'
,
})
})
def
test_options_method_model_resource_list_view
(
self
):
def
test_options_method_model_resource_list_view
(
self
):
...
@@ -87,9 +89,9 @@ class BaseViewTests(TestCase):
...
@@ -87,9 +89,9 @@ class BaseViewTests(TestCase):
self
.
_verify_options_response
(
response
,
self
.
_verify_options_response
(
response
,
name
=
'Mock List'
,
name
=
'Mock List'
,
description
=
'This is a mock model-based resource'
,
description
=
'This is a mock model-based resource'
,
fields
=
{
'foo'
:
'BooleanField'
,
fields
=
{
'foo'
:
'BooleanField'
,
'bar'
:
'IntegerField'
,
'bar'
:
'IntegerField'
,
'baz'
:
'CharField'
,
'baz'
:
'CharField'
,
})
})
def
test_options_method_model_resource_detail_view
(
self
):
def
test_options_method_model_resource_detail_view
(
self
):
...
@@ -97,17 +99,16 @@ class BaseViewTests(TestCase):
...
@@ -97,17 +99,16 @@ class BaseViewTests(TestCase):
self
.
_verify_options_response
(
response
,
self
.
_verify_options_response
(
response
,
name
=
'Mock Instance'
,
name
=
'Mock Instance'
,
description
=
'This is a mock model-based resource'
,
description
=
'This is a mock model-based resource'
,
fields
=
{
'foo'
:
'BooleanField'
,
fields
=
{
'foo'
:
'BooleanField'
,
'bar'
:
'IntegerField'
,
'bar'
:
'IntegerField'
,
'baz'
:
'CharField'
,
'baz'
:
'CharField'
,
})
})
def
_verify_options_response
(
self
,
response
,
name
,
description
,
fields
=
None
,
status
=
200
,
def
_verify_options_response
(
self
,
response
,
name
,
description
,
fields
=
None
,
status
=
200
,
mime_type
=
'application/json'
):
mime_type
=
'application/json'
):
self
.
assertEqual
(
response
.
status_code
,
status
)
self
.
assertEqual
(
response
.
status_code
,
status
)
self
.
assertEqual
(
response
[
'Content-Type'
]
.
split
(
';'
)[
0
],
mime_type
)
self
.
assertEqual
(
response
[
'Content-Type'
]
.
split
(
';'
)[
0
],
mime_type
)
parser
=
JSONParser
(
None
)
data
=
json
.
loads
(
response
.
content
)
(
data
,
files
)
=
parser
.
parse
(
StringIO
(
response
.
content
))
self
.
assertTrue
(
'application/json'
in
data
[
'renders'
])
self
.
assertTrue
(
'application/json'
in
data
[
'renders'
])
self
.
assertEqual
(
name
,
data
[
'name'
])
self
.
assertEqual
(
name
,
data
[
'name'
])
self
.
assertEqual
(
description
,
data
[
'description'
])
self
.
assertEqual
(
description
,
data
[
'description'
])
...
@@ -123,15 +124,12 @@ class ExtraViewsTests(TestCase):
...
@@ -123,15 +124,12 @@ class ExtraViewsTests(TestCase):
def
test_login_view
(
self
):
def
test_login_view
(
self
):
"""Ensure the login view exists"""
"""Ensure the login view exists"""
response
=
self
.
client
.
get
(
'/accounts/login'
)
response
=
self
.
client
.
get
(
reverse
(
'djangorestframework:login'
)
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
[
'Content-Type'
]
.
split
(
';'
)[
0
],
'text/html'
)
self
.
assertEqual
(
response
[
'Content-Type'
]
.
split
(
';'
)[
0
],
'text/html'
)
def
test_logout_view
(
self
):
def
test_logout_view
(
self
):
"""Ensure the logout view exists"""
"""Ensure the logout view exists"""
response
=
self
.
client
.
get
(
'/accounts/logout'
)
response
=
self
.
client
.
get
(
reverse
(
'djangorestframework:logout'
)
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
[
'Content-Type'
]
.
split
(
';'
)[
0
],
'text/html'
)
self
.
assertEqual
(
response
[
'Content-Type'
]
.
split
(
';'
)[
0
],
'text/html'
)
# TODO: Add login/logout behaviour tests
djangorestframework/urls.py
View file @
1cde31c8
from
django.conf.urls.defaults
import
patterns
from
django.conf.urls.defaults
import
patterns
,
url
urlpatterns
=
patterns
(
'djangorestframework.utils.staticviews'
,
(
r'^accounts/login/$'
,
'api_login'
),
template_name
=
{
'template_name'
:
'djangorestframework/login.html'
}
(
r'^accounts/logout/$'
,
'api_logout'
),
urlpatterns
=
patterns
(
'django.contrib.auth.views'
,
url
(
r'^login/$'
,
'login'
,
template_name
,
name
=
'login'
),
url
(
r'^logout/$'
,
'logout'
,
template_name
,
name
=
'logout'
),
)
)
djangorestframework/utils/__init__.py
View file @
1cde31c8
import
django
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
django.core.urlresolvers
import
resolve
,
reverse
as
django_reverse
from
django.core.urlresolvers
import
resolve
from
django.conf
import
settings
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
#def admin_media_prefix(request):
# """Adds the ADMIN_MEDIA_PREFIX to the request context."""
# return {'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX}
from
mediatypes
import
media_type_matches
,
is_form_media_type
from
mediatypes
import
add_media_type_param
,
get_media_type_params
,
order_by_precedence
MSIE_USER_AGENT_REGEX
=
re
.
compile
(
r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )'
)
MSIE_USER_AGENT_REGEX
=
re
.
compile
(
r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )'
)
def
as_tuple
(
obj
):
def
as_tuple
(
obj
):
"""
"""
Given an object which may be a list/tuple, another object, or None,
Given an object which may be a list/tuple, another object, or None,
...
@@ -49,45 +43,6 @@ def url_resolves(url):
...
@@ -49,45 +43,6 @@ def url_resolves(url):
return
True
return
True
def
allowed_methods
(
view
):
"""
Return the list of uppercased allowed HTTP methods on `view`.
"""
return
[
method
.
upper
()
for
method
in
view
.
http_method_names
if
hasattr
(
view
,
method
)]
# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
#class object_dict(dict):
# """object view of dict, you can
# >>> a = object_dict()
# >>> a.fish = 'fish'
# >>> a['fish']
# 'fish'
# >>> a['water'] = 'water'
# >>> a.water
# 'water'
# >>> a.test = {'value': 1}
# >>> a.test2 = object_dict({'name': 'test2', 'value': 2})
# >>> a.test, a.test2.name, a.test2.value
# (1, 'test2', 2)
# """
# def __init__(self, initd=None):
# if initd is None:
# initd = {}
# dict.__init__(self, initd)
#
# def __getattr__(self, item):
# d = self.__getitem__(item)
# # if value is the only key in object, you can omit it
# if isinstance(d, dict) and 'value' in d and len(d) == 1:
# return d['value']
# else:
# return d
#
# def __setattr__(self, item, value):
# self.__setitem__(item, value)
# From xml2dict
# From xml2dict
class
XML2Dict
(
object
):
class
XML2Dict
(
object
):
...
@@ -99,8 +54,8 @@ class XML2Dict(object):
...
@@ -99,8 +54,8 @@ class XML2Dict(object):
# Save attrs and text, hope there will not be a child with same name
# Save attrs and text, hope there will not be a child with same name
if
node
.
text
:
if
node
.
text
:
node_tree
=
node
.
text
node_tree
=
node
.
text
for
(
k
,
v
)
in
node
.
attrib
.
items
():
for
(
k
,
v
)
in
node
.
attrib
.
items
():
k
,
v
=
self
.
_namespace_split
(
k
,
v
)
k
,
v
=
self
.
_namespace_split
(
k
,
v
)
node_tree
[
k
]
=
v
node_tree
[
k
]
=
v
#Save childrens
#Save childrens
for
child
in
node
.
getchildren
():
for
child
in
node
.
getchildren
():
...
@@ -116,7 +71,6 @@ class XML2Dict(object):
...
@@ -116,7 +71,6 @@ class XML2Dict(object):
return
node_tree
return
node_tree
def
_namespace_split
(
self
,
tag
,
value
):
def
_namespace_split
(
self
,
tag
,
value
):
"""
"""
Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
...
@@ -179,23 +133,41 @@ class XMLRenderer():
...
@@ -179,23 +133,41 @@ class XMLRenderer():
xml
.
endDocument
()
xml
.
endDocument
()
return
stream
.
getvalue
()
return
stream
.
getvalue
()
def
dict2xml
(
input
):
def
dict2xml
(
input
):
return
XMLRenderer
()
.
dict2xml
(
input
)
return
XMLRenderer
()
.
dict2xml
(
input
)
def
reverse
(
viewname
,
request
,
*
args
,
**
kwargs
):
class
RequestFactory
(
DjangoRequestFactory
):
"""
Do the same as :py:func:`django.core.urlresolvers.reverse` but using
*request* to build a fully qualified URL.
"""
return
request
.
build_absolute_uri
(
django_reverse
(
viewname
,
*
args
,
**
kwargs
))
if
django
.
VERSION
>=
(
1
,
4
):
from
django.core.urlresolvers
import
reverse_lazy
as
django_reverse_lazy
def
reverse_lazy
(
viewname
,
request
,
*
args
,
**
kwargs
):
"""
"""
Do the same as :py:func:`django.core.urlresolvers.reverse_lazy` but using
Replicate RequestFactory, but return Request, not HttpRequest.
*request* to build a fully qualified URL.
"""
"""
return
request
.
build_absolute_uri
(
django_reverse_lazy
(
viewname
,
*
args
,
**
kwargs
))
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/utils/staticviews.py
deleted
100644 → 0
View file @
5fd4c639
from
django.contrib.auth.views
import
*
from
django.conf
import
settings
from
django.http
import
HttpResponse
from
django.shortcuts
import
render_to_response
from
django.template
import
RequestContext
import
base64
# BLERGH
# Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS
# to add ADMIN_MEDIA_PREFIX to the RequestContext. I don't like this but really really want users to not have to
# be making settings changes in order to accomodate django-rest-framework
@csrf_protect
@never_cache
def
api_login
(
request
,
template_name
=
'djangorestframework/login.html'
,
redirect_field_name
=
REDIRECT_FIELD_NAME
,
authentication_form
=
AuthenticationForm
):
"""Displays the login form and handles the login action."""
redirect_to
=
request
.
REQUEST
.
get
(
redirect_field_name
,
''
)
if
request
.
method
==
"POST"
:
form
=
authentication_form
(
data
=
request
.
POST
)
if
form
.
is_valid
():
# Light security check -- make sure redirect_to isn't garbage.
if
not
redirect_to
or
' '
in
redirect_to
:
redirect_to
=
settings
.
LOGIN_REDIRECT_URL
# Heavier security check -- redirects to http://example.com should
# not be allowed, but things like /view/?param=http://example.com
# should be allowed. This regex checks if there is a '//' *before* a
# question mark.
elif
'//'
in
redirect_to
and
re
.
match
(
r'[^\?]*//'
,
redirect_to
):
redirect_to
=
settings
.
LOGIN_REDIRECT_URL
# Okay, security checks complete. Log the user in.
auth_login
(
request
,
form
.
get_user
())
if
request
.
session
.
test_cookie_worked
():
request
.
session
.
delete_test_cookie
()
return
HttpResponseRedirect
(
redirect_to
)
else
:
form
=
authentication_form
(
request
)
request
.
session
.
set_test_cookie
()
#current_site = get_current_site(request)
return
render_to_response
(
template_name
,
{
'form'
:
form
,
redirect_field_name
:
redirect_to
,
#'site': current_site,
#'site_name': current_site.name,
'ADMIN_MEDIA_PREFIX'
:
settings
.
ADMIN_MEDIA_PREFIX
,
},
context_instance
=
RequestContext
(
request
))
def
api_logout
(
request
,
next_page
=
None
,
template_name
=
'djangorestframework/login.html'
,
redirect_field_name
=
REDIRECT_FIELD_NAME
):
return
logout
(
request
,
next_page
,
template_name
,
redirect_field_name
)
djangorestframework/views.py
View file @
1cde31c8
...
@@ -6,15 +6,13 @@ By setting or modifying class attributes on your view, you change it's predefine
...
@@ -6,15 +6,13 @@ By setting or modifying class attributes on your view, you change it's predefine
"""
"""
import
re
import
re
from
django.core.urlresolvers
import
set_script_prefix
,
get_script_prefix
from
django.utils.html
import
escape
from
django.utils.html
import
escape
from
django.utils.safestring
import
mark_safe
from
django.utils.safestring
import
mark_safe
from
django.views.decorators.csrf
import
csrf_exempt
from
django.views.decorators.csrf
import
csrf_exempt
from
djangorestframework.compat
import
View
as
DjangoView
,
apply_markdown
from
djangorestframework.compat
import
View
as
DjangoView
,
apply_markdown
from
djangorestframework.response
import
ImmediateResponse
from
djangorestframework.response
import
Response
,
ImmediateResponse
from
djangorestframework.mixins
import
*
from
djangorestframework.mixins
import
*
from
djangorestframework.utils
import
allowed_methods
from
djangorestframework
import
resources
,
renderers
,
parsers
,
authentication
,
permissions
,
status
from
djangorestframework
import
resources
,
renderers
,
parsers
,
authentication
,
permissions
,
status
...
@@ -81,12 +79,12 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
...
@@ -81,12 +79,12 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
or `None` to use default behaviour.
or `None` to use default behaviour.
"""
"""
renderer
_classe
s
=
renderers
.
DEFAULT_RENDERERS
renderers
=
renderers
.
DEFAULT_RENDERERS
"""
"""
List of renderer classes the resource can serialize the response with, ordered by preference.
List of renderer classes the resource can serialize the response with, ordered by preference.
"""
"""
parser
_classe
s
=
parsers
.
DEFAULT_PARSERS
parsers
=
parsers
.
DEFAULT_PARSERS
"""
"""
List of parser classes the resource can parse the request with.
List of parser classes the resource can parse the request with.
"""
"""
...
@@ -118,7 +116,15 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
...
@@ -118,7 +116,15 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
"""
"""
Return the list of allowed HTTP methods, uppercased.
Return the list of allowed HTTP methods, uppercased.
"""
"""
return
allowed_methods
(
self
)
return
[
method
.
upper
()
for
method
in
self
.
http_method_names
if
hasattr
(
self
,
method
)]
@property
def
default_response_headers
(
self
):
return
{
'Allow'
:
', '
.
join
(
self
.
allowed_methods
),
'Vary'
:
'Authenticate, Accept'
}
def
get_name
(
self
):
def
get_name
(
self
):
"""
"""
...
@@ -183,32 +189,35 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
...
@@ -183,32 +189,35 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
def
initial
(
self
,
request
,
*
args
,
**
kargs
):
def
initial
(
self
,
request
,
*
args
,
**
kargs
):
"""
"""
Returns an `HttpRequest`. This method is a hook for any code that needs to run
This method is a hook for any code that needs to run prior to
prior to
anything else.
anything else.
Required if you want to do things like set `request.upload_handlers`
before
Required if you want to do things like set `request.upload_handlers`
the authentication and dispatch handling is run.
before
the authentication and dispatch handling is run.
"""
"""
pass
pass
def
final
(
self
,
request
,
response
,
*
args
,
**
kargs
):
def
final
(
self
,
request
,
response
,
*
args
,
**
kargs
):
"""
"""
Returns an `HttpResponse`. This method is a hook for any code that needs to run
This method is a hook for any code that needs to run after everything
after everything else in the view.
else in the view.
Returns the final response object.
"""
"""
# Always add these headers.
response
.
view
=
self
response
[
'Allow'
]
=
', '
.
join
(
allowed_methods
(
self
))
response
.
request
=
request
# sample to allow caching using Vary http header
response
.
renderers
=
self
.
renderers
response
[
'Vary'
]
=
'Authenticate, Accept'
for
key
,
value
in
self
.
headers
.
items
():
response
[
key
]
=
value
return
response
return
response
# Note: session based authentication is explicitly CSRF validated,
# Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt.
# all other authentication is CSRF exempt.
@csrf_exempt
@csrf_exempt
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
request
=
self
.
create_request
(
request
)
request
=
self
.
create_request
(
request
)
self
.
request
=
request
self
.
args
=
args
self
.
args
=
args
self
.
kwargs
=
kwargs
self
.
kwargs
=
kwargs
self
.
headers
=
self
.
default_response_headers
try
:
try
:
self
.
initial
(
request
,
*
args
,
**
kwargs
)
self
.
initial
(
request
,
*
args
,
**
kwargs
)
...
@@ -222,26 +231,17 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
...
@@ -222,26 +231,17 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
else
:
else
:
handler
=
self
.
http_method_not_allowed
handler
=
self
.
http_method_not_allowed
# TODO: should we enforce HttpResponse, like Django does ?
response
=
handler
(
request
,
*
args
,
**
kwargs
)
response
=
handler
(
request
,
*
args
,
**
kwargs
)
# Prepare response for the response cycle.
if
isinstance
(
response
,
Response
):
self
.
response
=
response
=
self
.
prepare_response
(
response
)
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
# TODO: ugly hack to handle both HttpResponse and Response.
if
hasattr
(
response
,
'raw_content'
):
response
.
raw_content
=
self
.
filter_response
(
response
.
raw_content
)
response
.
raw_content
=
self
.
filter_response
(
response
.
raw_content
)
else
:
response
.
content
=
self
.
filter_response
(
response
.
content
)
except
ImmediateResponse
,
response
:
except
ImmediateResponse
,
exc
:
# Prepare response for the response cycle.
response
=
exc
.
response
self
.
response
=
response
=
self
.
prepare_response
(
response
)
# `final` is the last opportunity to temper with the response, or even
self
.
response
=
self
.
final
(
request
,
response
,
*
args
,
**
kwargs
)
# completely replace it.
return
self
.
response
return
self
.
final
(
request
,
response
,
*
args
,
**
kwargs
)
def
options
(
self
,
request
,
*
args
,
**
kwargs
):
def
options
(
self
,
request
,
*
args
,
**
kwargs
):
content
=
{
content
=
{
...
@@ -266,7 +266,7 @@ class ModelView(View):
...
@@ -266,7 +266,7 @@ class ModelView(View):
resource
=
resources
.
ModelResource
resource
=
resources
.
ModelResource
class
InstanceModelView
(
InstanceMixin
,
ReadModelMixin
,
UpdateModelMixin
,
DeleteModelMixin
,
ModelView
):
class
InstanceModelView
(
ReadModelMixin
,
UpdateModelMixin
,
DeleteModelMixin
,
ModelView
):
"""
"""
A view which provides default operations for read/update/delete against a model instance.
A view which provides default operations for read/update/delete against a model instance.
"""
"""
...
...
docs/howto/setup.rst
View file @
1cde31c8
...
@@ -49,19 +49,19 @@ YAML
...
@@ -49,19 +49,19 @@ YAML
YAML support is optional, and requires `PyYAML`_.
YAML support is optional, and requires `PyYAML`_.
Login / Logout
Login / Logout
--------------
--------------
Django REST framework includes login and logout views that are useful if
Django REST framework includes login and logout views that are needed if
you're using the self-documenting API::
you're using the self-documenting API.
Make sure you include the following in your `urlconf`::
from django.conf.urls.defaults import patterns
from django.conf.urls.defaults import patterns
, url
urlpatterns = patterns('djangorestframework.views',
urlpatterns = patterns('',
# Add your resources here
...
(r'^accounts/login/$', 'api_login'),
url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework'))
(r'^accounts/logout/$', 'api_logout'),
)
)
.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/
.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/
...
...
docs/index.rst
View file @
1cde31c8
...
@@ -64,6 +64,12 @@ To add Django REST framework to a Django project:
...
@@ -64,6 +64,12 @@ To add Django REST framework to a Django project:
* Ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``.
* Ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``.
* Add ``djangorestframework`` to your ``INSTALLED_APPS``.
* Add ``djangorestframework`` to your ``INSTALLED_APPS``.
* Add the following to your URLconf. (To include the REST framework Login/Logout views.)::
urlpatterns = patterns('',
...
url(r'
^
restframework
', include('
djangorestframework
.
urls
', namespace='
djangorestframework
'))
)
For more information on settings take a look at the :ref:`setup` section.
For more information on settings take a look at the :ref:`setup` section.
...
...
examples/blogpost/models.py
View file @
1cde31c8
...
@@ -2,6 +2,7 @@ from django.db import models
...
@@ -2,6 +2,7 @@ from django.db import models
from
django.template.defaultfilters
import
slugify
from
django.template.defaultfilters
import
slugify
import
uuid
import
uuid
def
uuid_str
():
def
uuid_str
():
return
str
(
uuid
.
uuid1
())
return
str
(
uuid
.
uuid1
())
...
@@ -14,6 +15,7 @@ RATING_CHOICES = ((0, 'Awful'),
...
@@ -14,6 +15,7 @@ RATING_CHOICES = ((0, 'Awful'),
MAX_POSTS
=
10
MAX_POSTS
=
10
class
BlogPost
(
models
.
Model
):
class
BlogPost
(
models
.
Model
):
key
=
models
.
CharField
(
primary_key
=
True
,
max_length
=
64
,
default
=
uuid_str
,
editable
=
False
)
key
=
models
.
CharField
(
primary_key
=
True
,
max_length
=
64
,
default
=
uuid_str
,
editable
=
False
)
title
=
models
.
CharField
(
max_length
=
128
)
title
=
models
.
CharField
(
max_length
=
128
)
...
@@ -37,4 +39,3 @@ class Comment(models.Model):
...
@@ -37,4 +39,3 @@ class Comment(models.Model):
comment
=
models
.
TextField
()
comment
=
models
.
TextField
()
rating
=
models
.
IntegerField
(
blank
=
True
,
null
=
True
,
choices
=
RATING_CHOICES
,
help_text
=
'How did you rate this post?'
)
rating
=
models
.
IntegerField
(
blank
=
True
,
null
=
True
,
choices
=
RATING_CHOICES
,
help_text
=
'How did you rate this post?'
)
created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
examples/blogpost/resources.py
View file @
1cde31c8
...
@@ -11,8 +11,15 @@ class BlogPostResource(ModelResource):
...
@@ -11,8 +11,15 @@ class BlogPostResource(ModelResource):
fields
=
(
'created'
,
'title'
,
'slug'
,
'content'
,
'url'
,
'comments'
)
fields
=
(
'created'
,
'title'
,
'slug'
,
'content'
,
'url'
,
'comments'
)
ordering
=
(
'-created'
,)
ordering
=
(
'-created'
,)
def
url
(
self
,
instance
):
return
reverse
(
'blog-post'
,
kwargs
=
{
'key'
:
instance
.
key
},
request
=
self
.
request
)
def
comments
(
self
,
instance
):
def
comments
(
self
,
instance
):
return
reverse
(
'comments'
,
request
,
kwargs
=
{
'blogpost'
:
instance
.
key
})
return
reverse
(
'comments'
,
kwargs
=
{
'blogpost'
:
instance
.
key
},
request
=
self
.
request
)
class
CommentResource
(
ModelResource
):
class
CommentResource
(
ModelResource
):
...
@@ -24,4 +31,6 @@ class CommentResource(ModelResource):
...
@@ -24,4 +31,6 @@ class CommentResource(ModelResource):
ordering
=
(
'-created'
,)
ordering
=
(
'-created'
,)
def
blogpost
(
self
,
instance
):
def
blogpost
(
self
,
instance
):
return
reverse
(
'blog-post'
,
request
,
kwargs
=
{
'key'
:
instance
.
blogpost
.
key
})
return
reverse
(
'blog-post'
,
kwargs
=
{
'key'
:
instance
.
blogpost
.
key
},
request
=
self
.
request
)
examples/mixin/urls.py
View file @
1cde31c8
...
@@ -10,11 +10,12 @@ from django.conf.urls.defaults import patterns, url
...
@@ -10,11 +10,12 @@ from django.conf.urls.defaults import patterns, url
class
ExampleView
(
ResponseMixin
,
View
):
class
ExampleView
(
ResponseMixin
,
View
):
"""An example view using Django 1.3's class based views.
"""An example view using Django 1.3's class based views.
Uses djangorestframework's RendererMixin to provide support for multiple output formats."""
Uses djangorestframework's RendererMixin to provide support for multiple output formats."""
renderer
_classe
s
=
DEFAULT_RENDERERS
renderers
=
DEFAULT_RENDERERS
def
get
(
self
,
request
):
def
get
(
self
,
request
):
url
=
reverse
(
'mixin-view'
,
request
)
response
=
Response
({
'description'
:
'Some example content'
,
response
=
Response
({
'description'
:
'Some example content'
,
'url'
:
reverse
(
'mixin-view'
,
request
)
},
status
=
200
)
'url'
:
url
},
status
=
200
)
self
.
response
=
self
.
prepare_response
(
response
)
self
.
response
=
self
.
prepare_response
(
response
)
return
self
.
response
return
self
.
response
...
@@ -22,4 +23,3 @@ class ExampleView(ResponseMixin, View):
...
@@ -22,4 +23,3 @@ class ExampleView(ResponseMixin, View):
urlpatterns
=
patterns
(
''
,
urlpatterns
=
patterns
(
''
,
url
(
r'^$'
,
ExampleView
.
as_view
(),
name
=
'mixin-view'
),
url
(
r'^$'
,
ExampleView
.
as_view
(),
name
=
'mixin-view'
),
)
)
examples/modelresourceexample/models.py
View file @
1cde31c8
...
@@ -2,6 +2,7 @@ from django.db import models
...
@@ -2,6 +2,7 @@ from django.db import models
MAX_INSTANCES
=
10
MAX_INSTANCES
=
10
class
MyModel
(
models
.
Model
):
class
MyModel
(
models
.
Model
):
foo
=
models
.
BooleanField
()
foo
=
models
.
BooleanField
()
bar
=
models
.
IntegerField
(
help_text
=
'Must be an integer.'
)
bar
=
models
.
IntegerField
(
help_text
=
'Must be an integer.'
)
...
@@ -15,5 +16,3 @@ class MyModel(models.Model):
...
@@ -15,5 +16,3 @@ class MyModel(models.Model):
super
(
MyModel
,
self
)
.
save
(
*
args
,
**
kwargs
)
super
(
MyModel
,
self
)
.
save
(
*
args
,
**
kwargs
)
while
MyModel
.
objects
.
all
()
.
count
()
>
MAX_INSTANCES
:
while
MyModel
.
objects
.
all
()
.
count
()
>
MAX_INSTANCES
:
MyModel
.
objects
.
all
()
.
order_by
(
'-created'
)[
0
]
.
delete
()
MyModel
.
objects
.
all
()
.
order_by
(
'-created'
)[
0
]
.
delete
()
examples/modelresourceexample/resources.py
View file @
1cde31c8
from
djangorestframework.resources
import
ModelResource
from
djangorestframework.resources
import
ModelResource
from
djangorestframework.reverse
import
reverse
from
modelresourceexample.models
import
MyModel
from
modelresourceexample.models
import
MyModel
class
MyModelResource
(
ModelResource
):
class
MyModelResource
(
ModelResource
):
model
=
MyModel
model
=
MyModel
fields
=
(
'foo'
,
'bar'
,
'baz'
,
'url'
)
fields
=
(
'foo'
,
'bar'
,
'baz'
,
'url'
)
ordering
=
(
'created'
,)
ordering
=
(
'created'
,)
def
url
(
self
,
instance
):
return
reverse
(
'model-resource-instance'
,
kwargs
=
{
'id'
:
instance
.
id
},
request
=
self
.
request
)
examples/modelresourceexample/urls.py
View file @
1cde31c8
...
@@ -2,7 +2,10 @@ from django.conf.urls.defaults import patterns, url
...
@@ -2,7 +2,10 @@ from django.conf.urls.defaults import patterns, url
from
djangorestframework.views
import
ListOrCreateModelView
,
InstanceModelView
from
djangorestframework.views
import
ListOrCreateModelView
,
InstanceModelView
from
modelresourceexample.resources
import
MyModelResource
from
modelresourceexample.resources
import
MyModelResource
my_model_list
=
ListOrCreateModelView
.
as_view
(
resource
=
MyModelResource
)
my_model_instance
=
InstanceModelView
.
as_view
(
resource
=
MyModelResource
)
urlpatterns
=
patterns
(
''
,
urlpatterns
=
patterns
(
''
,
url
(
r'^$'
,
ListOrCreateModelView
.
as_view
(
resource
=
MyModelResource
)
,
name
=
'model-resource-root'
),
url
(
r'^$'
,
my_model_list
,
name
=
'model-resource-root'
),
url
(
r'^(?P<
pk>[0-9]+)/$'
,
InstanceModelView
.
as_view
(
resource
=
MyModelResource
)
),
url
(
r'^(?P<
id>[0-9]+)/$'
,
my_model_instance
,
name
=
'model-resource-instance'
),
)
)
examples/objectstore/views.py
View file @
1cde31c8
...
@@ -28,6 +28,20 @@ def remove_oldest_files(dir, max_files):
...
@@ -28,6 +28,20 @@ def remove_oldest_files(dir, max_files):
[
os
.
remove
(
path
)
for
path
in
ctime_sorted_paths
[
max_files
:]]
[
os
.
remove
(
path
)
for
path
in
ctime_sorted_paths
[
max_files
:]]
def
get_filename
(
key
):
"""
Given a stored object's key returns the file's path.
"""
return
os
.
path
.
join
(
OBJECT_STORE_DIR
,
key
)
def
get_file_url
(
key
,
request
):
"""
Given a stored object's key returns the URL for the object.
"""
return
reverse
(
'stored-object'
,
kwargs
=
{
'key'
:
key
},
request
=
request
)
class
ObjectStoreRoot
(
View
):
class
ObjectStoreRoot
(
View
):
"""
"""
Root of the Object Store API.
Root of the Object Store API.
...
@@ -38,20 +52,25 @@ class ObjectStoreRoot(View):
...
@@ -38,20 +52,25 @@ class ObjectStoreRoot(View):
"""
"""
Return a list of all the stored object URLs. (Ordered by creation time, newest first)
Return a list of all the stored object URLs. (Ordered by creation time, newest first)
"""
"""
filepaths
=
[
os
.
path
.
join
(
OBJECT_STORE_DIR
,
file
)
for
file
in
os
.
listdir
(
OBJECT_STORE_DIR
)
if
not
file
.
startswith
(
'.'
)]
filepaths
=
[
os
.
path
.
join
(
OBJECT_STORE_DIR
,
file
)
for
file
in
os
.
listdir
(
OBJECT_STORE_DIR
)
if
not
file
.
startswith
(
'.'
)]
ctime_sorted_basenames
=
[
item
[
0
]
for
item
in
sorted
([(
os
.
path
.
basename
(
path
),
os
.
path
.
getctime
(
path
))
for
path
in
filepaths
],
ctime_sorted_basenames
=
[
item
[
0
]
for
item
in
sorted
([(
os
.
path
.
basename
(
path
),
os
.
path
.
getctime
(
path
))
for
path
in
filepaths
],
key
=
operator
.
itemgetter
(
1
),
reverse
=
True
)]
key
=
operator
.
itemgetter
(
1
),
reverse
=
True
)]
return
Response
([
reverse
(
'stored-object'
,
request
,
kwargs
=
{
'key'
:
key
})
for
key
in
ctime_sorted_basenames
])
content
=
[
get_file_url
(
key
,
request
)
for
key
in
ctime_sorted_basenames
]
return
Response
(
content
)
def
post
(
self
,
request
):
def
post
(
self
,
request
):
"""
"""
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
)
filename
=
get_filename
(
key
)
pickle
.
dump
(
self
.
CONTENT
,
open
(
pathname
,
'wb'
))
pickle
.
dump
(
self
.
CONTENT
,
open
(
filename
,
'wb'
))
remove_oldest_files
(
OBJECT_STORE_DIR
,
MAX_FILES
)
remove_oldest_files
(
OBJECT_STORE_DIR
,
MAX_FILES
)
url
=
reverse
(
'stored-object'
,
request
,
kwargs
=
{
'key'
:
key
}
)
url
=
get_file_url
(
key
,
request
)
return
Response
(
self
.
CONTENT
,
status
.
HTTP_201_CREATED
,
{
'Location'
:
url
})
return
Response
(
self
.
CONTENT
,
status
.
HTTP_201_CREATED
,
{
'Location'
:
url
})
...
@@ -60,30 +79,31 @@ class StoredObject(View):
...
@@ -60,30 +79,31 @@ class StoredObject(View):
Represents a stored object.
Represents a stored object.
The object may be any picklable content.
The object may be any picklable content.
"""
"""
def
get
(
self
,
request
,
key
):
def
get
(
self
,
request
,
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
)
filename
=
get_filename
(
key
)
if
not
os
.
path
.
exists
(
path
name
):
if
not
os
.
path
.
exists
(
file
name
):
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
(
pickle
.
load
(
open
(
path
name
,
'rb'
)))
return
Response
(
pickle
.
load
(
open
(
file
name
,
'rb'
)))
def
put
(
self
,
request
,
key
):
def
put
(
self
,
request
,
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
)
filename
=
get_filename
(
key
)
pickle
.
dump
(
self
.
CONTENT
,
open
(
path
name
,
'wb'
))
pickle
.
dump
(
self
.
CONTENT
,
open
(
file
name
,
'wb'
))
return
Response
(
self
.
CONTENT
)
return
Response
(
self
.
CONTENT
)
def
delete
(
self
,
request
,
key
):
def
delete
(
self
,
request
,
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
)
filename
=
get_filename
(
key
)
if
not
os
.
path
.
exists
(
path
name
):
if
not
os
.
path
.
exists
(
file
name
):
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
os
.
remove
(
path
name
)
os
.
remove
(
file
name
)
return
Response
()
return
Response
()
examples/pygments_api/forms.py
View file @
1cde31c8
...
@@ -6,6 +6,7 @@ from pygments.styles import get_all_styles
...
@@ -6,6 +6,7 @@ from pygments.styles import get_all_styles
LEXER_CHOICES
=
sorted
([(
item
[
1
][
0
],
item
[
0
])
for
item
in
get_all_lexers
()])
LEXER_CHOICES
=
sorted
([(
item
[
1
][
0
],
item
[
0
])
for
item
in
get_all_lexers
()])
STYLE_CHOICES
=
sorted
((
item
,
item
)
for
item
in
list
(
get_all_styles
()))
STYLE_CHOICES
=
sorted
((
item
,
item
)
for
item
in
list
(
get_all_styles
()))
class
PygmentsForm
(
forms
.
Form
):
class
PygmentsForm
(
forms
.
Form
):
"""A simple form with some of the most important pygments settings.
"""A simple form with some of the most important pygments settings.
The code to be highlighted can be specified either in a text field, or by URL.
The code to be highlighted can be specified either in a text field, or by URL.
...
@@ -24,5 +25,3 @@ class PygmentsForm(forms.Form):
...
@@ -24,5 +25,3 @@ class PygmentsForm(forms.Form):
initial
=
'python'
)
initial
=
'python'
)
style
=
forms
.
ChoiceField
(
choices
=
STYLE_CHOICES
,
style
=
forms
.
ChoiceField
(
choices
=
STYLE_CHOICES
,
initial
=
'friendly'
)
initial
=
'friendly'
)
examples/pygments_api/tests.py
View file @
1cde31c8
...
@@ -44,6 +44,3 @@ class TestPygmentsExample(TestCase):
...
@@ -44,6 +44,3 @@ class TestPygmentsExample(TestCase):
response
=
view
(
request
)
response
=
view
(
request
)
response_locations
=
json
.
loads
(
response
.
content
)
response_locations
=
json
.
loads
(
response
.
content
)
self
.
assertEquals
(
locations
,
response_locations
)
self
.
assertEquals
(
locations
,
response_locations
)
examples/pygments_api/views.py
View file @
1cde31c8
from
__future__
import
with_statement
# for python 2.5
from
__future__
import
with_statement
# for python 2.5
from
django.conf
import
settings
from
django.conf
import
settings
from
djangorestframework.resources
import
FormResource
from
djangorestframework.response
import
Response
from
djangorestframework.response
import
Response
from
djangorestframework.renderers
import
BaseRenderer
from
djangorestframework.renderers
import
BaseRenderer
from
djangorestframework.reverse
import
reverse
from
djangorestframework.reverse
import
reverse
...
@@ -30,9 +29,13 @@ def list_dir_sorted_by_ctime(dir):
...
@@ -30,9 +29,13 @@ def list_dir_sorted_by_ctime(dir):
"""
"""
Return a list of files sorted by creation time
Return a list of files sorted by creation time
"""
"""
filepaths
=
[
os
.
path
.
join
(
dir
,
file
)
for
file
in
os
.
listdir
(
dir
)
if
not
file
.
startswith
(
'.'
)]
filepaths
=
[
os
.
path
.
join
(
dir
,
file
)
return
[
item
[
0
]
for
item
in
sorted
(
[(
path
,
os
.
path
.
getctime
(
path
))
for
path
in
filepaths
],
for
file
in
os
.
listdir
(
dir
)
key
=
operator
.
itemgetter
(
1
),
reverse
=
False
)
]
if
not
file
.
startswith
(
'.'
)]
ctimes
=
[(
path
,
os
.
path
.
getctime
(
path
))
for
path
in
filepaths
]
ctimes
=
sorted
(
ctimes
,
key
=
operator
.
itemgetter
(
1
),
reverse
=
False
)
return
[
filepath
for
filepath
,
ctime
in
ctimes
]
def
remove_oldest_files
(
dir
,
max_files
):
def
remove_oldest_files
(
dir
,
max_files
):
"""
"""
...
@@ -60,8 +63,11 @@ class PygmentsRoot(View):
...
@@ -60,8 +63,11 @@ class PygmentsRoot(View):
"""
"""
Return a list of all currently existing snippets.
Return a list of all currently existing snippets.
"""
"""
unique_ids
=
[
os
.
path
.
split
(
f
)[
1
]
for
f
in
list_dir_sorted_by_ctime
(
HIGHLIGHTED_CODE_DIR
)]
unique_ids
=
[
os
.
path
.
split
(
f
)[
1
]
return
Response
([
reverse
(
'pygments-instance'
,
request
,
args
=
[
unique_id
])
for
unique_id
in
unique_ids
])
for
f
in
list_dir_sorted_by_ctime
(
HIGHLIGHTED_CODE_DIR
)]
urls
=
[
reverse
(
'pygments-instance'
,
args
=
[
unique_id
],
request
=
request
)
for
unique_id
in
unique_ids
]
return
Response
(
urls
)
def
post
(
self
,
request
):
def
post
(
self
,
request
):
"""
"""
...
@@ -81,7 +87,7 @@ class PygmentsRoot(View):
...
@@ -81,7 +87,7 @@ class PygmentsRoot(View):
remove_oldest_files
(
HIGHLIGHTED_CODE_DIR
,
MAX_FILES
)
remove_oldest_files
(
HIGHLIGHTED_CODE_DIR
,
MAX_FILES
)
location
=
reverse
(
'pygments-instance'
,
request
,
args
=
[
unique_id
]
)
location
=
reverse
(
'pygments-instance'
,
args
=
[
unique_id
],
request
=
request
)
return
Response
(
status
=
status
.
HTTP_201_CREATED
,
headers
=
{
'Location'
:
location
})
return
Response
(
status
=
status
.
HTTP_201_CREATED
,
headers
=
{
'Location'
:
location
})
...
@@ -90,7 +96,7 @@ class PygmentsInstance(View):
...
@@ -90,7 +96,7 @@ class PygmentsInstance(View):
Simply return the stored highlighted HTML file with the correct mime type.
Simply return the stored highlighted HTML file with the correct mime type.
This Resource only renders HTML and uses a standard HTML renderer rather than the renderers.DocumentingHTMLRenderer class.
This Resource only renders HTML and uses a standard HTML renderer rather than the renderers.DocumentingHTMLRenderer class.
"""
"""
renderer
_classes
=
(
HTMLRenderer
,
)
renderer
s
=
(
HTMLRenderer
,
)
def
get
(
self
,
request
,
unique_id
):
def
get
(
self
,
request
,
unique_id
):
"""
"""
...
@@ -110,4 +116,3 @@ class PygmentsInstance(View):
...
@@ -110,4 +116,3 @@ class PygmentsInstance(View):
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
os
.
remove
(
pathname
)
os
.
remove
(
pathname
)
return
Response
()
return
Response
()
examples/requestexample/views.py
View file @
1cde31c8
...
@@ -22,7 +22,7 @@ class MyBaseViewUsingEnhancedRequest(RequestMixin, View):
...
@@ -22,7 +22,7 @@ class MyBaseViewUsingEnhancedRequest(RequestMixin, View):
Base view enabling the usage of enhanced requests with user defined views.
Base view enabling the usage of enhanced requests with user defined views.
"""
"""
parser
_classe
s
=
parsers
.
DEFAULT_PARSERS
parsers
=
parsers
.
DEFAULT_PARSERS
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
request
=
request
=
self
.
create_request
(
request
)
self
.
request
=
request
=
self
.
create_request
(
request
)
...
@@ -41,4 +41,3 @@ class EchoRequestContentView(MyBaseViewUsingEnhancedRequest):
...
@@ -41,4 +41,3 @@ class EchoRequestContentView(MyBaseViewUsingEnhancedRequest):
def
put
(
self
,
request
,
*
args
,
**
kwargs
):
def
put
(
self
,
request
,
*
args
,
**
kwargs
):
return
HttpResponse
((
"Found
%
s in request.DATA, content :
%
s"
%
return
HttpResponse
((
"Found
%
s in request.DATA, content :
%
s"
%
(
type
(
request
.
DATA
),
request
.
DATA
)))
(
type
(
request
.
DATA
),
request
.
DATA
)))
examples/resourceexample/forms.py
View file @
1cde31c8
from
django
import
forms
from
django
import
forms
class
MyForm
(
forms
.
Form
):
class
MyForm
(
forms
.
Form
):
foo
=
forms
.
BooleanField
(
required
=
False
)
foo
=
forms
.
BooleanField
(
required
=
False
)
bar
=
forms
.
IntegerField
(
help_text
=
'Must be an integer.'
)
bar
=
forms
.
IntegerField
(
help_text
=
'Must be an integer.'
)
...
...
examples/resourceexample/views.py
View file @
1cde31c8
...
@@ -16,9 +16,11 @@ class ExampleView(View):
...
@@ -16,9 +16,11 @@ class ExampleView(View):
Handle GET requests, returning a list of URLs pointing to
Handle GET requests, returning a list of URLs pointing to
three other views.
three other views.
"""
"""
urls
=
[
reverse
(
'another-example'
,
request
,
kwargs
=
{
'num'
:
num
})
resource_urls
=
[
reverse
(
'another-example'
,
kwargs
=
{
'num'
:
num
},
request
=
request
)
for
num
in
range
(
3
)]
for
num
in
range
(
3
)]
return
Response
({
"Some other resources"
:
urls
})
return
Response
({
"Some other resources"
:
resource_
urls
})
class
AnotherExampleView
(
View
):
class
AnotherExampleView
(
View
):
...
...
examples/sandbox/views.py
View file @
1cde31c8
...
@@ -49,19 +49,19 @@ class Sandbox(View):
...
@@ -49,19 +49,19 @@ class Sandbox(View):
def
get
(
self
,
request
):
def
get
(
self
,
request
):
return
Response
([
return
Response
([
{
'name'
:
'Simple Resource example'
,
{
'name'
:
'Simple Resource example'
,
'url'
:
reverse
(
'example-resource'
,
request
)},
'url'
:
reverse
(
'example-resource'
,
request
=
request
)},
{
'name'
:
'Simple ModelResource example'
,
{
'name'
:
'Simple ModelResource example'
,
'url'
:
reverse
(
'model-resource-root'
,
request
)},
'url'
:
reverse
(
'model-resource-root'
,
request
=
request
)},
{
'name'
:
'Simple Mixin-only example'
,
{
'name'
:
'Simple Mixin-only example'
,
'url'
:
reverse
(
'mixin-view'
,
request
)},
'url'
:
reverse
(
'mixin-view'
,
request
=
request
)},
{
'name'
:
'Object store API'
{
'name'
:
'Object store API'
,
'url'
:
reverse
(
'object-store-root'
,
request
)},
'url'
:
reverse
(
'object-store-root'
,
request
=
request
)},
{
'name'
:
'Code highlighting API'
,
{
'name'
:
'Code highlighting API'
,
'url'
:
reverse
(
'pygments-root'
,
request
)},
'url'
:
reverse
(
'pygments-root'
,
request
=
request
)},
{
'name'
:
'Blog posts API'
,
{
'name'
:
'Blog posts API'
,
'url'
:
reverse
(
'blog-posts-root'
,
request
)},
'url'
:
reverse
(
'blog-posts-root'
,
request
=
request
)},
{
'name'
:
'Permissions example'
,
{
'name'
:
'Permissions example'
,
'url'
:
reverse
(
'permissions-example'
,
request
)},
'url'
:
reverse
(
'permissions-example'
,
request
=
request
)},
{
'name'
:
'Simple request mixin example'
,
{
'name'
:
'Simple request mixin example'
,
'url'
:
reverse
(
'request-example'
,
request
)}
'url'
:
reverse
(
'request-example'
,
request
=
request
)}
])
])
examples/urls.py
View file @
1cde31c8
from
django.conf.urls.defaults
import
patterns
,
include
from
django.conf.urls.defaults
import
patterns
,
include
,
url
from
sandbox.views
import
Sandbox
from
sandbox.views
import
Sandbox
try
:
try
:
from
django.contrib.staticfiles.urls
import
staticfiles_urlpatterns
from
django.contrib.staticfiles.urls
import
staticfiles_urlpatterns
...
@@ -15,9 +15,7 @@ urlpatterns = patterns('',
...
@@ -15,9 +15,7 @@ urlpatterns = patterns('',
(
r'^pygments/'
,
include
(
'pygments_api.urls'
)),
(
r'^pygments/'
,
include
(
'pygments_api.urls'
)),
(
r'^blog-post/'
,
include
(
'blogpost.urls'
)),
(
r'^blog-post/'
,
include
(
'blogpost.urls'
)),
(
r'^permissions-example/'
,
include
(
'permissionsexample.urls'
)),
(
r'^permissions-example/'
,
include
(
'permissionsexample.urls'
)),
(
r'^request-example/'
,
include
(
'requestexample.urls'
)),
url
(
r'^restframework/'
,
include
(
'djangorestframework.urls'
,
namespace
=
'djangorestframework'
)),
(
r'^'
,
include
(
'djangorestframework.urls'
)),
)
)
urlpatterns
+=
staticfiles_urlpatterns
()
urlpatterns
+=
staticfiles_urlpatterns
()
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