aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJamie Matthews2012-10-24 11:39:17 +0100
committerJamie Matthews2012-10-24 11:39:17 +0100
commitac2d39892d6b3fbbe5cd53b9ef83367249ba4880 (patch)
tree413fedc79aeedb06722b416fd74af38d767f9c9e
parent388a807f64f60d84556288e2ade4f0fe57a8e66b (diff)
downloaddjango-rest-framework-ac2d39892d6b3fbbe5cd53b9ef83367249ba4880.tar.bz2
Add cross-field validate method
-rw-r--r--docs/api-guide/serializers.md8
-rw-r--r--rest_framework/serializers.py13
-rw-r--r--rest_framework/tests/serializer.py24
3 files changed, 42 insertions, 3 deletions
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 9011d31f..40f8a170 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -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,
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 802ca55f..15fe26ee 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -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'
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index a32de80d..936f15aa 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -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):