Add cross-field validate method

This commit is contained in:
Jamie Matthews 2012-10-24 11:39:17 +01:00
parent 388a807f64
commit ac2d39892d
3 changed files with 42 additions and 3 deletions

View File

@ -76,9 +76,7 @@ Deserialization is similar. First we parse a stream into python native datatype
When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages.
**TODO: Describe validation in more depth**
## Custom field validation
### Field-level validation
You can specify custom field-level validation by adding `validate_<fieldname>()` methods to your `Serializer` subclass. These are analagous to `clean_<fieldname>` methods on Django forms, but accept slightly different arguments. They take a dictionary of deserialized data as a first argument, and the field name in that data as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided). Your `validate_<fieldname>` methods should either just return the data dictionary or raise a `ValidationError`. For example:
@ -97,6 +95,10 @@ You can specify custom field-level validation by adding `validate_<fieldname>()`
raise serializers.ValidationError("Blog post is not about Django")
return data
### Final cross-field validation
To do any other validation that requires access to multiple fields, add a method called `validate` to your `Serializer` subclass. This method takes a single argument, which is the `attrs` dictionary. It should raise a `ValidationError` if necessary, or just return `attrs`.
## Dealing with nested objects
The previous example is fine for dealing with objects that only have simple datatypes, but sometimes we also need to be able to represent more complex objects,

View File

@ -225,6 +225,18 @@ class BaseSerializer(Field):
return data
def clean_all(self, attrs):
"""
Run the `validate` method on the serializer, if it exists
"""
try:
validate_method = getattr(self, 'validate', None)
if validate_method:
attrs = validate_method(attrs)
except ValidationError as err:
self._errors['non_field_errors'] = err.messages
return attrs
def restore_object(self, attrs, instance=None):
"""
Deserialize a dictionary of attributes into an object instance.
@ -259,6 +271,7 @@ class BaseSerializer(Field):
if data is not None:
attrs = self.restore_fields(data)
attrs = self.clean_fields(attrs)
attrs = self.clean_all(attrs)
else:
self._errors['non_field_errors'] = 'No input provided'

View File

@ -163,6 +163,30 @@ class ValidationTests(TestCase):
self.assertFalse(serializer.is_valid())
self.assertEquals(serializer.errors, {'content': [u'Test not in value']})
def test_cross_field_validation(self):
class CommentSerializerWithCrossFieldValidator(CommentSerializer):
def validate(self, attrs):
if attrs["email"] not in attrs["content"]:
raise serializers.ValidationError("Email address not in content")
return attrs
data = {
'email': 'tom@example.com',
'content': 'A comment from tom@example.com',
'created': datetime.datetime(2012, 1, 1)
}
serializer = CommentSerializerWithCrossFieldValidator(data)
self.assertTrue(serializer.is_valid())
data['content'] = 'A comment from foo@bar.com'
serializer = CommentSerializerWithCrossFieldValidator(data)
self.assertFalse(serializer.is_valid())
self.assertEquals(serializer.errors, {'non_field_errors': [u'Email address not in content']})
class MetadataTests(TestCase):
def test_empty(self):