Created
March 27, 2018 20:53
-
-
Save erickmendonca/96fd0d766cd3be185eefbfc81ae555ab to your computer and use it in GitHub Desktop.
Serializer - throw all errors at the end of validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Mapping | |
from collections import OrderedDict | |
from django.core.exceptions import ValidationError as DjangoValidationError | |
from rest_framework import serializers | |
from rest_framework.fields import empty | |
class AllErrorsSerializer(serializers.Serializer): | |
''' | |
This serializer avoids erroring out sooner on data missing | |
required fields or failing on validators defined on the field | |
declaration. | |
Warning: required fields might not be present on custom `.validate()` | |
methods since this class does not raise an ValidationError earlier. | |
''' | |
def __init__(self, *args, **kwargs): | |
super().__init__(*args, **kwargs) | |
self._errors = dict() | |
def to_internal_value(self, data): | |
'''' | |
Overrides Serializer `to_internal_value` to store field validation | |
errors instead of raising an Exception right away. These errors | |
will be thrown out at the end of `run_validation` along with other | |
custom validations. | |
''' | |
if not isinstance(data, Mapping): | |
message = self.error_messages['invalid'].format( | |
datatype=type(data).__name__ | |
) | |
raise serializers.ValidationError({ | |
serializers.api_settings.NON_FIELD_ERRORS_KEY: [message] | |
}, code='invalid') | |
ret = OrderedDict() | |
errors = OrderedDict() | |
fields = self._writable_fields | |
for field in fields: | |
validate_method = getattr( | |
self, 'validate_' + field.field_name, None, | |
) | |
primitive_value = field.get_value(data) | |
try: | |
validated_value = field.run_validation(primitive_value) | |
if validate_method is not None: | |
validated_value = validate_method(validated_value) | |
except serializers.ValidationError as exc: | |
errors[field.field_name] = exc.detail | |
except DjangoValidationError as exc: | |
errors[field.field_name] = serializers.get_error_detail(exc) | |
except serializers.SkipField: | |
pass | |
else: | |
serializers.set_value(ret, field.source_attrs, validated_value) | |
# save errors instead of raising an Exception | |
if errors: | |
self._errors.update(errors) | |
return ret | |
def run_validation(self, data=empty): | |
''' | |
We override this method so we can coerce all errors on a single | |
ValidationError. | |
''' | |
(is_empty_value, data) = self.validate_empty_values(data) | |
if is_empty_value: | |
return data | |
value = self.to_internal_value(data) | |
errors = list() | |
try: | |
self.run_validators(value) | |
except (serializers.ValidationError, DjangoValidationError) as exc: | |
errors.extend(exc.detail) | |
try: | |
value = self.validate(value) | |
except (serializers.ValidationError, DjangoValidationError) as exc: | |
errors.extend(exc.detail) | |
assert value is not None, ( | |
'.validate() should return the validated data') | |
if errors or self.errors: | |
raise serializers.ValidationError( | |
{**self.errors, 'non_field_errors': errors}, | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment