aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2014-12-08 14:56:45 +0000
committerTom Christie2014-12-08 14:56:45 +0000
commiteee02a47d997bd4439fe5fbdc01979d8f372247a (patch)
treed87d8b4b17198ccf33c8515033a2378e3dc4f3f0
parentef89c1566329f723e8a54f58ebe5aef51489f1b9 (diff)
downloaddjango-rest-framework-eee02a47d997bd4439fe5fbdc01979d8f372247a.tar.bz2
Added ListSerializer.validate(). Closes #2168.
-rw-r--r--rest_framework/fields.py38
-rw-r--r--rest_framework/serializers.py108
-rw-r--r--tests/test_serializer_lists.py16
3 files changed, 108 insertions, 54 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 37adbe16..0c6c2d39 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -294,31 +294,47 @@ class Field(object):
return self.default()
return self.default
- def run_validation(self, data=empty):
+ def validate_empty_values(self, data):
"""
- Validate a simple representation and return the internal value.
-
- The provided data may be `empty` if no representation was included
- in the input.
-
- May raise `SkipField` if the field should not be included in the
- validated data.
+ Validate empty values, and either:
+
+ * Raise `ValidationError`, indicating invalid data.
+ * Raise `SkipField`, indicating that the field should be ignored.
+ * Return (True, data), indicating an empty value that should be
+ returned without any furhter validation being applied.
+ * Return (False, data), indicating a non-empty value, that should
+ have validation applied as normal.
"""
if self.read_only:
- return self.get_default()
+ return (True, self.get_default())
if data is empty:
if getattr(self.root, 'partial', False):
raise SkipField()
if self.required:
self.fail('required')
- return self.get_default()
+ return (True, self.get_default())
if data is None:
if not self.allow_null:
self.fail('null')
- return None
+ return (True, None)
+
+ return (False, data)
+ def run_validation(self, data=empty):
+ """
+ Validate a simple representation and return the internal value.
+
+ The provided data may be `empty` if no representation was included
+ in the input.
+
+ May raise `SkipField` if the field should not be included in the
+ validated data.
+ """
+ (is_empty_value, data) = self.validate_empty_values(data)
+ if is_empty_value:
+ return data
value = self.to_internal_value(data)
self.run_validators(value)
return value
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 39523077..fb6c826b 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -229,6 +229,35 @@ class SerializerMetaclass(type):
return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
+def get_validation_error_detail(exc):
+ assert isinstance(exc, (ValidationError, DjangoValidationError))
+
+ if isinstance(exc, DjangoValidationError):
+ # Normally you should raise `serializers.ValidationError`
+ # inside your codebase, but we handle Django's validation
+ # exception class as well for simpler compat.
+ # Eg. Calling Model.clean() explicitly inside Serializer.validate()
+ return {
+ api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
+ }
+ elif isinstance(exc.detail, dict):
+ # If errors may be a dict we use the standard {key: list of values}.
+ # Here we ensure that all the values are *lists* of errors.
+ return dict([
+ (key, value if isinstance(value, list) else [value])
+ for key, value in exc.detail.items()
+ ])
+ elif isinstance(exc.detail, list):
+ # Errors raised as a list are non-field errors.
+ return {
+ api_settings.NON_FIELD_ERRORS_KEY: exc.detail
+ }
+ # Errors raised as a string are non-field errors.
+ return {
+ api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
+ }
+
+
@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
default_error_messages = {
@@ -293,55 +322,17 @@ class Serializer(BaseSerializer):
performed by validators and the `.validate()` method should
be coerced into an error dictionary with a 'non_fields_error' key.
"""
- if data is empty:
- if getattr(self.root, 'partial', False):
- raise SkipField()
- if self.required:
- self.fail('required')
- return self.get_default()
-
- if data is None:
- if not self.allow_null:
- self.fail('null')
- return None
-
- if not isinstance(data, dict):
- message = self.error_messages['invalid'].format(
- datatype=type(data).__name__
- )
- raise ValidationError({
- api_settings.NON_FIELD_ERRORS_KEY: [message]
- })
+ (is_empty_value, data) = self.validate_empty_values(data)
+ if is_empty_value:
+ return data
value = self.to_internal_value(data)
try:
self.run_validators(value)
value = self.validate(value)
assert value is not None, '.validate() should return the validated data'
- except ValidationError as exc:
- if isinstance(exc.detail, dict):
- # .validate() errors may be a dict, in which case, use
- # standard {key: list of values} style.
- raise ValidationError(dict([
- (key, value if isinstance(value, list) else [value])
- for key, value in exc.detail.items()
- ]))
- elif isinstance(exc.detail, list):
- raise ValidationError({
- api_settings.NON_FIELD_ERRORS_KEY: exc.detail
- })
- else:
- raise ValidationError({
- api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
- })
- except DjangoValidationError as exc:
- # Normally you should raise `serializers.ValidationError`
- # inside your codebase, but we handle Django's validation
- # exception class as well for simpler compat.
- # Eg. Calling Model.clean() explicitly inside Serializer.validate()
- raise ValidationError({
- api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
- })
+ except (ValidationError, DjangoValidationError) as exc:
+ raise ValidationError(detail=get_validation_error_detail(exc))
return value
@@ -349,6 +340,14 @@ class Serializer(BaseSerializer):
"""
Dict of native values <- Dict of primitive datatypes.
"""
+ if not isinstance(data, dict):
+ message = self.error_messages['invalid'].format(
+ datatype=type(data).__name__
+ )
+ raise ValidationError({
+ api_settings.NON_FIELD_ERRORS_KEY: [message]
+ })
+
ret = OrderedDict()
errors = OrderedDict()
fields = [
@@ -462,6 +461,26 @@ class ListSerializer(BaseSerializer):
return html.parse_html_list(dictionary, prefix=self.field_name)
return dictionary.get(self.field_name, empty)
+ def run_validation(self, data=empty):
+ """
+ We override the default `run_validation`, because the validation
+ performed by validators and the `.validate()` method should
+ be coerced into an error dictionary with a 'non_fields_error' key.
+ """
+ (is_empty_value, data) = self.validate_empty_values(data)
+ if is_empty_value:
+ return data
+
+ value = self.to_internal_value(data)
+ try:
+ self.run_validators(value)
+ value = self.validate(value)
+ assert value is not None, '.validate() should return the validated data'
+ except (ValidationError, DjangoValidationError) as exc:
+ raise ValidationError(detail=get_validation_error_detail(exc))
+
+ return value
+
def to_internal_value(self, data):
"""
List of dicts of native values <- List of dicts of primitive datatypes.
@@ -503,6 +522,9 @@ class ListSerializer(BaseSerializer):
self.child.to_representation(item) for item in iterable
]
+ def validate(self, attrs):
+ return attrs
+
def update(self, instance, validated_data):
raise NotImplementedError(
"Serializers with many=True do not support multiple update by "
diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py
index 640067e3..35b68ae7 100644
--- a/tests/test_serializer_lists.py
+++ b/tests/test_serializer_lists.py
@@ -272,3 +272,19 @@ class TestNestedListOfListsSerializer:
serializer = self.Serializer(data=input_data)
assert serializer.is_valid()
assert serializer.validated_data == expected_output
+
+
+class TestListSerializerClass:
+ """Tests for a custom list_serializer_class."""
+ def test_list_serializer_class_validate(self):
+ class CustomListSerializer(serializers.ListSerializer):
+ def validate(self, attrs):
+ raise serializers.ValidationError('Non field error')
+
+ class TestSerializer(serializers.Serializer):
+ class Meta:
+ list_serializer_class = CustomListSerializer
+
+ serializer = TestSerializer(data=[], many=True)
+ assert not serializer.is_valid()
+ assert serializer.errors == {'non_field_errors': ['Non field error']}