Commit eee02a47 by Tom Christie

Added ListSerializer.validate(). Closes #2168.

parent ef89c156
...@@ -294,31 +294,47 @@ class Field(object): ...@@ -294,31 +294,47 @@ class Field(object):
return self.default() return self.default()
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. Validate empty values, and either:
The provided data may be `empty` if no representation was included * Raise `ValidationError`, indicating invalid data.
in the input. * Raise `SkipField`, indicating that the field should be ignored.
* Return (True, data), indicating an empty value that should be
May raise `SkipField` if the field should not be included in the returned without any furhter validation being applied.
validated data. * Return (False, data), indicating a non-empty value, that should
have validation applied as normal.
""" """
if self.read_only: if self.read_only:
return self.get_default() return (True, self.get_default())
if data is empty: if data is empty:
if getattr(self.root, 'partial', False): if getattr(self.root, 'partial', False):
raise SkipField() raise SkipField()
if self.required: if self.required:
self.fail('required') self.fail('required')
return self.get_default() return (True, self.get_default())
if data is None: if data is None:
if not self.allow_null: if not self.allow_null:
self.fail('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) value = self.to_internal_value(data)
self.run_validators(value) self.run_validators(value)
return value return value
......
...@@ -229,6 +229,35 @@ class SerializerMetaclass(type): ...@@ -229,6 +229,35 @@ class SerializerMetaclass(type):
return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) 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) @six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer): class Serializer(BaseSerializer):
default_error_messages = { default_error_messages = {
...@@ -293,55 +322,17 @@ class Serializer(BaseSerializer): ...@@ -293,55 +322,17 @@ class Serializer(BaseSerializer):
performed by validators and the `.validate()` method should performed by validators and the `.validate()` method should
be coerced into an error dictionary with a 'non_fields_error' key. be coerced into an error dictionary with a 'non_fields_error' key.
""" """
if data is empty: (is_empty_value, data) = self.validate_empty_values(data)
if getattr(self.root, 'partial', False): if is_empty_value:
raise SkipField() return data
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]
})
value = self.to_internal_value(data) value = self.to_internal_value(data)
try: try:
self.run_validators(value) self.run_validators(value)
value = self.validate(value) value = self.validate(value)
assert value is not None, '.validate() should return the validated data' assert value is not None, '.validate() should return the validated data'
except ValidationError as exc: except (ValidationError, DjangoValidationError) as exc:
if isinstance(exc.detail, dict): raise ValidationError(detail=get_validation_error_detail(exc))
# .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)
})
return value return value
...@@ -349,6 +340,14 @@ class Serializer(BaseSerializer): ...@@ -349,6 +340,14 @@ class Serializer(BaseSerializer):
""" """
Dict of native values <- Dict of primitive datatypes. 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() ret = OrderedDict()
errors = OrderedDict() errors = OrderedDict()
fields = [ fields = [
...@@ -462,6 +461,26 @@ class ListSerializer(BaseSerializer): ...@@ -462,6 +461,26 @@ class ListSerializer(BaseSerializer):
return html.parse_html_list(dictionary, prefix=self.field_name) return html.parse_html_list(dictionary, prefix=self.field_name)
return dictionary.get(self.field_name, empty) 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): def to_internal_value(self, data):
""" """
List of dicts of native values <- List of dicts of primitive datatypes. List of dicts of native values <- List of dicts of primitive datatypes.
...@@ -503,6 +522,9 @@ class ListSerializer(BaseSerializer): ...@@ -503,6 +522,9 @@ class ListSerializer(BaseSerializer):
self.child.to_representation(item) for item in iterable self.child.to_representation(item) for item in iterable
] ]
def validate(self, attrs):
return attrs
def update(self, instance, validated_data): def update(self, instance, validated_data):
raise NotImplementedError( raise NotImplementedError(
"Serializers with many=True do not support multiple update by " "Serializers with many=True do not support multiple update by "
......
...@@ -272,3 +272,19 @@ class TestNestedListOfListsSerializer: ...@@ -272,3 +272,19 @@ class TestNestedListOfListsSerializer:
serializer = self.Serializer(data=input_data) serializer = self.Serializer(data=input_data)
assert serializer.is_valid() assert serializer.is_valid()
assert serializer.validated_data == expected_output 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']}
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