[Solved]-Serializer validate function is not called DRF

7πŸ‘

I ran into a similar problem where my custom validation field was not being called. I was writing it to bypass an incorrect DRF validation (more details shown below, but not necessary for the answer).

Looking into the DRF source code I found my problem: DRF always validates your field using its code before validating with your custom code.

''' rest-framework/serializers.py '''

for field in fields:
    validate_method = getattr(self, 'validate_' + field.field_name, None)
    primitive_value = field.get_value(data)
    try:
        # DRF validation always runs first!
        # If DRF validator throws, then custom validation is not called
        validated_value = field.run_validation(primitive_value)
        if validate_method is not None:
            # this is your custom validation
            validated_value = validate_method(validated_value)
    except ValidationError as exc:
        errors[field.field_name] = exc.detail
    except DjangoValidationError as exc:
        errors[field.field_name] = get_error_detail(exc)

Answer: Custom validators cannot be used to bypass DRF’s validators, as they will always run first and will raise an exception before you can say it is valid.

(for those interested, the validation error I hit was like this: ModelSerializer used for ModelA, which has a OneToOne relation to ModelB. ModelB has a UUID for its pk. DRF throws the error '53abb068-0286-411e-8729-0174635c5d81' is not a valid UUID. when validating, which is incorrect, and really infuriating.)

πŸ‘€sean.hudson

5πŸ‘

Your ParentSerializer validation method has some issues. Assumes that there is a title field in your ParentSerializer model. For field level validation, you will get the field instead of whole data. That is validate_title function should have title(title field of the data) as parameter not data. So you dont have to check data.get('title') for the existance of title. Reference

class ParentSerializer(serializers.ModelSerializer):
    """
    Serializer for task
    """
    def validate_title(self, title):
        if not title:
            raise serializers.ValidationError('Please set title')
        return title
πŸ‘€Aneesh R S

1πŸ‘

In addition to @sean.hudson’s answer I was trying to figure out how to override the child serializer validation.

It might be possible to "skip" or more accurately ignore children serializer validation errors, by overriding to_internal_value of the ParentSerialzer:

class ParentSerializer(serializers.ModelSerializer):
    children = ChildSerializer(many=True)

    def to_internal_value(self, *args, **kwargs):
        try:
            # runs the child serializers
            return super().to_internal_value(*args, **kwargs)
        except ValidationError as e:
            # fails, and then overrides the child errors with the parent error
            return self.validate(self.initial_data)

    def validate(self, attrs):
        errors = {}
        errors['custom_override_error'] = 'this ignores and overrides the children serializer errors'
        if len(errors):
            raise ValidationError(errors)
        return attrs
    class Meta:
        model = Parent
πŸ‘€jmunsch

1πŸ‘

My problem was that I had my own custom to_internal_value method. Removing it fixed the issue.

class EventSerializer(serializers.Serializer):
    end_date = serializers.DateTimeField(format=DATE_FORMAT, required=True)
    start_date = serializers.DateTimeField(format=DATE_FORMAT, required=True)
    description = serializers.CharField(required=True)


    def validate_start_date(self, start_date):
        return start_date

    def validate_end_date(self, end_date):
        return end_date

    # def to_internal_value(self, data):
    #     if data.get('start_date', False):
    #         data['start_date'] = datetime.strptime(data['start_date'], DATE_FORMAT)
    #     if data.get('end_date', False):
    #         data['end_date'] = datetime.strptime(data['end_date'], DATE_FORMAT)
    #     return data
πŸ‘€smoquet

1πŸ‘

I would like to add what the official documentation says, I hope it can be of help.

Field-level validation

You can specify custom field-level validation by adding .validate_<field_name> methods to your Serializer subclass. These are similar to the .clean_<field_name> methods on Django forms.

These methods take a single argument, which is the field value that requires validation.

Your validate_<field_name> methods should return the validated value or raise a serializers.ValidationError. For example:

from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()

def validate_title(self, value):
    """
    Check that the blog post is about Django.
    """
    if 'django' not in value.lower():
        raise serializers.ValidationError("Blog post is not about Django")
    return value`

Leave a comment