mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-31 16:07:38 +03:00 
			
		
		
		
	Lots of validator tests passing after refactor
This commit is contained in:
		
							parent
							
								
									136c9b5271
								
							
						
					
					
						commit
						a9df917d10
					
				|  | @ -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): | ||||||
|         #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 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 |  | ||||||
| 
 | 
 | ||||||
|  |         if not form_instance and hasattr(resource, 'get_bound_form'): | ||||||
|             # 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): | ||||||
|                 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: | ||||||
|  |  | ||||||
|  | @ -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): | ||||||
|     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 | ||||||
|  |  | ||||||
|  | @ -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.""" | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| __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.""" | ||||||
| 
 | 
 | ||||||
|  | @ -39,6 +38,9 @@ class Resource(EmitterMixin, AuthMixin, FormValidatorMixin, RequestMixin, View): | ||||||
|                 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 ) | ||||||
|  |  | ||||||
|  | @ -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): | ||||||
| 
 | 
 | ||||||
|     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 | ||||||
| 
 | 
 | ||||||
|  |         view = DisabledFormView() | ||||||
|         content = {'qwerty':'uiop'}        |         content = {'qwerty':'uiop'}        | ||||||
|         self.assertEqual(DisabledFormValidator().get_bound_form(content), None) |         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 DisabledModelFormValidator(ModelFormValidatorMixin): |         class DisabledModelFormView(object): | ||||||
|             form = None |             form = None | ||||||
|  |             model = None | ||||||
|   |   | ||||||
|  |         view = DisabledModelFormView() | ||||||
|         content = {'qwerty':'uiop'}        |         content = {'qwerty':'uiop'}        | ||||||
|         self.assertEqual(DisabledModelFormValidator().get_bound_form(content), None) |         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): | ||||||
|                     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 MockValidator(FormValidatorMixin): |         class MockView(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): | ||||||
| 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. | ||||||
|     (ModelFormValidatorMixin 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 MockFormValidator(FormValidatorMixin): |         class MockFormView(object): | ||||||
|             form = MockForm |             form = MockForm | ||||||
|  |             validators = (FormValidator,) | ||||||
|   |   | ||||||
|         class MockModelFormValidator(ModelFormValidatorMixin): |         class MockModelFormView(object): | ||||||
|             form = MockForm |             form = MockForm | ||||||
|  |             validators = (ModelFormValidator,) | ||||||
|           |           | ||||||
|         self.MockFormValidator = MockFormValidator |         self.MockFormView = MockFormView | ||||||
|         self.MockModelFormValidator = MockModelFormValidator        |         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): | ||||||
|     # 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): | # class TestModelFormValidator(TestCase): | ||||||
|     """Tests specific to ModelFormValidatorMixin""" | #     """Tests specific to ModelFormValidatorMixin""" | ||||||
|      | #      | ||||||
|     def setUp(self): | #     def setUp(self): | ||||||
|         """Create a validator for a model with two fields and a property."""     | #         """Create a validator for a model with two fields and a property."""     | ||||||
|         class MockModel(models.Model): | #         class MockModel(models.Model): | ||||||
|             qwerty = models.CharField(max_length=256) | #             qwerty = models.CharField(max_length=256) | ||||||
|             uiop = models.CharField(max_length=256, blank=True) | #             uiop = models.CharField(max_length=256, blank=True) | ||||||
|              | #              | ||||||
|             @property | #             @property | ||||||
|             def readonly(self): | #             def readonly(self): | ||||||
|                 return 'read only' | #                 return 'read only' | ||||||
|              | #              | ||||||
|         class MockValidator(ModelFormValidatorMixin): | #         class MockValidator(ModelFormValidatorMixin): | ||||||
|             model = MockModel | #             model = MockModel | ||||||
|         | #         | ||||||
|         self.MockValidator = MockValidator | #         self.MockValidator = MockValidator | ||||||
| 
 | #  | ||||||
| 
 | #  | ||||||
