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
15f9e7c5
Commit
15f9e7c5
authored
May 12, 2011
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactoring resource specfic stuff into ResourceMixin - validators now defunct
parent
4d126796
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
368 additions
and
147 deletions
+368
-147
djangorestframework/authentication.py
+2
-2
djangorestframework/mixins.py
+64
-62
djangorestframework/parsers.py
+7
-12
djangorestframework/renderers.py
+4
-4
djangorestframework/resource.py
+249
-22
djangorestframework/tests/content.py
+15
-15
djangorestframework/tests/files.py
+5
-1
djangorestframework/tests/methods.py
+2
-2
djangorestframework/tests/parsers.py
+11
-7
djangorestframework/views.py
+7
-18
examples/sandbox/views.py
+2
-2
No files found.
djangorestframework/authentication.py
View file @
15f9e7c5
...
...
@@ -85,9 +85,9 @@ class UserLoggedInAuthenticaton(BaseAuthenticaton):
if
getattr
(
request
,
'user'
,
None
)
and
request
.
user
.
is_active
:
# If this is a POST request we enforce CSRF validation.
if
request
.
method
.
upper
()
==
'POST'
:
# Temporarily replace request.POST with .
RAW_CONTENT
,
# Temporarily replace request.POST with .
DATA
,
# so that we use our more generic request parsing
request
.
_post
=
self
.
view
.
RAW_CONTENT
request
.
_post
=
self
.
view
.
DATA
resp
=
CsrfViewMiddleware
()
.
process_view
(
request
,
None
,
(),
{})
del
(
request
.
_post
)
if
resp
is
not
None
:
# csrf failed
...
...
djangorestframework/mixins.py
View file @
15f9e7c5
""""""
"""
The mixins module provides a set of reusable mixin classes that can be added to a ``View``.
"""
from
django.contrib.auth.models
import
AnonymousUser
from
django.db.models.query
import
QuerySet
...
...
@@ -18,9 +20,12 @@ from StringIO import StringIO
__all__
=
(
# Base behavior mixins
'RequestMixin'
,
'ResponseMixin'
,
'AuthMixin'
,
'ResourceMixin'
,
# Model behavior mixins
'ReadModelMixin'
,
'CreateModelMixin'
,
'UpdateModelMixin'
,
...
...
@@ -36,13 +41,12 @@ class RequestMixin(object):
Mixin class to provide request parsing behavior.
"""
USE_FORM_OVERLOADING
=
True
METHOD_PARAM
=
"_method"
CONTENTTYPE_PARAM
=
"_content_type"
CONTENT_PARAM
=
"_content"
_
USE_FORM_OVERLOADING
=
True
_METHOD_PARAM
=
'_method'
_CONTENTTYPE_PARAM
=
'_content_type'
_CONTENT_PARAM
=
'_content'
parsers
=
()
validators
=
()
def
_get_method
(
self
):
"""
...
...
@@ -137,62 +141,58 @@ class RequestMixin(object):
self
.
_stream
=
stream
def
_get_raw_content
(
self
):
"""
Returns the parsed content of the request
"""
if
not
hasattr
(
self
,
'_raw_content'
):
self
.
_raw_content
=
self
.
parse
(
self
.
stream
,
self
.
content_type
)
return
self
.
_raw_content
def
_load_data_and_files
(
self
):
(
self
.
_data
,
self
.
_files
)
=
self
.
_parse
(
self
.
stream
,
self
.
content_type
)
def
_get_data
(
self
):
if
not
hasattr
(
self
,
'_data'
):
self
.
_load_data_and_files
()
return
self
.
_data
def
_get_content
(
self
):
"""
Returns the parsed and validated content of the request
"""
if
not
hasattr
(
self
,
'_content'
):
self
.
_content
=
self
.
validate
(
self
.
RAW_CONTENT
)
def
_get_files
(
self
):
if
not
hasattr
(
self
,
'_files'
):
self
.
_load_data_and_files
()
return
self
.
_files
return
self
.
_content
# TODO: Modify this so that it happens implictly, rather than being called explicitly
# ie accessing any of .DATA, .FILES, .content_type, .
stream or .
method will force
# ie accessing any of .DATA, .FILES, .content_type, .method will force
# form overloading.
def
perform_form_overloading
(
self
):
def
_
perform_form_overloading
(
self
):
"""
Check the request to see if it is using form POST '_method'/'_content'/'_content_type' overrides.
If it is then alter self.method, self.content_type, self.CONTENT to reflect that rather than simply
delegating them to the original request.
"""
if
not
self
.
USE_FORM_OVERLOADING
or
self
.
method
!=
'POST'
or
not
is_form_media_type
(
self
.
content_type
):
if
not
self
.
_
USE_FORM_OVERLOADING
or
self
.
method
!=
'POST'
or
not
is_form_media_type
(
self
.
content_type
):
return
# Temporarily switch to using the form parsers, then parse the content
parsers
=
self
.
parsers
self
.
parsers
=
(
FormParser
,
MultiPartParser
)
content
=
self
.
RAW_CONTENT
content
=
self
.
DATA
self
.
parsers
=
parsers
# Method overloading - change the method and remove the param from the content
if
self
.
METHOD_PARAM
in
content
:
self
.
method
=
content
[
self
.
METHOD_PARAM
]
.
upper
()
del
self
.
_
raw_content
[
self
.
METHOD_PARAM
]
if
self
.
_
METHOD_PARAM
in
content
:
self
.
method
=
content
[
self
.
_
METHOD_PARAM
]
.
upper
()
del
self
.
_
data
[
self
.
_
METHOD_PARAM
]
# Content overloading - rewind the stream and modify the content type
if
self
.
CONTENT_PARAM
in
content
and
self
.
CONTENTTYPE_PARAM
in
content
:
self
.
_content_type
=
content
[
self
.
CONTENTTYPE_PARAM
]
self
.
_stream
=
StringIO
(
content
[
self
.
CONTENT_PARAM
])
del
(
self
.
_
raw_content
)
if
self
.
_CONTENT_PARAM
in
content
and
self
.
_
CONTENTTYPE_PARAM
in
content
:
self
.
_content_type
=
content
[
self
.
_
CONTENTTYPE_PARAM
]
self
.
_stream
=
StringIO
(
content
[
self
.
_
CONTENT_PARAM
])
del
(
self
.
_
data
)
def
parse
(
self
,
stream
,
content_type
):
def
_
parse
(
self
,
stream
,
content_type
):
"""
Parse the request content.
May raise a 415 ErrorResponse (Unsupported Media Type), or a 400 ErrorResponse (Bad Request).
"""
if
stream
is
None
or
content_type
is
None
:
return
None
return
(
None
,
None
)
parsers
=
as_tuple
(
self
.
parsers
)
...
...
@@ -206,48 +206,28 @@ class RequestMixin(object):
content_type
})
# TODO: Acutally this needs to go into Resource
def
validate
(
self
,
content
):
"""
Validate, cleanup, and type-ify the request content.
"""
for
validator_cls
in
self
.
validators
:
validator
=
validator_cls
(
self
)
content
=
validator
.
validate
(
content
)
return
content
# TODO: Acutally this needs to go into Resource
def
get_bound_form
(
self
,
content
=
None
):
"""
Return a bound form instance for the given content,
if there is an appropriate form validator attached to the view.
"""
for
validator_cls
in
self
.
validators
:
if
hasattr
(
validator_cls
,
'get_bound_form'
):
validator
=
validator_cls
(
self
)
return
validator
.
get_bound_form
(
content
)
return
None
@property
def
parsed_media_types
(
self
):
"""Return an list of all the media types that this view can parse."""
"""
Return an 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 most preferred parser.
(This has no behavioral effect, but is may be used by documenting renderers)"""
"""
Return the view's most preferred parser.
(This has no behavioral effect, but is may be used by documenting renderers)
"""
return
self
.
parsers
[
0
]
method
=
property
(
_get_method
,
_set_method
)
content_type
=
property
(
_get_content_type
,
_set_content_type
)
stream
=
property
(
_get_stream
,
_set_stream
)
RAW_CONTENT
=
property
(
_get_raw_content
)
CONTENT
=
property
(
_get_content
)
DATA
=
property
(
_get_data
)
FILES
=
property
(
_get_files
)
########## ResponseMixin ##########
...
...
@@ -422,6 +402,28 @@ class AuthMixin(object):
permission
.
check_permission
(
user
)
########## Resource Mixin ##########
class
ResourceMixin
(
object
):
@property
def
CONTENT
(
self
):
if
not
hasattr
(
self
,
'_content'
):
self
.
_content
=
self
.
_get_content
(
self
.
DATA
,
self
.
FILES
)
return
self
.
_content
def
_get_content
(
self
,
data
,
files
):
resource
=
self
.
resource
(
self
)
return
resource
.
validate
(
data
,
files
)
def
get_bound_form
(
self
,
content
=
None
):
resource
=
self
.
resource
(
self
)
return
resource
.
get_bound_form
(
content
)
def
object_to_data
(
self
,
obj
):
resource
=
self
.
resource
(
self
)
return
resource
.
object_to_data
(
obj
)
########## Model Mixins ##########
class
ReadModelMixin
(
object
):
...
...
djangorestframework/parsers.py
View file @
15f9e7c5
...
...
@@ -41,7 +41,7 @@ class BaseParser(object):
"""
self
.
view
=
view
def
can_handle_request
(
self
,
media
_type
):
def
can_handle_request
(
self
,
content
_type
):
"""
Returns `True` if this parser is able to deal with the given media type.
...
...
@@ -52,12 +52,12 @@ class BaseParser(object):
This may be overridden to provide for other behavior, but typically you'll
instead want to just set the ``media_type`` attribute on the class.
"""
return
media_type_matches
(
media
_type
,
self
.
media_type
)
return
media_type_matches
(
content
_type
,
self
.
media_type
)
def
parse
(
self
,
stream
):
"""
Given a stream to read from, return the deserialized output.
The return value may be of any type, but for many parsers it might typically be a dict-like object
.
Should return a 2-tuple of (data, files)
.
"""
raise
NotImplementedError
(
"BaseParser.parse() Must be overridden to be implemented."
)
...
...
@@ -67,7 +67,7 @@ class JSONParser(BaseParser):
def
parse
(
self
,
stream
):
try
:
return
json
.
load
(
stream
)
return
(
json
.
load
(
stream
),
None
)
except
ValueError
,
exc
:
raise
ErrorResponse
(
status
.
HTTP_400_BAD_REQUEST
,
{
'detail'
:
'JSON parse error -
%
s'
%
unicode
(
exc
)})
...
...
@@ -107,7 +107,7 @@ class PlainTextParser(BaseParser):
media_type
=
'text/plain'
def
parse
(
self
,
stream
):
return
stream
.
read
(
)
return
(
stream
.
read
(),
None
)
class
FormParser
(
BaseParser
,
DataFlatener
):
...
...
@@ -139,7 +139,7 @@ class FormParser(BaseParser, DataFlatener):
if
key
in
self
.
RESERVED_FORM_PARAMS
:
data
.
pop
(
key
)
return
data
return
(
data
,
None
)
def
remove_empty_val
(
self
,
val_list
):
""" """
...
...
@@ -152,11 +152,6 @@ class FormParser(BaseParser, DataFlatener):
val_list
.
pop
(
ind
)
class
MultipartData
(
dict
):
def
__init__
(
self
,
data
,
files
):
dict
.
__init__
(
self
,
data
)
self
.
FILES
=
files
class
MultiPartParser
(
BaseParser
,
DataFlatener
):
media_type
=
'multipart/form-data'
RESERVED_FORM_PARAMS
=
(
'csrfmiddlewaretoken'
,)
...
...
@@ -175,4 +170,4 @@ class MultiPartParser(BaseParser, DataFlatener):
if
key
in
self
.
RESERVED_FORM_PARAMS
:
data
.
pop
(
key
)
return
MultipartData
(
data
,
files
)
return
(
data
,
files
)
djangorestframework/renderers.py
View file @
15f9e7c5
...
...
@@ -150,7 +150,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
# If we're not using content overloading there's no point in supplying a generic form,
# as the view won't treat the form's value as the content of the request.
if
not
getattr
(
view
,
'USE_FORM_OVERLOADING'
,
False
):
if
not
getattr
(
view
,
'
_
USE_FORM_OVERLOADING'
,
False
):
return
None
# NB. http://jacobian.org/writing/dynamic-form-generation/
...
...
@@ -164,14 +164,14 @@ class DocumentingTemplateRenderer(BaseRenderer):
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
view
.
parsed_media_types
]
initial_contenttype
=
view
.
default_parser
.
media_type
self
.
fields
[
view
.
CONTENTTYPE_PARAM
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
self
.
fields
[
view
.
_
CONTENTTYPE_PARAM
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
choices
=
contenttype_choices
,
initial
=
initial_contenttype
)
self
.
fields
[
view
.
CONTENT_PARAM
]
=
forms
.
CharField
(
label
=
'Content'
,
self
.
fields
[
view
.
_
CONTENT_PARAM
]
=
forms
.
CharField
(
label
=
'Content'
,
widget
=
forms
.
Textarea
)
# If either of these reserved parameters are turned off then content tunneling is not possible
if
self
.
view
.
CONTENTTYPE_PARAM
is
None
or
self
.
view
.
CONTENT_PARAM
is
None
:
if
self
.
view
.
_CONTENTTYPE_PARAM
is
None
or
self
.
view
.
_
CONTENT_PARAM
is
None
:
return
None
# Okey doke, let's do it
...
...
djangorestframework/resource.py
View file @
15f9e7c5
...
...
@@ -42,7 +42,7 @@ def _object_to_data(obj):
return
[
_object_to_data
(
item
)
for
item
in
obj
]
if
isinstance
(
obj
,
models
.
Manager
):
# Manager objects
ret
=
[
_object_to_data
(
item
)
for
item
in
obj
.
all
()]
ret
urn
[
_object_to_data
(
item
)
for
item
in
obj
.
all
()]
if
isinstance
(
obj
,
models
.
Model
):
# Model instances
return
_object_to_data
(
_model_to_dict
(
obj
))
...
...
@@ -60,26 +60,48 @@ def _object_to_data(obj):
return
smart_unicode
(
obj
,
strings_only
=
True
)
# TODO: Replace this with new Serializer code based on Forms API.
def
_form_to_data
(
form
):
"""
Returns a dict containing the data in a form instance.
This code is pretty much a clone of the ``Form.as_p()`` ``Form.as_ul``
and ``Form.as_table()`` methods, except that it returns data suitable
for arbitrary serialization, rather than rendering the result directly
into html.
"""
ret
=
{}
for
name
,
field
in
form
.
fields
.
items
():
if
not
form
.
is_bound
:
data
=
form
.
initial
.
get
(
name
,
field
.
initial
)
if
callable
(
data
):
data
=
data
()
else
:
if
isinstance
(
field
,
FileField
)
and
form
.
data
is
None
:
data
=
form
.
initial
.
get
(
name
,
field
.
initial
)
else
:
data
=
field
.
widget
.
value_from_datadict
(
form
.
data
,
form
.
files
,
name
)
ret
[
name
]
=
field
.
prepare_value
(
data
)
return
ret
#class Resource(object):
# def __init__(self, view):
# self.view = view
#
# def object_to_data(self, obj):
# pass
#
# def data_to_object(self, data, files):
# pass
#
#class FormResource(object):
# pass
#
#class ModelResource(object):
# pass
class
BaseResource
(
object
):
"""Base class for all Resource classes, which simply defines the interface they provide."""
def
__init__
(
self
,
view
):
self
.
view
=
view
def
validate
(
self
,
data
,
files
):
"""Given some content as input return some cleaned, validated content.
Typically raises a ErrorResponse with status code 400 (Bad Request) on failure.
Must be overridden to be implemented."""
return
data
def
object_to_data
(
self
,
obj
):
return
_object_to_data
(
obj
)
class
Resource
(
object
):
class
Resource
(
BaseResource
):
"""
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.
...
...
@@ -99,9 +121,11 @@ class Resource(object):
# you should explicitly set the fields attribute on your class.
fields
=
None
@classmethod
def
object_to_serializable
(
self
,
data
):
"""A (horrible) munging of Piston's pre-serialization. Returns a dict"""
# TODO: Replace this with new Serializer code based on Forms API.
def
object_to_data
(
self
,
obj
):
"""
A (horrible) munging of Piston's pre-serialization. Returns a dict.
"""
def
_any
(
thing
,
fields
=
()):
"""
...
...
@@ -321,5 +345,208 @@ class Resource(object):
return
dict
([
(
k
,
_any
(
v
))
for
k
,
v
in
data
.
iteritems
()
])
# Kickstart the seralizin'.
return
_any
(
data
,
self
.
fields
)
return
_any
(
obj
,
self
.
fields
)
class
FormResource
(
Resource
):
"""Validator class that uses forms for validation.
Also provides a get_bound_form() method which may be used by some renderers.
The view class should provide `.form` attribute which specifies the form classmethod
to be used for validation.
On calling validate() this validator may set a `.bound_form_instance` attribute on the
view, which may be used by some renderers."""
def
validate
(
self
,
data
,
files
):
"""
Given some content as input return some cleaned, validated content.
Raises a ErrorResponse with status code 400 (Bad Request) on failure.
Validation is standard form validation, with an additional constraint that no extra unknown fields may be supplied.
On failure the ErrorResponse content is a dict which may contain 'errors' and 'field-errors' keys.
If the 'errors' key exists it is a list of strings of non-field errors.
If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}.
"""
return
self
.
_validate
(
data
,
files
)
def
_validate
(
self
,
data
,
files
,
allowed_extra_fields
=
()):
"""
Wrapped by validate to hide the extra_fields option that the ModelValidatorMixin uses.
extra_fields is a list of fields which are not defined by the form, but which we still
expect to see on the input.
"""
bound_form
=
self
.
get_bound_form
(
data
,
files
)
if
bound_form
is
None
:
return
data
self
.
view
.
bound_form_instance
=
bound_form
seen_fields_set
=
set
(
data
.
keys
())
form_fields_set
=
set
(
bound_form
.
fields
.
keys
())
allowed_extra_fields_set
=
set
(
allowed_extra_fields
)
# In addition to regular validation we also ensure no additional fields are being passed in...
unknown_fields
=
seen_fields_set
-
(
form_fields_set
|
allowed_extra_fields_set
)
# Check using both regular validation, and our stricter no additional fields rule
if
bound_form
.
is_valid
()
and
not
unknown_fields
:
# Validation succeeded...
cleaned_data
=
bound_form
.
cleaned_data
cleaned_data
.
update
(
bound_form
.
files
)
# Add in any extra fields to the cleaned content...
for
key
in
(
allowed_extra_fields_set
&
seen_fields_set
)
-
set
(
cleaned_data
.
keys
()):
cleaned_data
[
key
]
=
data
[
key
]
return
cleaned_data
# Validation failed...
detail
=
{}
if
not
bound_form
.
errors
and
not
unknown_fields
:
detail
=
{
u'errors'
:
[
u'No content was supplied.'
]}
else
:
# Add any non-field errors
if
bound_form
.
non_field_errors
():
detail
[
u'errors'
]
=
bound_form
.
non_field_errors
()
# Add standard field errors
field_errors
=
dict
((
key
,
map
(
unicode
,
val
))
for
(
key
,
val
)
in
bound_form
.
errors
.
iteritems
()
if
not
key
.
startswith
(
'__'
))
# Add any unknown field errors
for
key
in
unknown_fields
:
field_errors
[
key
]
=
[
u'This field does not exist.'
]
if
field_errors
:
detail
[
u'field-errors'
]
=
field_errors
# Return HTTP 400 response (BAD REQUEST)
raise
ErrorResponse
(
400
,
detail
)
def
get_bound_form
(
self
,
data
=
None
,
files
=
None
):
"""Given some content return a Django form bound to that content.
If form validation is turned off (form class attribute is None) then returns None."""
form_cls
=
getattr
(
self
,
'form'
,
None
)
if
not
form_cls
:
return
None
if
data
is
not
None
:
return
form_cls
(
data
,
files
)
return
form_cls
()
class
ModelResource
(
FormResource
):
"""Validator class that uses forms for validation and otherwise falls back to a model form if no form is set.
Also provides a get_bound_form() method which may be used by some renderers."""
"""The form class that should be used for validation, or None to use model form validation."""
form
=
None
"""The model class from which the model form should be constructed if no form is set."""
model
=
None
"""The list of fields we expect to receive as input. Fields in this list will may be received with
raising non-existent field errors, even if they do not exist as fields on the ModelForm.
Setting the fields class attribute causes the exclude_fields class attribute to be disregarded."""
fields
=
None
"""The list of fields to exclude from the Model. This is only used if the fields class attribute is not set."""
exclude_fields
=
(
'id'
,
'pk'
)
# TODO: test the different validation here to allow for get get_absolute_url to be supplied on input and not bork out
# TODO: be really strict on fields - check they match in the handler methods. (this isn't a validator thing tho.)
def
validate
(
self
,
data
,
files
):
"""
Given some content as input return some cleaned, validated content.
Raises a ErrorResponse with status code 400 (Bad Request) on failure.
Validation is standard form or model form validation,
with an additional constraint that no extra unknown fields may be supplied,
and that all fields specified by the fields class attribute must be supplied,
even if they are not validated by the form/model form.
On failure the ErrorResponse content is a dict which may contain 'errors' and 'field-errors' keys.
If the 'errors' key exists it is a list of strings of non-field errors.
If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}.
"""
return
self
.
_validate
(
data
,
files
,
allowed_extra_fields
=
self
.
_property_fields_set
)
def
get_bound_form
(
self
,
data
=
None
,
files
=
None
):
"""Given some content return a Django form bound to that content.
If the form class attribute has been explicitly set then use that class to create a Form,
otherwise if model is set use that class to create a ModelForm, otherwise return None."""
form_cls
=
getattr
(
self
,
'form'
,
None
)
model_cls
=
getattr
(
self
,
'model'
,
None
)
if
form_cls
:
# Use explict Form
return
super
(
ModelFormValidator
,
self
)
.
get_bound_form
(
data
,
files
)
elif
model_cls
:
# Fall back to ModelForm which we create on the fly
class
OnTheFlyModelForm
(
forms
.
ModelForm
):
class
Meta
:
model
=
model_cls
#fields = tuple(self._model_fields_set)
# Instantiate the ModelForm as appropriate
if
content
and
isinstance
(
content
,
models
.
Model
):
# Bound to an existing model instance
return
OnTheFlyModelForm
(
instance
=
content
)
elif
not
data
is
None
:
return
OnTheFlyModelForm
(
data
,
files
)
return
OnTheFlyModelForm
()
# Both form and model not set? Okay bruv, whatevs...
return
None
@property
def
_model_fields_set
(
self
):
"""Return a set containing the names of validated fields on the model."""
resource
=
self
.
view
.
resource
model
=
getattr
(
resource
,
'model'
,
None
)
fields
=
getattr
(
resource
,
'fields'
,
self
.
fields
)
exclude_fields
=
getattr
(
resource
,
'exclude_fields'
,
self
.
exclude_fields
)
model_fields
=
set
(
field
.
name
for
field
in
model
.
_meta
.
fields
)
if
fields
:
return
model_fields
&
set
(
as_tuple
(
fields
))
return
model_fields
-
set
(
as_tuple
(
exclude_fields
))
@property
def
_property_fields_set
(
self
):
"""Returns a set containing the names of validated properties on the model."""
resource
=
self
.
view
.
resource
model
=
getattr
(
resource
,
'model'
,
None
)
fields
=
getattr
(
resource
,
'fields'
,
self
.
fields
)
exclude_fields
=
getattr
(
resource
,
'exclude_fields'
,
self
.
exclude_fields
)
property_fields
=
set
(
attr
for
attr
in
dir
(
model
)
if
isinstance
(
getattr
(
model
,
attr
,
None
),
property
)
and
not
attr
.
startswith
(
'_'
))
if
fields
:
return
property_fields
&
set
(
as_tuple
(
fields
))
return
property_fields
-
set
(
as_tuple
(
exclude_fields
))
djangorestframework/tests/content.py
View file @
15f9e7c5
...
...
@@ -14,14 +14,14 @@ class TestContentParsing(TestCase):
def
ensure_determines_no_content_GET
(
self
,
view
):
"""Ensure view.RAW_CONTENT returns None for GET request with no content."""
view
.
request
=
self
.
req
.
get
(
'/'
)
self
.
assertEqual
(
view
.
RAW_CONTENT
,
None
)
self
.
assertEqual
(
view
.
DATA
,
None
)
def
ensure_determines_form_content_POST
(
self
,
view
):
"""Ensure view.RAW_CONTENT returns content for POST request with form content."""
form_data
=
{
'qwerty'
:
'uiop'
}
view
.
parsers
=
(
FormParser
,
MultiPartParser
)
view
.
request
=
self
.
req
.
post
(
'/'
,
data
=
form_data
)
self
.
assertEqual
(
view
.
RAW_CONTENT
,
form_data
)
self
.
assertEqual
(
view
.
DATA
,
form_data
)
def
ensure_determines_non_form_content_POST
(
self
,
view
):
"""Ensure view.RAW_CONTENT returns content for POST request with non-form content."""
...
...
@@ -29,14 +29,14 @@ class TestContentParsing(TestCase):
content_type
=
'text/plain'
view
.
parsers
=
(
PlainTextParser
,)
view
.
request
=
self
.
req
.
post
(
'/'
,
content
,
content_type
=
content_type
)
self
.
assertEqual
(
view
.
RAW_CONTENT
,
content
)
self
.
assertEqual
(
view
.
DATA
,
content
)
def
ensure_determines_form_content_PUT
(
self
,
view
):
"""Ensure view.RAW_CONTENT returns content for PUT request with form content."""
form_data
=
{
'qwerty'
:
'uiop'
}
view
.
parsers
=
(
FormParser
,
MultiPartParser
)
view
.
request
=
self
.
req
.
put
(
'/'
,
data
=
form_data
)
self
.
assertEqual
(
view
.
RAW_CONTENT
,
form_data
)
self
.
assertEqual
(
view
.
DATA
,
form_data
)
def
ensure_determines_non_form_content_PUT
(
self
,
view
):
"""Ensure view.RAW_CONTENT returns content for PUT request with non-form content."""
...
...
@@ -44,36 +44,36 @@ class TestContentParsing(TestCase):
content_type
=
'text/plain'
view
.
parsers
=
(
PlainTextParser
,)
view
.
request
=
self
.
req
.
post
(
'/'
,
content
,
content_type
=
content_type
)
self
.
assertEqual
(
view
.
RAW_CONTENT
,
content
)
self
.
assertEqual
(
view
.
DATA
,
content
)
def
test_standard_behaviour_determines_no_content_GET
(
self
):
"""Ensure view.
RAW_CONTENT
returns None for GET request with no content."""
"""Ensure view.
DATA
returns None for GET request with no content."""
self
.
ensure_determines_no_content_GET
(
RequestMixin
())
def
test_standard_behaviour_determines_form_content_POST
(
self
):
"""Ensure view.
RAW_CONTENT
returns content for POST request with form content."""
"""Ensure view.
DATA
returns content for POST request with form content."""
self
.
ensure_determines_form_content_POST
(
RequestMixin
())
def
test_standard_behaviour_determines_non_form_content_POST
(
self
):
"""Ensure view.
RAW_CONTENT
returns content for POST request with non-form content."""
"""Ensure view.
DATA
returns content for POST request with non-form content."""
self
.
ensure_determines_non_form_content_POST
(
RequestMixin
())
def
test_standard_behaviour_determines_form_content_PUT
(
self
):
"""Ensure view.
RAW_CONTENT
returns content for PUT request with form content."""
"""Ensure view.
DATA
returns content for PUT request with form content."""
self
.
ensure_determines_form_content_PUT
(
RequestMixin
())
def
test_standard_behaviour_determines_non_form_content_PUT
(
self
):
"""Ensure view.
RAW_CONTENT
returns content for PUT request with non-form content."""
"""Ensure view.
DATA
returns content for PUT request with non-form content."""
self
.
ensure_determines_non_form_content_PUT
(
RequestMixin
())
def
test_overloaded_behaviour_allows_content_tunnelling
(
self
):
"""Ensure request.
RAW_CONTENT
returns content for overloaded POST request"""
"""Ensure request.
DATA
returns content for overloaded POST request"""
content
=
'qwerty'
content_type
=
'text/plain'
view
=
RequestMixin
()
form_data
=
{
view
.
CONTENT_PARAM
:
content
,
view
.
CONTENTTYPE_PARAM
:
content_type
}
form_data
=
{
view
.
_
CONTENT_PARAM
:
content
,
view
.
_
CONTENTTYPE_PARAM
:
content_type
}
view
.
request
=
self
.
req
.
post
(
'/'
,
form_data
)
view
.
parsers
=
(
PlainTextParser
,)
view
.
perform_form_overloading
()
self
.
assertEqual
(
view
.
RAW_CONTENT
,
content
)
view
.
_
perform_form_overloading
()
self
.
assertEqual
(
view
.
DATA
,
content
)
djangorestframework/tests/files.py
View file @
15f9e7c5
...
...
@@ -2,6 +2,7 @@ from django.test import TestCase
from
django
import
forms
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.views
import
BaseView
from
djangorestframework.resource
import
FormResource
import
StringIO
class
UploadFilesTests
(
TestCase
):
...
...
@@ -15,9 +16,12 @@ class UploadFilesTests(TestCase):
class
FileForm
(
forms
.
Form
):
file
=
forms
.
FileField
class
MockResource
(
FormResource
):
form
=
FileForm
class
MockView
(
BaseView
):
permissions
=
()
form
=
FileForm
resource
=
MockResource
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
return
{
'FILE_NAME'
:
self
.
CONTENT
[
'file'
]
.
name
,
...
...
djangorestframework/tests/methods.py
View file @
15f9e7c5
...
...
@@ -22,6 +22,6 @@ class TestMethodOverloading(TestCase):
def
test_overloaded_POST_behaviour_determines_overloaded_method
(
self
):
"""POST requests can be overloaded to another method by setting a reserved form field"""
view
=
RequestMixin
()
view
.
request
=
self
.
req
.
post
(
'/'
,
{
view
.
METHOD_PARAM
:
'DELETE'
})
view
.
perform_form_overloading
()
view
.
request
=
self
.
req
.
post
(
'/'
,
{
view
.
_
METHOD_PARAM
:
'DELETE'
})
view
.
_
perform_form_overloading
()
self
.
assertEqual
(
view
.
method
,
'DELETE'
)
djangorestframework/tests/parsers.py
View file @
15f9e7c5
...
...
@@ -24,7 +24,8 @@ Here is some example data, which would eventually be sent along with a post requ
Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter :
>>> FormParser(some_view).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': 'blo1'}
>>> (data, files) = FormParser(some_view).parse(StringIO(inpt))
>>> data == {'key1': 'bla1', 'key2': 'blo1'}
True
However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` :
...
...
@@ -36,7 +37,8 @@ However, you can customize this behaviour by subclassing :class:`parsers.FormPar
This new parser only flattens the lists of parameters that contain a single value.
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': ['blo1', 'blo2']}
>>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
>>> data == {'key1': 'bla1', 'key2': ['blo1', 'blo2']}
True
.. note:: The same functionality is available for :class:`parsers.MultiPartParser`.
...
...
@@ -61,7 +63,8 @@ The browsers usually strip the parameter completely. A hack to avoid this, and t
:class:`parsers.FormParser` strips the values ``_empty`` from all the lists.
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'blo1'}
>>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
>>> data == {'key1': 'blo1'}
True
Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it.
...
...
@@ -71,7 +74,8 @@ Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a lis
... def is_a_list(self, key, val_list):
... return key == 'key2'
...
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'blo1', 'key2': []}
>>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
>>> data == {'key1': 'blo1', 'key2': []}
True
Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`.
...
...
@@ -123,7 +127,7 @@ class TestMultiPartParser(TestCase):
post_req
=
RequestFactory
()
.
post
(
'/'
,
self
.
body
,
content_type
=
self
.
content_type
)
view
=
BaseView
()
view
.
request
=
post_req
parsed
=
MultiPartParser
(
view
)
.
parse
(
StringIO
(
self
.
body
))
self
.
assertEqual
(
parsed
[
'key1'
],
'val1'
)
self
.
assertEqual
(
parsed
.
FILES
[
'file1'
]
.
read
(),
'blablabla'
)
(
data
,
files
)
=
MultiPartParser
(
view
)
.
parse
(
StringIO
(
self
.
body
))
self
.
assertEqual
(
data
[
'key1'
],
'val1'
)
self
.
assertEqual
(
files
[
'file1'
]
.
read
(),
'blablabla'
)
djangorestframework/views.py
View file @
15f9e7c5
...
...
@@ -17,7 +17,7 @@ __all__ = (
class
BaseView
(
RequestMixin
,
ResponseMixin
,
AuthMixin
,
View
):
class
BaseView
(
Re
sourceMixin
,
Re
questMixin
,
ResponseMixin
,
AuthMixin
,
View
):
"""Handles incoming requests and maps them to REST operations.
Performs request deserialization, response serialization, authentication and input validation."""
...
...
@@ -46,9 +46,6 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View):
# List of all permissions that must be checked.
permissions
=
(
permissions
.
FullAnonAccess
,
)
# Optional form for input validation and presentation of HTML formatted responses.
form
=
None
# Allow name and description for the Resource to be set explicitly,
# overiding the default classname/docstring behaviour.
# These are used for documentation in the standard html and text renderers.
...
...
@@ -60,22 +57,13 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View):
return
[
method
.
upper
()
for
method
in
self
.
http_method_names
if
hasattr
(
self
,
method
)]
def
http_method_not_allowed
(
self
,
request
,
*
args
,
**
kwargs
):
"""Return an HTTP 405 error if an operation is called which does not have a handler method."""
"""
Return an HTTP 405 error if an operation is called which does not have a handler method.
"""
raise
ErrorResponse
(
status
.
HTTP_405_METHOD_NOT_ALLOWED
,
{
'detail'
:
'Method
\'
%
s
\'
not allowed on this resource.'
%
self
.
method
})
def
cleanup_response
(
self
,
data
):
"""Perform any resource-specific data filtering prior to the standard HTTP
content-type serialization.
Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can.
TODO: This is going to be removed. I think that the 'fields' behaviour is going to move into
the RendererMixin and Renderer classes."""
return
data
# Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt.
@csrf_exempt
...
...
@@ -92,7 +80,7 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View):
try
:
# If using a form POST with '_method'/'_content'/'_content_type' overrides, then alter
# self.method, self.content_type, self.RAW_CONTENT & self.CONTENT appropriately.
self
.
perform_form_overloading
()
self
.
_
perform_form_overloading
()
# Authenticate and check request is has the relevant permissions
self
.
_check_permissions
()
...
...
@@ -114,13 +102,14 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View):
response
=
Response
(
status
.
HTTP_204_NO_CONTENT
)
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
response
.
cleaned_content
=
self
.
resource
.
object_to_serializable
(
response
.
raw_content
)
response
.
cleaned_content
=
self
.
object_to_data
(
response
.
raw_content
)
except
ErrorResponse
,
exc
:
response
=
exc
.
response
except
:
import
traceback
traceback
.
print_exc
()
raise
# Always add these headers.
#
...
...
examples/sandbox/views.py
View file @
15f9e7c5
"""The root view for the examples provided with Django REST framework"""
from
django.core.urlresolvers
import
reverse
from
djangorestframework.
resource
import
Resource
from
djangorestframework.
views
import
BaseView
class
Sandbox
(
Resource
):
class
Sandbox
(
BaseView
):
"""This is the sandbox for the examples provided with [Django REST framework](http://django-rest-framework.org).
These examples are provided to help you get a better idea of the some of the features of RESTful APIs created using the framework.
...
...
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