Commit 51fae73f by Jamie Matthews

Implement per-field validation on Serializers

parent 5d76f03a
......@@ -78,6 +78,23 @@ When deserializing data, you always need to call `is_valid()` before attempting
**TODO: Describe validation in more depth**
## Custom field validation
Like Django forms, you can specify custom field-level validation by adding `clean_<fieldname>()` methods to your `Serializer` subclass. This method takes 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, if one was provided.) It should either return the data dictionary or raise a `ValidationError`. For example:
class BlogPostSerializer(Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()
def clean_title(self, data, source):
"""
Check that the blog post is about Django
"""
value = data[source]
if "Django" not in value:
raise ValidationError("Blog post is not about Django")
return data
## 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,
......
......@@ -208,6 +208,23 @@ class BaseSerializer(Field):
return reverted_data
def clean_fields(self, data):
"""
Run clean_<fieldname> validators on the serializer
"""
fields = self.get_fields(serialize=False, data=data, nested=self.opts.nested)
for field_name, field in fields.items():
try:
clean_method = getattr(self, 'clean_%s' % field_name, None)
if clean_method:
source = field.source or field_name
data = clean_method(data, source)
except ValidationError as err:
self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages)
return data
def restore_object(self, attrs, instance=None):
"""
Deserialize a dictionary of attributes into an object instance.
......@@ -241,6 +258,7 @@ class BaseSerializer(Field):
self._errors = {}
if data is not None:
attrs = self.restore_fields(data)
attrs = self.clean_fields(attrs)
else:
self._errors['non_field_errors'] = 'No input provided'
......
......@@ -138,6 +138,31 @@ class ValidationTests(TestCase):
self.assertEquals(serializer.is_valid(), True)
self.assertEquals(serializer.errors, {})
def test_field_validation(self):
class CommentSerializerWithFieldValidator(CommentSerializer):
def clean_content(self, attrs, source):
value = attrs[source]
if "test" not in value:
raise serializers.ValidationError("Test not in value")
return attrs
data = {
'email': 'tom@example.com',
'content': 'A test comment',
'created': datetime.datetime(2012, 1, 1)
}
serializer = CommentSerializerWithFieldValidator(data)
self.assertTrue(serializer.is_valid())
data['content'] = 'This should not validate'
serializer = CommentSerializerWithFieldValidator(data)
self.assertFalse(serializer.is_valid())
self.assertEquals(serializer.errors, {'content': [u'Test not in value']})
class MetadataTests(TestCase):
def test_empty(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment