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
349ffcaf
Commit
349ffcaf
authored
Apr 11, 2011
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Rename mixins into Mixin class, rename ResponseException to ErrorResponse, remove NoContent
parent
a1ed5650
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
64 additions
and
191 deletions
+64
-191
djangorestframework/emitters.py
+11
-136
djangorestframework/modelresource.py
+3
-3
djangorestframework/parsers.py
+2
-2
djangorestframework/resource.py
+15
-12
djangorestframework/response.py
+10
-17
djangorestframework/tests/content.py
+1
-1
djangorestframework/tests/emitters.py
+3
-2
djangorestframework/tests/methods.py
+1
-1
djangorestframework/tests/validators.py
+10
-10
djangorestframework/utils.py
+1
-0
djangorestframework/validators.py
+7
-7
No files found.
djangorestframework/emitters.py
View file @
349ffcaf
...
...
@@ -4,11 +4,10 @@ by serializing the output along with documentation regarding the Resource, outpu
and providing forms and links depending on the allowed methods, emitters and parsers on the Resource.
"""
from
django.conf
import
settings
from
django.http
import
HttpResponse
from
django.template
import
RequestContext
,
loader
from
django
import
forms
from
djangorestframework.response
import
NoContent
,
ResponseException
from
djangorestframework.response
import
ErrorResponse
from
djangorestframework.utils
import
dict2xml
,
url_resolves
from
djangorestframework.markdownwrapper
import
apply_markdown
from
djangorestframework.breadcrumbs
import
get_breadcrumbs
...
...
@@ -18,7 +17,6 @@ from djangorestframework import status
from
urllib
import
quote_plus
import
string
import
re
from
decimal
import
Decimal
try
:
import
json
...
...
@@ -26,132 +24,9 @@ except ImportError:
import
simplejson
as
json
_MSIE_USER_AGENT
=
re
.
compile
(
r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )'
)
class
EmitterMixin
(
object
):
"""Adds behaviour for pluggable Emitters to a :class:`.Resource` or Django :class:`View`. class.
Default behaviour is to use standard HTTP Accept header content negotiation.
Also supports overidding the content type by specifying an _accept= parameter in the URL.
Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead."""
ACCEPT_QUERY_PARAM
=
'_accept'
# Allow override of Accept header in URL query params
REWRITE_IE_ACCEPT_HEADER
=
True
request
=
None
response
=
None
emitters
=
()
def
emit
(
self
,
response
):
"""Takes a :class:`Response` object and returns a Django :class:`HttpResponse`."""
self
.
response
=
response
try
:
emitter
=
self
.
_determine_emitter
(
self
.
request
)
except
ResponseException
,
exc
:
emitter
=
self
.
default_emitter
response
=
exc
.
response
# Serialize the response content
if
response
.
has_content_body
:
content
=
emitter
(
self
)
.
emit
(
output
=
response
.
cleaned_content
)
else
:
content
=
emitter
(
self
)
.
emit
()
# Munge DELETE Response code to allow us to return content
# (Do this *after* we've rendered the template so that we include the normal deletion response code in the output)
if
response
.
status
==
204
:
response
.
status
=
200
# Build the HTTP Response
# TODO: Check if emitter.mimetype is underspecified, or if a content-type header has been set
resp
=
HttpResponse
(
content
,
mimetype
=
emitter
.
media_type
,
status
=
response
.
status
)
for
(
key
,
val
)
in
response
.
headers
.
items
():
resp
[
key
]
=
val
return
resp
def
_determine_emitter
(
self
,
request
):
"""Return the appropriate emitter for the output, given the client's 'Accept' header,
and the content types that this Resource knows how to serve.
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html"""
if
self
.
ACCEPT_QUERY_PARAM
and
request
.
GET
.
get
(
self
.
ACCEPT_QUERY_PARAM
,
None
):
# Use _accept parameter override
accept_list
=
[
request
.
GET
.
get
(
self
.
ACCEPT_QUERY_PARAM
)]
elif
self
.
REWRITE_IE_ACCEPT_HEADER
and
request
.
META
.
has_key
(
'HTTP_USER_AGENT'
)
and
_MSIE_USER_AGENT
.
match
(
request
.
META
[
'HTTP_USER_AGENT'
]):
accept_list
=
[
'text/html'
,
'*/*'
]
elif
request
.
META
.
has_key
(
'HTTP_ACCEPT'
):
# Use standard HTTP Accept negotiation
accept_list
=
request
.
META
[
"HTTP_ACCEPT"
]
.
split
(
','
)
else
:
# No accept header specified
return
self
.
default_emitter
# Parse the accept header into a dict of {qvalue: set of media types}
# We ignore mietype parameters
accept_dict
=
{}
for
token
in
accept_list
:
components
=
token
.
split
(
';'
)
mimetype
=
components
[
0
]
.
strip
()
qvalue
=
Decimal
(
'1.0'
)
if
len
(
components
)
>
1
:
# Parse items that have a qvalue eg text/html;q=0.9
try
:
(
q
,
num
)
=
components
[
-
1
]
.
split
(
'='
)
if
q
==
'q'
:
qvalue
=
Decimal
(
num
)
except
:
# Skip malformed entries
continue
if
accept_dict
.
has_key
(
qvalue
):
accept_dict
[
qvalue
]
.
add
(
mimetype
)
else
:
accept_dict
[
qvalue
]
=
set
((
mimetype
,))
# Convert to a list of sets ordered by qvalue (highest first)
accept_sets
=
[
accept_dict
[
qvalue
]
for
qvalue
in
sorted
(
accept_dict
.
keys
(),
reverse
=
True
)]
for
accept_set
in
accept_sets
:
# Return any exact match
for
emitter
in
self
.
emitters
:
if
emitter
.
media_type
in
accept_set
:
return
emitter
# Return any subtype match
for
emitter
in
self
.
emitters
:
if
emitter
.
media_type
.
split
(
'/'
)[
0
]
+
'/*'
in
accept_set
:
return
emitter
# Return default
if
'*/*'
in
accept_set
:
return
self
.
default_emitter
raise
ResponseException
(
status
.
HTTP_406_NOT_ACCEPTABLE
,
{
'detail'
:
'Could not statisfy the client
\'
s Accept header'
,
'available_types'
:
self
.
emitted_media_types
})
@property
def
emitted_media_types
(
self
):
"""Return an list of all the media types that this resource can emit."""
return
[
emitter
.
media_type
for
emitter
in
self
.
emitters
]
@property
def
default_emitter
(
self
):
"""Return the resource's most prefered emitter.
(This emitter is used if the client does not send and Accept: header, or sends Accept: */*)"""
return
self
.
emitters
[
0
]
# TODO: Rename verbose to something more appropriate
# TODO:
NoContent
could be handled more cleanly. It'd be nice if it was handled by default,
# TODO:
Maybe None
could be handled more cleanly. It'd be nice if it was handled by default,
# and only have an emitter output anything if it explicitly provides support for that.
class
BaseEmitter
(
object
):
...
...
@@ -162,10 +37,10 @@ class BaseEmitter(object):
def
__init__
(
self
,
resource
):
self
.
resource
=
resource
def
emit
(
self
,
output
=
No
Content
,
verbose
=
False
):
def
emit
(
self
,
output
=
No
ne
,
verbose
=
False
):
"""By default emit simply returns the ouput as-is.
Override this method to provide for other behaviour."""
if
output
is
No
Content
:
if
output
is
No
ne
:
return
''
return
output
...
...
@@ -177,8 +52,8 @@ class TemplateEmitter(BaseEmitter):
media_type
=
None
template
=
None
def
emit
(
self
,
output
=
No
Content
,
verbose
=
False
):
if
output
is
No
Content
:
def
emit
(
self
,
output
=
No
ne
,
verbose
=
False
):
if
output
is
No
ne
:
return
''
context
=
RequestContext
(
self
.
request
,
output
)
...
...
@@ -276,7 +151,7 @@ class DocumentingTemplateEmitter(BaseEmitter):
return
GenericContentForm
(
resource
)
def
emit
(
self
,
output
=
No
Content
):
def
emit
(
self
,
output
=
No
ne
):
content
=
self
.
_get_content
(
self
.
resource
,
self
.
resource
.
request
,
output
)
form_instance
=
self
.
_get_form_instance
(
self
.
resource
)
...
...
@@ -324,8 +199,8 @@ class JSONEmitter(BaseEmitter):
"""Emitter which serializes to JSON"""
media_type
=
'application/json'
def
emit
(
self
,
output
=
No
Content
,
verbose
=
False
):
if
output
is
No
Content
:
def
emit
(
self
,
output
=
No
ne
,
verbose
=
False
):
if
output
is
No
ne
:
return
''
if
verbose
:
return
json
.
dumps
(
output
,
indent
=
4
,
sort_keys
=
True
)
...
...
@@ -336,8 +211,8 @@ class XMLEmitter(BaseEmitter):
"""Emitter which serializes to XML."""
media_type
=
'application/xml'
def
emit
(
self
,
output
=
No
Content
,
verbose
=
False
):
if
output
is
No
Content
:
def
emit
(
self
,
output
=
No
ne
,
verbose
=
False
):
if
output
is
No
ne
:
return
''
return
dict2xml
(
output
)
...
...
djangorestframework/modelresource.py
View file @
349ffcaf
...
...
@@ -3,7 +3,7 @@ from django.db.models import Model
from
django.db.models.query
import
QuerySet
from
django.db.models.fields.related
import
RelatedField
from
djangorestframework.response
import
Response
,
ResponseException
from
djangorestframework.response
import
Response
,
ErrorResponse
from
djangorestframework.resource
import
Resource
from
djangorestframework
import
status
,
validators
...
...
@@ -370,7 +370,7 @@ class ModelResource(Resource):
# Otherwise assume the kwargs uniquely identify the model
instance
=
self
.
model
.
objects
.
get
(
**
kwargs
)
except
self
.
model
.
DoesNotExist
:
raise
ResponseException
(
status
.
HTTP_404_NOT_FOUND
)
raise
ErrorResponse
(
status
.
HTTP_404_NOT_FOUND
)
return
instance
...
...
@@ -402,7 +402,7 @@ class ModelResource(Resource):
# Otherwise assume the kwargs uniquely identify the model
instance
=
self
.
model
.
objects
.
get
(
**
kwargs
)
except
self
.
model
.
DoesNotExist
:
raise
ResponseException
(
status
.
HTTP_404_NOT_FOUND
,
None
,
{})
raise
ErrorResponse
(
status
.
HTTP_404_NOT_FOUND
,
None
,
{})
instance
.
delete
()
return
...
...
djangorestframework/parsers.py
View file @
349ffcaf
...
...
@@ -9,7 +9,7 @@ We need a method to be able to:
and multipart/form-data. (eg also handle multipart/json)
"""
from
django.http.multipartparser
import
MultiPartParser
as
DjangoMPParser
from
djangorestframework.response
import
ResponseException
from
djangorestframework.response
import
ErrorResponse
from
djangorestframework
import
status
from
djangorestframework.utils
import
as_tuple
from
djangorestframework.mediatypes
import
MediaType
...
...
@@ -59,7 +59,7 @@ class JSONParser(BaseParser):
try
:
return
json
.
load
(
stream
)
except
ValueError
,
exc
:
raise
ResponseException
(
status
.
HTTP_400_BAD_REQUEST
,
{
'detail'
:
'JSON parse error -
%
s'
%
str
(
exc
)})
raise
ErrorResponse
(
status
.
HTTP_400_BAD_REQUEST
,
{
'detail'
:
'JSON parse error -
%
s'
%
str
(
exc
)})
class
DataFlatener
(
object
):
...
...
djangorestframework/resource.py
View file @
349ffcaf
...
...
@@ -2,9 +2,8 @@ from django.core.urlresolvers import set_script_prefix
from
django.views.decorators.csrf
import
csrf_exempt
from
djangorestframework.compat
import
View
from
djangorestframework.emitters
import
EmitterMixin
from
djangorestframework.response
import
Response
,
ResponseException
from
djangorestframework.request
import
RequestMixin
,
AuthMixin
from
djangorestframework.response
import
Response
,
ErrorResponse
from
djangorestframework.mixins
import
RequestMixin
,
ResponseMixin
,
AuthMixin
from
djangorestframework
import
emitters
,
parsers
,
authenticators
,
validators
,
status
...
...
@@ -16,7 +15,7 @@ from djangorestframework import emitters, parsers, authenticators, validators, s
__all__
=
[
'Resource'
]
class
Resource
(
EmitterMixin
,
AuthMixin
,
Request
Mixin
,
View
):
class
Resource
(
RequestMixin
,
ResponseMixin
,
Auth
Mixin
,
View
):
"""Handles incoming requests and maps them to REST operations,
performing authentication, input deserialization, input validation, output serialization."""
...
...
@@ -81,7 +80,7 @@ class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
def
not_implemented
(
self
,
operation
):
"""Return an HTTP 500 server error if an operation is called which has been allowed by
allowed_methods, but which has not been implemented."""
raise
ResponseException
(
status
.
HTTP_500_INTERNAL_SERVER_ERROR
,
raise
ErrorResponse
(
status
.
HTTP_500_INTERNAL_SERVER_ERROR
,
{
'detail'
:
'
%
s operation on this resource has not been implemented'
%
(
operation
,
)})
...
...
@@ -89,15 +88,15 @@ class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
"""Ensure the request method is permitted for this resource, raising a ResourceException if it is not."""
if
not
method
in
self
.
callmap
.
keys
():
raise
ResponseException
(
status
.
HTTP_501_NOT_IMPLEMENTED
,
raise
ErrorResponse
(
status
.
HTTP_501_NOT_IMPLEMENTED
,
{
'detail'
:
'Unknown or unsupported method
\'
%
s
\'
'
%
method
})
if
not
method
in
self
.
allowed_methods
:
raise
ResponseException
(
status
.
HTTP_405_METHOD_NOT_ALLOWED
,
raise
ErrorResponse
(
status
.
HTTP_405_METHOD_NOT_ALLOWED
,
{
'detail'
:
'Method
\'
%
s
\'
not allowed on this resource.'
%
method
})
if
auth
is
None
and
not
method
in
self
.
anon_allowed_methods
:
raise
ResponseException
(
status
.
HTTP_403_FORBIDDEN
,
raise
ErrorResponse
(
status
.
HTTP_403_FORBIDDEN
,
{
'detail'
:
'You do not have permission to access this resource. '
+
'You may need to login or otherwise authenticate the request.'
})
...
...
@@ -172,7 +171,7 @@ class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
response
.
cleaned_content
=
self
.
cleanup_response
(
response
.
raw_content
)
except
ResponseException
,
exc
:
except
ErrorResponse
,
exc
:
response
=
exc
.
response
except
:
...
...
@@ -183,8 +182,12 @@ class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
#
# TODO - this isn't actually the correct way to set the vary header,
# also it's currently sub-obtimal for HTTP caching - need to sort that out.
response
.
headers
[
'Allow'
]
=
', '
.
join
(
self
.
allowed_methods
)
response
.
headers
[
'Vary'
]
=
'Authenticate, Accept'
try
:
response
.
headers
[
'Allow'
]
=
', '
.
join
(
self
.
allowed_methods
)
response
.
headers
[
'Vary'
]
=
'Authenticate, Accept'
return
self
.
emit
(
response
)
return
self
.
emit
(
response
)
except
:
import
traceback
traceback
.
print_exc
()
djangorestframework/response.py
View file @
349ffcaf
from
django.core.handlers.wsgi
import
STATUS_CODE_TEXT
__all__
=
[
'NoContent'
,
'Response'
,
]
class
NoContent
(
object
):
"""Used to indicate no body in http response.
(We cannot just use None, as that is a valid, serializable response object.)
TODO: On reflection I'm going to get rid of this and just not support serialized 'None' responses.
"""
pass
__all__
=
[
'Response'
,
'ErrorResponse'
]
# TODO: remove raw_content/cleaned_content and just use content?
class
Response
(
object
):
def
__init__
(
self
,
status
=
200
,
content
=
NoContent
,
headers
=
{}):
"""An HttpResponse that may include content that hasn't yet been serialized."""
def
__init__
(
self
,
status
=
200
,
content
=
None
,
headers
=
{}):
self
.
status
=
status
self
.
has_content_body
=
not
content
is
NoContent
# TODO: remove and just use content
self
.
raw_content
=
content
# content prior to filtering
- TODO: remove and just use content
self
.
cleaned_content
=
content
# content after filtering
TODO: remove and just use content
self
.
has_content_body
=
content
is
not
None
self
.
raw_content
=
content
# content prior to filtering
self
.
cleaned_content
=
content
# content after filtering
self
.
headers
=
headers
@property
...
...
@@ -28,6 +20,7 @@ class Response(object):
return
STATUS_CODE_TEXT
.
get
(
self
.
status
,
''
)
class
ResponseException
(
BaseException
):
def
__init__
(
self
,
status
,
content
=
NoContent
,
headers
=
{}):
class
ErrorResponse
(
BaseException
):
"""An exception representing an HttpResponse that should be returned immediatley."""
def
__init__
(
self
,
status
,
content
=
None
,
headers
=
{}):
self
.
response
=
Response
(
status
,
content
=
content
,
headers
=
headers
)
djangorestframework/tests/content.py
View file @
349ffcaf
...
...
@@ -3,7 +3,7 @@ Tests for content parsing, and form-overloaded content parsing.
"""
from
django.test
import
TestCase
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.
request
import
RequestMixin
from
djangorestframework.
mixins
import
RequestMixin
from
djangorestframework.parsers
import
FormParser
,
MultipartParser
,
PlainTextParser
...
...
djangorestframework/tests/emitters.py
View file @
349ffcaf
...
...
@@ -2,7 +2,8 @@ from django.conf.urls.defaults import patterns, url
from
django
import
http
from
django.test
import
TestCase
from
djangorestframework.compat
import
View
from
djangorestframework.emitters
import
EmitterMixin
,
BaseEmitter
from
djangorestframework.emitters
import
BaseEmitter
from
djangorestframework.mixins
import
ResponseMixin
from
djangorestframework.response
import
Response
DUMMYSTATUS
=
200
...
...
@@ -11,7 +12,7 @@ DUMMYCONTENT = 'dummycontent'
EMITTER_A_SERIALIZER
=
lambda
x
:
'Emitter A:
%
s'
%
x
EMITTER_B_SERIALIZER
=
lambda
x
:
'Emitter B:
%
s'
%
x
class
MockView
(
Emitter
Mixin
,
View
):
class
MockView
(
Response
Mixin
,
View
):
def
get
(
self
,
request
):
response
=
Response
(
DUMMYSTATUS
,
DUMMYCONTENT
)
return
self
.
emit
(
response
)
...
...
djangorestframework/tests/methods.py
View file @
349ffcaf
from
django.test
import
TestCase
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.
request
import
RequestMixin
from
djangorestframework.
mixins
import
RequestMixin
class
TestMethodOverloading
(
TestCase
):
...
...
djangorestframework/tests/validators.py
View file @
349ffcaf
...
...
@@ -3,7 +3,7 @@ from django.db import models
from
django.test
import
TestCase
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.validators
import
BaseValidator
,
FormValidator
,
ModelFormValidator
from
djangorestframework.response
import
ResponseException
from
djangorestframework.response
import
ErrorResponse
class
TestValidatorMixinInterfaces
(
TestCase
):
...
...
@@ -81,7 +81,7 @@ class TestNonFieldErrors(TestCase):
content
=
{
'field1'
:
'example1'
,
'field2'
:
'example2'
}
try
:
FormValidator
(
view
)
.
validate
(
content
)
except
ResponseException
,
exc
:
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'errors'
:
[
MockForm
.
ERROR_TEXT
]})
else
:
self
.
fail
(
'ResourceException was not raised'
)
#pragma: no cover
...
...
@@ -115,14 +115,14 @@ class TestFormValidation(TestCase):
def
validation_failure_raises_response_exception
(
self
,
validator
):
"""If form validation fails a ResourceException 400 (Bad Request) should be raised."""
content
=
{}
self
.
assertRaises
(
ResponseException
,
validator
.
validate
,
content
)
self
.
assertRaises
(
ErrorResponse
,
validator
.
validate
,
content
)
def
validation_does_not_allow_extra_fields_by_default
(
self
,
validator
):
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
broken clients more easily (eg submitting content with a misnamed field)"""
content
=
{
'qwerty'
:
'uiop'
,
'extra'
:
'extra'
}
self
.
assertRaises
(
ResponseException
,
validator
.
validate
,
content
)
self
.
assertRaises
(
ErrorResponse
,
validator
.
validate
,
content
)
def
validation_allows_extra_fields_if_explicitly_set
(
self
,
validator
):
"""If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
...
...
@@ -139,7 +139,7 @@ class TestFormValidation(TestCase):
content
=
{}
try
:
validator
.
validate
(
content
)
except
ResponseException
,
exc
:
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'field-errors'
:
{
'qwerty'
:
[
'This field is required.'
]}})
else
:
self
.
fail
(
'ResourceException was not raised'
)
#pragma: no cover
...
...
@@ -149,7 +149,7 @@ class TestFormValidation(TestCase):
content
=
{
'qwerty'
:
''
}
try
:
validator
.
validate
(
content
)
except
ResponseException
,
exc
:
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'field-errors'
:
{
'qwerty'
:
[
'This field is required.'
]}})
else
:
self
.
fail
(
'ResourceException was not raised'
)
#pragma: no cover
...
...
@@ -159,7 +159,7 @@ class TestFormValidation(TestCase):
content
=
{
'qwerty'
:
'uiop'
,
'extra'
:
'extra'
}
try
:
validator
.
validate
(
content
)
except
ResponseException
,
exc
:
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'field-errors'
:
{
'extra'
:
[
'This field does not exist.'
]}})
else
:
self
.
fail
(
'ResourceException was not raised'
)
#pragma: no cover
...
...
@@ -169,7 +169,7 @@ class TestFormValidation(TestCase):
content
=
{
'qwerty'
:
''
,
'extra'
:
'extra'
}
try
:
validator
.
validate
(
content
)
except
ResponseException
,
exc
:
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'field-errors'
:
{
'qwerty'
:
[
'This field is required.'
],
'extra'
:
[
'This field does not exist.'
]}})
else
:
...
...
@@ -286,14 +286,14 @@ class TestModelFormValidator(TestCase):
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
broken clients more easily (eg submitting content with a misnamed field)"""
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
,
'readonly'
:
'read only'
,
'extra'
:
'extra'
}
self
.
assertRaises
(
ResponseException
,
self
.
validator
.
validate
,
content
)
self
.
assertRaises
(
ErrorResponse
,
self
.
validator
.
validate
,
content
)
def
test_validate_requires_fields_on_model_forms
(
self
):
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
broken clients more easily (eg submitting content with a misnamed field)"""
content
=
{
'readonly'
:
'read only'
}
self
.
assertRaises
(
ResponseException
,
self
.
validator
.
validate
,
content
)
self
.
assertRaises
(
ErrorResponse
,
self
.
validator
.
validate
,
content
)
def
test_validate_does_not_require_blankable_fields_on_model_forms
(
self
):
"""Test standard ModelForm validation behaviour - fields with blank=True are not required."""
...
...
djangorestframework/utils.py
View file @
349ffcaf
...
...
@@ -14,6 +14,7 @@ except ImportError:
# """Adds the ADMIN_MEDIA_PREFIX to the request context."""
# return {'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX}
MSIE_USER_AGENT_REGEX
=
re
.
compile
(
r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )'
)
def
as_tuple
(
obj
):
"""Given obj return a tuple"""
...
...
djangorestframework/validators.py
View file @
349ffcaf
"""Mixin classes that provide a validate(content) function to validate and cleanup request content"""
from
django
import
forms
from
django.db
import
models
from
djangorestframework.response
import
ResponseException
from
djangorestframework.response
import
ErrorResponse
from
djangorestframework.utils
import
as_tuple
...
...
@@ -13,7 +13,7 @@ class BaseValidator(object):
def
validate
(
self
,
content
):
"""Given some content as input return some cleaned, validated content.
Typically raises a
ResponseException
with status code 400 (Bad Request) on failure.
Typically raises a
ErrorResponse
with status code 400 (Bad Request) on failure.
Must be overridden to be implemented."""
raise
NotImplementedError
()
...
...
@@ -32,11 +32,11 @@ class FormValidator(BaseValidator):
def
validate
(
self
,
content
):
"""Given some content as input return some cleaned, validated content.
Raises a
ResponseException
with status code 400 (Bad Request) on failure.
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
ResponseException
content is a dict which may contain 'errors' and 'field-errors' keys.
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
(
content
)
...
...
@@ -97,7 +97,7 @@ class FormValidator(BaseValidator):
detail
[
u'field-errors'
]
=
field_errors
# Return HTTP 400 response (BAD REQUEST)
raise
ResponseException
(
400
,
detail
)
raise
ErrorResponse
(
400
,
detail
)
def
get_bound_form
(
self
,
content
=
None
):
...
...
@@ -139,14 +139,14 @@ class ModelFormValidator(FormValidator):
# TODO: be really strict on fields - check they match in the handler methods. (this isn't a validator thing tho.)
def
validate
(
self
,
content
):
"""Given some content as input return some cleaned, validated content.
Raises a
ResponseException
with status code 400 (Bad Request) on failure.
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
ResponseException
content is a dict which may contain 'errors' and 'field-errors' keys.
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
(
content
,
allowed_extra_fields
=
self
.
_property_fields_set
)
...
...
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