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
a9df917d
Commit
a9df917d
authored
Apr 11, 2011
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Lots of validator tests passing after refactor
parent
136c9b52
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
239 additions
and
166 deletions
+239
-166
djangorestframework/emitters.py
+9
-14
djangorestframework/modelresource.py
+5
-3
djangorestframework/request.py
+23
-0
djangorestframework/resource.py
+6
-4
djangorestframework/tests/validators.py
+140
-110
djangorestframework/validators.py
+56
-35
No files found.
djangorestframework/emitters.py
View file @
a9df917d
...
@@ -9,7 +9,6 @@ from django.template import RequestContext, loader
...
@@ -9,7 +9,6 @@ from django.template import RequestContext, loader
from
django
import
forms
from
django
import
forms
from
djangorestframework.response
import
NoContent
,
ResponseException
from
djangorestframework.response
import
NoContent
,
ResponseException
from
djangorestframework.validators
import
FormValidatorMixin
from
djangorestframework.utils
import
dict2xml
,
url_resolves
from
djangorestframework.utils
import
dict2xml
,
url_resolves
from
djangorestframework.markdownwrapper
import
apply_markdown
from
djangorestframework.markdownwrapper
import
apply_markdown
from
djangorestframework.breadcrumbs
import
get_breadcrumbs
from
djangorestframework.breadcrumbs
import
get_breadcrumbs
...
@@ -217,15 +216,11 @@ class DocumentingTemplateEmitter(BaseEmitter):
...
@@ -217,15 +216,11 @@ class DocumentingTemplateEmitter(BaseEmitter):
#form_instance = resource.form_instance
#form_instance = resource.form_instance
# TODO! Reinstate this
# TODO! Reinstate this
form_instance
=
None
form_instance
=
getattr
(
resource
,
'bound_form_instance'
,
None
)
if
isinstance
(
resource
,
FormValidatorMixin
):
if
not
form_instance
and
hasattr
(
resource
,
'get_bound_form'
):
# If we already have a bound form instance (IE provided by the input parser, then use that)
if
resource
.
bound_form_instance
is
not
None
:
form_instance
=
resource
.
bound_form_instance
# Otherwise if we have a response that is valid against the form then use that
# Otherwise if we have a response that is valid against the form then use that
if
not
form_instance
and
resource
.
response
.
has_content_body
:
if
resource
.
response
.
has_content_body
:
try
:
try
:
form_instance
=
resource
.
get_bound_form
(
resource
.
response
.
cleaned_content
)
form_instance
=
resource
.
get_bound_form
(
resource
.
response
.
cleaned_content
)
if
form_instance
and
not
form_instance
.
is_valid
():
if
form_instance
and
not
form_instance
.
is_valid
():
...
@@ -233,12 +228,12 @@ class DocumentingTemplateEmitter(BaseEmitter):
...
@@ -233,12 +228,12 @@ class DocumentingTemplateEmitter(BaseEmitter):
except
:
except
:
form_instance
=
None
form_instance
=
None
# If we still don't have a form instance then try to get an unbound form
# If we still don't have a form instance then try to get an unbound form
if
not
form_instance
:
if
not
form_instance
:
try
:
try
:
form_instance
=
resource
.
get_bound_form
()
form_instance
=
resource
.
get_bound_form
()
except
:
except
:
pass
pass
# If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types
# If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types
if
not
form_instance
:
if
not
form_instance
:
...
...
djangorestframework/modelresource.py
View file @
a9df917d
...
@@ -5,15 +5,14 @@ from django.db.models.fields.related import RelatedField
...
@@ -5,15 +5,14 @@ from django.db.models.fields.related import RelatedField
from
djangorestframework.response
import
Response
,
ResponseException
from
djangorestframework.response
import
Response
,
ResponseException
from
djangorestframework.resource
import
Resource
from
djangorestframework.resource
import
Resource
from
djangorestframework.validators
import
ModelFormValidatorMixin
from
djangorestframework
import
status
,
validators
from
djangorestframework
import
status
import
decimal
import
decimal
import
inspect
import
inspect
import
re
import
re
class
ModelResource
(
Resource
,
ModelFormValidatorMixin
):
class
ModelResource
(
Resource
):
"""A specialized type of Resource, for resources that map directly to a Django Model.
"""A specialized type of Resource, for resources that map directly to a Django Model.
Useful things this provides:
Useful things this provides:
...
@@ -21,6 +20,9 @@ class ModelResource(Resource, ModelFormValidatorMixin):
...
@@ -21,6 +20,9 @@ class ModelResource(Resource, ModelFormValidatorMixin):
1. Nice serialization of returned Models and QuerySets.
1. Nice serialization of returned Models and QuerySets.
2. A default set of create/read/update/delete operations."""
2. A default set of create/read/update/delete operations."""
# List of validators to validate, cleanup and type-ify the request content
validators
=
(
validators
.
ModelFormValidator
,)
# 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.
# (The Model's class, rather than an instance of the Model)
# (The Model's class, rather than an instance of the Model)
model
=
None
model
=
None
...
...
djangorestframework/request.py
View file @
a9df917d
...
@@ -196,6 +196,29 @@ class RequestMixin(object):
...
@@ -196,6 +196,29 @@ class RequestMixin(object):
return
parser
.
parse
(
stream
)
return
parser
.
parse
(
stream
)
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
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
@property
def
parsed_media_types
(
self
):
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."""
...
...
djangorestframework/resource.py
View file @
a9df917d
...
@@ -3,10 +3,9 @@ from django.views.decorators.csrf import csrf_exempt
...
@@ -3,10 +3,9 @@ from django.views.decorators.csrf import csrf_exempt
from
djangorestframework.compat
import
View
from
djangorestframework.compat
import
View
from
djangorestframework.emitters
import
EmitterMixin
from
djangorestframework.emitters
import
EmitterMixin
from
djangorestframework.validators
import
FormValidatorMixin
from
djangorestframework.response
import
Response
,
ResponseException
from
djangorestframework.response
import
Response
,
ResponseException
from
djangorestframework.request
import
RequestMixin
,
AuthMixin
from
djangorestframework.request
import
RequestMixin
,
AuthMixin
from
djangorestframework
import
emitters
,
parsers
,
authenticators
,
status
from
djangorestframework
import
emitters
,
parsers
,
authenticators
,
validators
,
status
# TODO: Figure how out references and named urls need to work nicely
# TODO: Figure how out references and named urls need to work nicely
...
@@ -17,7 +16,7 @@ from djangorestframework import emitters, parsers, authenticators, status
...
@@ -17,7 +16,7 @@ from djangorestframework import emitters, parsers, authenticators, status
__all__
=
[
'Resource'
]
__all__
=
[
'Resource'
]
class
Resource
(
EmitterMixin
,
AuthMixin
,
FormValidatorMixin
,
RequestMixin
,
View
):
class
Resource
(
EmitterMixin
,
AuthMixin
,
RequestMixin
,
View
):
"""Handles incoming requests and maps them to REST operations,
"""Handles incoming requests and maps them to REST operations,
performing authentication, input deserialization, input validation, output serialization."""
performing authentication, input deserialization, input validation, output serialization."""
...
@@ -38,7 +37,10 @@ class Resource(EmitterMixin, AuthMixin, FormValidatorMixin, RequestMixin, View):
...
@@ -38,7 +37,10 @@ class Resource(EmitterMixin, AuthMixin, FormValidatorMixin, RequestMixin, View):
parsers
=
(
parsers
.
JSONParser
,
parsers
=
(
parsers
.
JSONParser
,
parsers
.
FormParser
,
parsers
.
FormParser
,
parsers
.
MultipartParser
)
parsers
.
MultipartParser
)
# List of validators to validate, cleanup and type-ify the request content
validators
=
(
validators
.
FormValidator
,)
# List of all authenticating methods to attempt.
# List of all authenticating methods to attempt.
authenticators
=
(
authenticators
.
UserLoggedInAuthenticator
,
authenticators
=
(
authenticators
.
UserLoggedInAuthenticator
,
authenticators
.
BasicAuthenticator
)
authenticators
.
BasicAuthenticator
)
...
...
djangorestframework/tests/validators.py
View file @
a9df917d
...
@@ -2,7 +2,7 @@ from django import forms
...
@@ -2,7 +2,7 @@ from django import forms
from
django.db
import
models
from
django.db
import
models
from
django.test
import
TestCase
from
django.test
import
TestCase
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.validators
import
ValidatorMixin
,
FormValidatorMixin
,
ModelFormValidatorMixin
from
djangorestframework.validators
import
BaseValidator
,
FormValidator
,
ModelFormValidator
from
djangorestframework.response
import
ResponseException
from
djangorestframework.response
import
ResponseException
...
@@ -11,59 +11,68 @@ class TestValidatorMixinInterfaces(TestCase):
...
@@ -11,59 +11,68 @@ class TestValidatorMixinInterfaces(TestCase):
def
test_validator_mixin_interface
(
self
):
def
test_validator_mixin_interface
(
self
):
"""Ensure the ValidatorMixin base class interface is as expected."""
"""Ensure the ValidatorMixin base class interface is as expected."""
self
.
assertRaises
(
NotImplementedError
,
ValidatorMixin
(
)
.
validate
,
None
)
self
.
assertRaises
(
NotImplementedError
,
BaseValidator
(
None
)
.
validate
,
None
)
def
test_form_validator_mixin_interface
(
self
):
#
def test_form_validator_mixin_interface(self):
"""Ensure the FormValidatorMixin interface is as expected."""
#
"""Ensure the FormValidatorMixin interface is as expected."""
self
.
assertTrue
(
issubclass
(
FormValidatorMixin
,
ValidatorMixin
))
# self.assertTrue(issubclass(FormValidator, BaseValidator
))
getattr
(
FormValidatorMixin
,
'form'
)
# getattr(FormValidator
, 'form')
getattr
(
FormValidatorMixin
,
'validate'
)
# getattr(FormValidator
, 'validate')
def
test_model_form_validator_mixin_interface
(
self
):
#
def test_model_form_validator_mixin_interface(self):
"""Ensure the ModelFormValidatorMixin interface is as expected."""
#
"""Ensure the ModelFormValidatorMixin interface is as expected."""
self
.
assertTrue
(
issubclass
(
ModelFormValidatorMixin
,
FormValidatorMixin
))
# self.assertTrue(issubclass(ModelFormValidator, FormValidator
))
getattr
(
ModelFormValidatorMixin
,
'model'
)
# getattr(ModelFormValidator
, 'model')
getattr
(
ModelFormValidatorMixin
,
'form'
)
# getattr(ModelFormValidator
, 'form')
getattr
(
ModelFormValidatorMixin
,
'fields'
)
# getattr(ModelFormValidator
, 'fields')
getattr
(
ModelFormValidatorMixin
,
'exclude_fields'
)
# getattr(ModelFormValidator
, 'exclude_fields')
getattr
(
ModelFormValidatorMixin
,
'validate'
)
# getattr(ModelFormValidator
, 'validate')
class
TestDisabledValidations
(
TestCase
):
class
TestDisabledValidations
(
TestCase
):
"""Tests on
Validator Mixins
with validation disabled by setting form to None"""
"""Tests on
FormValidator
with validation disabled by setting form to None"""
def
test_disabled_form_validator_returns_content_unchanged
(
self
):
def
test_disabled_form_validator_returns_content_unchanged
(
self
):
"""If the form attribute is None on FormValidatorMixin then validate(content) should just return the content unmodified."""
"""If the view's form attribute is None then FormValidator(view).validate(content)
class
DisabledFormValidator
(
FormValidatorMixin
):
should just return the content unmodified."""
class
DisabledFormView
(
object
):
form
=
None
form
=
None
view
=
DisabledFormView
()
content
=
{
'qwerty'
:
'uiop'
}
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
DisabledFormValidator
(
)
.
validate
(
content
),
content
)
self
.
assertEqual
(
FormValidator
(
view
)
.
validate
(
content
),
content
)
def
test_disabled_form_validator_get_bound_form_returns_none
(
self
):
def
test_disabled_form_validator_get_bound_form_returns_none
(
self
):
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
"""If the view's form attribute is None on then
class
DisabledFormValidator
(
FormValidatorMixin
):
FormValidator(view).get_bound_form(content) should just return None."""
class
DisabledFormView
(
object
):
form
=
None
form
=
None
content
=
{
'qwerty'
:
'uiop'
}
view
=
DisabledFormView
()
self
.
assertEqual
(
DisabledFormValidator
()
.
get_bound_form
(
content
),
None
)
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
FormValidator
(
view
)
.
get_bound_form
(
content
),
None
)
def
test_disabled_model_form_validator_returns_content_unchanged
(
self
):
def
test_disabled_model_form_validator_returns_content_unchanged
(
self
):
"""If the form attribute is None on FormValidatorMixin then validate(content) should just return the content unmodified."""
"""If the view's form and model attributes are None then
class
DisabledModelFormValidator
(
ModelFormValidatorMixin
):
ModelFormValidator(view).validate(content) should just return the content unmodified."""
class
DisabledModelFormView
(
object
):
form
=
None
form
=
None
model
=
None
view
=
DisabledModelFormView
()
content
=
{
'qwerty'
:
'uiop'
}
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
DisabledModelFormValidator
()
.
validate
(
content
),
content
)
self
.
assertEqual
(
ModelFormValidator
(
view
)
.
get_bound_form
(
content
),
None
)
#
def
test_disabled_model_form_validator_get_bound_form_returns_none
(
self
):
def
test_disabled_model_form_validator_get_bound_form_returns_none
(
self
):
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
class
DisabledModelFormV
alidator
(
ModelFormValidatorMixin
):
class
DisabledModelFormV
iew
(
object
):
form
=
None
form
=
None
model
=
None
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
DisabledModelFormValidator
()
.
get_bound_form
(
content
),
None
)
view
=
DisabledModelFormView
()
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
ModelFormValidator
(
view
)
.
get_bound_form
(
content
),
None
)
#
class
TestNonFieldErrors
(
TestCase
):
class
TestNonFieldErrors
(
TestCase
):
"""Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
"""Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
...
@@ -80,12 +89,13 @@ class TestNonFieldErrors(TestCase):
...
@@ -80,12 +89,13 @@ class TestNonFieldErrors(TestCase):
raise
forms
.
ValidationError
(
self
.
ERROR_TEXT
)
raise
forms
.
ValidationError
(
self
.
ERROR_TEXT
)
return
self
.
cleaned_data
#pragma: no cover
return
self
.
cleaned_data
#pragma: no cover
class
MockV
alidator
(
FormValidatorMixin
):
class
MockV
iew
(
object
):
form
=
MockForm
form
=
MockForm
view
=
MockView
()
content
=
{
'field1'
:
'example1'
,
'field2'
:
'example2'
}
content
=
{
'field1'
:
'example1'
,
'field2'
:
'example2'
}
try
:
try
:
MockValidator
(
)
.
validate
(
content
)
FormValidator
(
view
)
.
validate
(
content
)
except
ResponseException
,
exc
:
except
ResponseException
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'errors'
:
[
MockForm
.
ERROR_TEXT
]})
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'errors'
:
[
MockForm
.
ERROR_TEXT
]})
else
:
else
:
...
@@ -95,19 +105,21 @@ class TestNonFieldErrors(TestCase):
...
@@ -95,19 +105,21 @@ class TestNonFieldErrors(TestCase):
class
TestFormValidation
(
TestCase
):
class
TestFormValidation
(
TestCase
):
"""Tests which check basic form validation.
"""Tests which check basic form validation.
Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set.
Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set.
(ModelFormValidator
Mixin should behave as FormValidatorMixin if
form is set rather than relying on the default ModelForm)"""
(ModelFormValidator
should behave as FormValidator if a
form is set rather than relying on the default ModelForm)"""
def
setUp
(
self
):
def
setUp
(
self
):
class
MockForm
(
forms
.
Form
):
class
MockForm
(
forms
.
Form
):
qwerty
=
forms
.
CharField
(
required
=
True
)
qwerty
=
forms
.
CharField
(
required
=
True
)
class
MockFormV
alidator
(
FormValidatorMixin
):
class
MockFormV
iew
(
object
):
form
=
MockForm
form
=
MockForm
validators
=
(
FormValidator
,)
class
MockModelFormValidator
(
ModelFormValidatorMixin
):
class
MockModelFormView
(
object
):
form
=
MockForm
form
=
MockForm
validators
=
(
ModelFormValidator
,)
self
.
MockFormValidator
=
MockFormValidator
self
.
MockModelFormValidator
=
MockModelFormValidator
self
.
MockFormView
=
MockFormView
self
.
MockModelFormView
=
MockModelFormView
def
validation_returns_content_unchanged_if_already_valid_and_clean
(
self
,
validator
):
def
validation_returns_content_unchanged_if_already_valid_and_clean
(
self
,
validator
):
...
@@ -181,111 +193,129 @@ class TestFormValidation(TestCase):
...
@@ -181,111 +193,129 @@ class TestFormValidation(TestCase):
# Tests on FormValidtionMixin
# Tests on FormValidtionMixin
def
test_form_validation_returns_content_unchanged_if_already_valid_and_clean
(
self
):
def
test_form_validation_returns_content_unchanged_if_already_valid_and_clean
(
self
):
self
.
validation_returns_content_unchanged_if_already_valid_and_clean
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_returns_content_unchanged_if_already_valid_and_clean
(
validator
)
def
test_form_validation_failure_raises_response_exception
(
self
):
def
test_form_validation_failure_raises_response_exception
(
self
):
self
.
validation_failure_raises_response_exception
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_failure_raises_response_exception
(
validator
)
def
test_validation_does_not_allow_extra_fields_by_default
(
self
):
def
test_validation_does_not_allow_extra_fields_by_default
(
self
):
self
.
validation_does_not_allow_extra_fields_by_default
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_does_not_allow_extra_fields_by_default
(
validator
)
def
test_validation_allows_extra_fields_if_explicitly_set
(
self
):
def
test_validation_allows_extra_fields_if_explicitly_set
(
self
):
self
.
validation_allows_extra_fields_if_explicitly_set
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_allows_extra_fields_if_explicitly_set
(
validator
)
def
test_validation_does_not_require_extra_fields_if_explicitly_set
(
self
):
def
test_validation_does_not_require_extra_fields_if_explicitly_set
(
self
):
self
.
validation_does_not_require_extra_fields_if_explicitly_set
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_does_not_require_extra_fields_if_explicitly_set
(
validator
)
def
test_validation_failed_due_to_no_content_returns_appropriate_message
(
self
):
def
test_validation_failed_due_to_no_content_returns_appropriate_message
(
self
):
self
.
validation_failed_due_to_no_content_returns_appropriate_message
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_failed_due_to_no_content_returns_appropriate_message
(
validator
)
def
test_validation_failed_due_to_field_error_returns_appropriate_message
(
self
):
def
test_validation_failed_due_to_field_error_returns_appropriate_message
(
self
):
self
.
validation_failed_due_to_field_error_returns_appropriate_message
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_failed_due_to_field_error_returns_appropriate_message
(
validator
)
def
test_validation_failed_due_to_invalid_field_returns_appropriate_message
(
self
):
def
test_validation_failed_due_to_invalid_field_returns_appropriate_message
(
self
):
self
.
validation_failed_due_to_invalid_field_returns_appropriate_message
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_failed_due_to_invalid_field_returns_appropriate_message
(
validator
)
def
test_validation_failed_due_to_multiple_errors_returns_appropriate_message
(
self
):
def
test_validation_failed_due_to_multiple_errors_returns_appropriate_message
(
self
):
self
.
validation_failed_due_to_multiple_errors_returns_appropriate_message
(
self
.
MockFormValidator
())
validator
=
FormValidator
(
self
.
MockFormView
())
self
.
validation_failed_due_to_multiple_errors_returns_appropriate_message
(
validator
)
# Same tests on ModelFormValidtionMixin
# Same tests on ModelFormValidtionMixin
def
test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean
(
self
):
def
test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean
(
self
):
self
.
validation_returns_content_unchanged_if_already_valid_and_clean
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_returns_content_unchanged_if_already_valid_and_clean
(
validator
)
def
test_modelform_validation_failure_raises_response_exception
(
self
):
def
test_modelform_validation_failure_raises_response_exception
(
self
):
self
.
validation_failure_raises_response_exception
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_failure_raises_response_exception
(
validator
)
def
test_modelform_validation_does_not_allow_extra_fields_by_default
(
self
):
def
test_modelform_validation_does_not_allow_extra_fields_by_default
(
self
):
self
.
validation_does_not_allow_extra_fields_by_default
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_does_not_allow_extra_fields_by_default
(
validator
)
def
test_modelform_validation_allows_extra_fields_if_explicitly_set
(
self
):
def
test_modelform_validation_allows_extra_fields_if_explicitly_set
(
self
):
self
.
validation_allows_extra_fields_if_explicitly_set
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_allows_extra_fields_if_explicitly_set
(
validator
)
def
test_modelform_validation_does_not_require_extra_fields_if_explicitly_set
(
self
):
def
test_modelform_validation_does_not_require_extra_fields_if_explicitly_set
(
self
):
self
.
validation_does_not_require_extra_fields_if_explicitly_set
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_does_not_require_extra_fields_if_explicitly_set
(
validator
)
def
test_modelform_validation_failed_due_to_no_content_returns_appropriate_message
(
self
):
def
test_modelform_validation_failed_due_to_no_content_returns_appropriate_message
(
self
):
self
.
validation_failed_due_to_no_content_returns_appropriate_message
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_failed_due_to_no_content_returns_appropriate_message
(
validator
)
def
test_modelform_validation_failed_due_to_field_error_returns_appropriate_message
(
self
):
def
test_modelform_validation_failed_due_to_field_error_returns_appropriate_message
(
self
):
self
.
validation_failed_due_to_field_error_returns_appropriate_message
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_failed_due_to_field_error_returns_appropriate_message
(
validator
)
def
test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message
(
self
):
def
test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message
(
self
):
self
.
validation_failed_due_to_invalid_field_returns_appropriate_message
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_failed_due_to_invalid_field_returns_appropriate_message
(
validator
)
def
test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message
(
self
):
def
test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message
(
self
):
self
.
validation_failed_due_to_multiple_errors_returns_appropriate_message
(
self
.
MockModelFormValidator
())
validator
=
ModelFormValidator
(
self
.
MockModelFormView
())
self
.
validation_failed_due_to_multiple_errors_returns_appropriate_message
(
validator
)
class
TestModelFormValidator
(
TestCase
):
"""Tests specific to ModelFormValidatorMixin"""
# class TestModelFormValidator(TestCase):
# """Tests specific to ModelFormValidatorMixin"""
def
setUp
(
self
):
#
"""Create a validator for a model with two fields and a property."""
# def setUp(self):
class
MockModel
(
models
.
Model
):
# """Create a validator for a model with two fields and a property."""
qwerty
=
models
.
CharField
(
max_length
=
256
)
# class MockModel(models.Model):
uiop
=
models
.
CharField
(
max_length
=
256
,
blank
=
True
)
# qwerty = models.CharField(max_length=256)
# uiop = models.CharField(max_length=256, blank=True)
@property
#
def
readonly
(
self
):
# @property
return
'read only'
# def readonly(self):
# return 'read only'
class
MockValidator
(
ModelFormValidatorMixin
):
#
model
=
MockModel
# class MockValidator(ModelFormValidatorMixin):
# model = MockModel
self
.
MockValidator
=
MockValidator
#
# self.MockValidator = MockValidator
#
def
test_property_fields_are_allowed_on_model_forms
(
self
):
#
"""Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
# def test_property_fields_are_allowed_on_model_forms(self):
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
,
'readonly'
:
'read only'
}
# """Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
self
.
assertEqual
(
self
.
MockValidator
()
.
validate
(
content
),
content
)
# content = {'qwerty':'example', 'uiop': 'example', 'readonly': 'read only'}
# self.assertEqual(self.MockValidator().validate(content), content)
def
test_property_fields_are_not_required_on_model_forms
(
self
):
#
"""Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
# def test_property_fields_are_not_required_on_model_forms(self):
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
}
# """Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
self
.
assertEqual
(
self
.
MockValidator
()
.
validate
(
content
),
content
)
# content = {'qwerty':'example', 'uiop': 'example'}
# self.assertEqual(self.MockValidator().validate(content), content)
def
test_extra_fields_not_allowed_on_model_forms
(
self
):
#
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
# def test_extra_fields_not_allowed_on_model_forms(self):
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
# """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
broken clients more easily (eg submitting content with a misnamed field)"""
# It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
,
'readonly'
:
'read only'
,
'extra'
:
'extra'
}
# broken clients more easily (eg submitting content with a misnamed field)"""
self
.
assertRaises
(
ResponseException
,
self
.
MockValidator
()
.
validate
,
content
)
# content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'}
# self.assertRaises(ResponseException, self.MockValidator().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.
# def test_validate_requires_fields_on_model_forms(self):
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
# """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
broken clients more easily (eg submitting content with a misnamed field)"""
# It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
content
=
{
'readonly'
:
'read only'
}
# broken clients more easily (eg submitting content with a misnamed field)"""
self
.
assertRaises
(
ResponseException
,
self
.
MockValidator
()
.
validate
,
content
)
# content = {'readonly': 'read only'}
# self.assertRaises(ResponseException, self.MockValidator().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."""
# def test_validate_does_not_require_blankable_fields_on_model_forms(self):
content
=
{
'qwerty'
:
'example'
,
'readonly'
:
'read only'
}
# """Test standard ModelForm validation behaviour - fields with blank=True are not required."""
self
.
MockValidator
()
.
validate
(
content
)
# content = {'qwerty':'example', 'readonly': 'read only'}
# self.MockValidator().validate(content)
def
test_model_form_validator_uses_model_forms
(
self
):
#
self
.
assertTrue
(
isinstance
(
self
.
MockValidator
()
.
get_bound_form
(),
forms
.
ModelForm
))
# def test_model_form_validator_uses_model_forms(self):
# self.assertTrue(isinstance(self.MockValidator().get_bound_form(), forms.ModelForm))
djangorestframework/validators.py
View file @
a9df917d
...
@@ -4,25 +4,31 @@ from django.db import models
...
@@ -4,25 +4,31 @@ from django.db import models
from
djangorestframework.response
import
ResponseException
from
djangorestframework.response
import
ResponseException
from
djangorestframework.utils
import
as_tuple
from
djangorestframework.utils
import
as_tuple
class
ValidatorMixin
(
object
):
"""Base class for all ValidatorMixin classes, which simply defines the interface they provide."""
class
BaseValidator
(
object
):
"""Base class for all Validator classes, which simply defines the interface they provide."""
def
__init__
(
self
,
view
):
self
.
view
=
view
def
validate
(
self
,
content
):
def
validate
(
self
,
content
):
"""Given some content as input return some cleaned, validated content.
"""Given some content as input return some cleaned, validated content.
R
aises a ResponseException with status code 400 (Bad Request) on failure.
Typically r
aises a ResponseException with status code 400 (Bad Request) on failure.
Must be overridden to be implemented."""
Must be overridden to be implemented."""
raise
NotImplementedError
()
raise
NotImplementedError
()
class
FormValidatorMixin
(
ValidatorMixin
):
class
FormValidator
(
BaseValidator
):
"""Validator Mixin that uses forms for validation.
"""Validator class that uses forms for validation.
Extends the ValidatorMixin interface to also provide a get_bound_form() method.
Also provides a get_bound_form() method which may be used by some renderers.
(Which may be used by some emitters.)"""
"""The form class that should be used for validation, or None to turn off form validation."""
The view class should provide `.form` attribute which specifies the form classmethod
form
=
None
to be used for validation.
bound_form_instance
=
None
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
,
content
):
def
validate
(
self
,
content
):
"""Given some content as input return some cleaned, validated content.
"""Given some content as input return some cleaned, validated content.
...
@@ -44,7 +50,7 @@ class FormValidatorMixin(ValidatorMixin):
...
@@ -44,7 +50,7 @@ class FormValidatorMixin(ValidatorMixin):
if
bound_form
is
None
:
if
bound_form
is
None
:
return
content
return
content
self
.
bound_form_instance
=
bound_form
self
.
view
.
bound_form_instance
=
bound_form
seen_fields_set
=
set
(
content
.
keys
())
seen_fields_set
=
set
(
content
.
keys
())
form_fields_set
=
set
(
bound_form
.
fields
.
keys
())
form_fields_set
=
set
(
bound_form
.
fields
.
keys
())
...
@@ -78,7 +84,10 @@ class FormValidatorMixin(ValidatorMixin):
...
@@ -78,7 +84,10 @@ class FormValidatorMixin(ValidatorMixin):
detail
[
u'errors'
]
=
bound_form
.
non_field_errors
()
detail
[
u'errors'
]
=
bound_form
.
non_field_errors
()
# Add standard 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
(
'__'
))
field_errors
=
dict
((
key
,
map
(
unicode
,
val
))
for
(
key
,
val
)
in
bound_form
.
errors
.
iteritems
()
if
not
key
.
startswith
(
'__'
))
# Add any unknown field errors
# Add any unknown field errors
for
key
in
unknown_fields
:
for
key
in
unknown_fields
:
...
@@ -94,20 +103,21 @@ class FormValidatorMixin(ValidatorMixin):
...
@@ -94,20 +103,21 @@ class FormValidatorMixin(ValidatorMixin):
def
get_bound_form
(
self
,
content
=
None
):
def
get_bound_form
(
self
,
content
=
None
):
"""Given some content return a Django form bound to that content.
"""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."""
If form validation is turned off (form class attribute is None) then returns None."""
if
not
self
.
form
:
form_cls
=
getattr
(
self
.
view
,
'form'
,
None
)
if
not
form_cls
:
return
None
return
None
if
not
content
is
None
:
if
content
is
not
None
:
if
hasattr
(
content
,
'FILES'
):
if
hasattr
(
content
,
'FILES'
):
return
self
.
form
(
content
,
content
.
FILES
)
return
form_cls
(
content
,
content
.
FILES
)
return
self
.
form
(
content
)
return
form_cls
(
content
)
return
self
.
form
()
return
form_cls
()
class
ModelFormValidatorMixin
(
FormValidatorMixin
):
class
ModelFormValidator
(
FormValidator
):
"""Validator Mixin that uses forms for validation and falls back to a model form if no form is set.
"""Validator class that uses forms for validation and otherwise falls back to a model form if no form is set.
Extends the ValidatorMixin interface to also provide a get_bound_form() method.
Also provides a get_bound_form() method which may be used by some renderers."""
(Which may be used by some emitters.)"""
"""The form class that should be used for validation, or None to use model form validation."""
"""The form class that should be used for validation, or None to use model form validation."""
form
=
None
form
=
None
...
@@ -148,15 +158,18 @@ class ModelFormValidatorMixin(FormValidatorMixin):
...
@@ -148,15 +158,18 @@ class ModelFormValidatorMixin(FormValidatorMixin):
If the form class attribute has been explicitly set then use that class to create a Form,
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."""
otherwise if model is set use that class to create a ModelForm, otherwise return None."""
if
self
.
form
:
form_cls
=
getattr
(
self
.
view
,
'form'
,
None
)
model_cls
=
getattr
(
self
.
view
,
'model'
,
None
)
if
form_cls
:
# Use explict Form
# Use explict Form
return
super
(
ModelFormValidator
Mixin
,
self
)
.
get_bound_form
(
content
)
return
super
(
ModelFormValidator
,
self
)
.
get_bound_form
(
content
)
elif
self
.
model
:
elif
model_cls
:
# Fall back to ModelForm which we create on the fly
# Fall back to ModelForm which we create on the fly
class
OnTheFlyModelForm
(
forms
.
ModelForm
):
class
OnTheFlyModelForm
(
forms
.
ModelForm
):
class
Meta
:
class
Meta
:
model
=
self
.
model
model
=
model_cls
#fields = tuple(self._model_fields_set)
#fields = tuple(self._model_fields_set)
# Instantiate the ModelForm as appropriate
# Instantiate the ModelForm as appropriate
...
@@ -176,24 +189,32 @@ class ModelFormValidatorMixin(FormValidatorMixin):
...
@@ -176,24 +189,32 @@ class ModelFormValidatorMixin(FormValidatorMixin):
@property
@property
def
_model_fields_set
(
self
):
def
_model_fields_set
(
self
):
"""Return a set containing the names of validated fields on the model."""
"""Return a set containing the names of validated fields on the model."""
model_fields
=
set
(
field
.
name
for
field
in
self
.
model
.
_meta
.
fields
)
model
=
getattr
(
self
.
view
,
'model'
,
None
)
fields
=
getattr
(
self
.
view
,
'fields'
,
self
.
fields
)
exclude_fields
=
getattr
(
self
.
view
,
'exclude_fields'
,
self
.
exclude_fields
)
model_fields
=
set
(
field
.
name
for
field
in
model
.
_meta
.
fields
)
if
self
.
fields
:
if
fields
:
return
model_fields
&
set
(
as_tuple
(
self
.
fields
))
return
model_fields
&
set
(
as_tuple
(
fields
))
return
model_fields
-
set
(
as_tuple
(
self
.
exclude_fields
))
return
model_fields
-
set
(
as_tuple
(
exclude_fields
))
@property
@property
def
_property_fields_set
(
self
):
def
_property_fields_set
(
self
):
"""Returns a set containing the names of validated properties on the model."""
"""Returns a set containing the names of validated properties on the model."""
property_fields
=
set
(
attr
for
attr
in
dir
(
self
.
model
)
if
model
=
getattr
(
self
.
view
,
'model'
,
None
)
isinstance
(
getattr
(
self
.
model
,
attr
,
None
),
property
)
fields
=
getattr
(
self
.
view
,
'fields'
,
self
.
fields
)
exclude_fields
=
getattr
(
self
.
view
,
'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
(
'_'
))
and
not
attr
.
startswith
(
'_'
))
if
self
.
fields
:
if
fields
:
return
property_fields
&
set
(
as_tuple
(
self
.
fields
))
return
property_fields
&
set
(
as_tuple
(
fields
))
return
property_fields
-
set
(
as_tuple
(
self
.
exclude_fields
))
return
property_fields
-
set
(
as_tuple
(
exclude_fields
))
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