|     def test_property_fields_are_allowed_on_model_forms(self): | #     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.""" | #         """Validation on ModelForms may include property fields that exist on the Model to be included in the input.""" | ||||||
|         content = {'qwerty':'example', 'uiop': 'example', 'readonly': 'read only'} | #         content = {'qwerty':'example', 'uiop': 'example', 'readonly': 'read only'} | ||||||
|         self.assertEqual(self.MockValidator().validate(content), content) | #         self.assertEqual(self.MockValidator().validate(content), content) | ||||||
| 
 | #  | ||||||
|     def test_property_fields_are_not_required_on_model_forms(self): | #     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.""" | #         """Validation on ModelForms does not require property fields that exist on the Model to be included in the input.""" | ||||||
|         content = {'qwerty':'example', 'uiop': 'example'} | #         content = {'qwerty':'example', 'uiop': 'example'} | ||||||
|         self.assertEqual(self.MockValidator().validate(content), content) | #         self.assertEqual(self.MockValidator().validate(content), content) | ||||||
| 
 | #  | ||||||
|     def test_extra_fields_not_allowed_on_model_forms(self): | #     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. | #         """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 | #         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)""" | #         broken clients more easily (eg submitting content with a misnamed field)""" | ||||||
|         content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'}  | #         content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'}  | ||||||
|         self.assertRaises(ResponseException, self.MockValidator().validate, content) | #         self.assertRaises(ResponseException, self.MockValidator().validate, content) | ||||||
|      | #      | ||||||
|     def test_validate_requires_fields_on_model_forms(self): | #     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. | #         """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 | #         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)""" | #         broken clients more easily (eg submitting content with a misnamed field)""" | ||||||
|         content = {'readonly': 'read only'}  | #         content = {'readonly': 'read only'}  | ||||||
|         self.assertRaises(ResponseException, self.MockValidator().validate, content) | #         self.assertRaises(ResponseException, self.MockValidator().validate, content) | ||||||
|      | #      | ||||||
|     def test_validate_does_not_require_blankable_fields_on_model_forms(self): | #     def test_validate_does_not_require_blankable_fields_on_model_forms(self): | ||||||
|         """Test standard ModelForm validation behaviour - fields with blank=True are not required.""" | #         """Test standard ModelForm validation behaviour - fields with blank=True are not required.""" | ||||||
|         content = {'qwerty':'example', 'readonly': 'read only'} | #         content = {'qwerty':'example', 'readonly': 'read only'} | ||||||
|         self.MockValidator().validate(content) | #         self.MockValidator().validate(content) | ||||||
|      | #      | ||||||
|     def test_model_form_validator_uses_model_forms(self): | #     def test_model_form_validator_uses_model_forms(self): | ||||||
|         self.assertTrue(isinstance(self.MockValidator().get_bound_form(), forms.ModelForm)) | #         self.assertTrue(isinstance(self.MockValidator().get_bound_form(), forms.ModelForm)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|         Raises a ResponseException with status code 400 (Bad Request) on failure. |         Typically raises 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 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.""" | ||||||
| 
 | 
 | ||||||
|     """The form class that should be used for validation, or None to turn off form validation.""" |  | ||||||
|     form = None |  | ||||||
|     bound_form_instance = None |  | ||||||
| 
 | 
 | ||||||
|     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): | ||||||
|         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): | ||||||
|                 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): | ||||||
|     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): | ||||||
|         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) | ||||||
|             # Use explict Form |         model_cls = getattr(self.view, 'model', None) | ||||||
|             return super(ModelFormValidatorMixin, self).get_bound_form(content) |  | ||||||
| 
 | 
 | ||||||
|         elif self.model: |         if form_cls: | ||||||
|  |             # Use explict Form | ||||||
|  |             return super(ModelFormValidator, self).get_bound_form(content) | ||||||
|  | 
 | ||||||
|  |         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): | ||||||
|     @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) | ||||||
| 
 | 
 | ||||||
|         if self.fields: |         model_fields = set(field.name for field in model._meta.fields) | ||||||
|             return model_fields & set(as_tuple(self.fields)) |  | ||||||
| 
 | 
 | ||||||
|         return model_fields - set(as_tuple(self.exclude_fields)) |         if fields: | ||||||
|  |             return model_fields & set(as_tuple(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)) | ||||||
|      |      | ||||||
| 
 | 
 | ||||||
|      |      | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user