mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-29 21:14:01 +03:00
Merge pull request #2232 from tomchristie/validate-in-list-serializer
Added ListSerializer.validate().
This commit is contained in:
commit
62cca1eec7
|
@ -294,6 +294,34 @@ class Field(object):
|
||||||
return self.default()
|
return self.default()
|
||||||
return self.default
|
return self.default
|
||||||
|
|
||||||
|
def validate_empty_values(self, 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 (True, self.get_default())
|
||||||
|
|
||||||
|
if data is empty:
|
||||||
|
if getattr(self.root, 'partial', False):
|
||||||
|
raise SkipField()
|
||||||
|
if self.required:
|
||||||
|
self.fail('required')
|
||||||
|
return (True, self.get_default())
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
if not self.allow_null:
|
||||||
|
self.fail('null')
|
||||||
|
return (True, None)
|
||||||
|
|
||||||
|
return (False, data)
|
||||||
|
|
||||||
def run_validation(self, data=empty):
|
def run_validation(self, data=empty):
|
||||||
"""
|
"""
|
||||||
Validate a simple representation and return the internal value.
|
Validate a simple representation and return the internal value.
|
||||||
|
@ -304,21 +332,9 @@ class Field(object):
|
||||||
May raise `SkipField` if the field should not be included in the
|
May raise `SkipField` if the field should not be included in the
|
||||||
validated data.
|
validated data.
|
||||||
"""
|
"""
|
||||||
if self.read_only:
|
(is_empty_value, data) = self.validate_empty_values(data)
|
||||||
return self.get_default()
|
if is_empty_value:
|
||||||
|
return data
|
||||||
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
|
|
||||||
|
|
||||||
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):
|
||||||
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,18 +322,24 @@ 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:
|
value = self.to_internal_value(data)
|
||||||
if not self.allow_null:
|
try:
|
||||||
self.fail('null')
|
self.run_validators(value)
|
||||||
return None
|
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):
|
||||||
|
"""
|
||||||
|
Dict of native values <- Dict of primitive datatypes.
|
||||||
|
"""
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
message = self.error_messages['invalid'].format(
|
message = self.error_messages['invalid'].format(
|
||||||
datatype=type(data).__name__
|
datatype=type(data).__name__
|
||||||
|
@ -313,42 +348,6 @@ class Serializer(BaseSerializer):
|
||||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
||||||
})
|
})
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
|
||||||
"""
|
|
||||||
Dict of native values <- Dict of primitive datatypes.
|
|
||||||
"""
|
|
||||||
ret = OrderedDict()
|
ret = OrderedDict()
|
||||||
errors = OrderedDict()
|
errors = OrderedDict()
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -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):
|
||||||
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:
|
||||||
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']}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user