8

I'm trying to customize the default validation errors of DRF (3.x) for an account model. My goal is to write a validation function in order to send back a customized error message.

I've tried the following:

class AccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        fields = ('id', 'email', 'password',)

    def validate_password(self, value):
        """
        Validate Password.
        """
        if not value:
            raise serializers.ValidationError("Password cannot be empty!")
        elif len(value) < 5:
            raise serializers.ValidationError("Password to short...")
        return value

The the length validation works fine, but the 'password is empty' validation is never thrown because the default error ('password', [u'This field may not be blank.']) is thrown before.

Is there any option to disable default errors or to force validation by my custom function first?

Thanks for help!

2 Answers 2

8

You can override the validation errors on a per-field basis by setting the error_messages argument when initializing the field. You need to pass a dictionary with the key being the error message name, and the value being the custom text for the error message.

In your case, you are looking to implement two error messages: required and min_length. You may also need to override blank, which is triggering your current error, unless you set allow_blank=True on the field.

So with those changes, your serializer would become

class AccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(
        write_only=True,
        required=False,
        min_length=5,
        error_messages={
            "blank": "Password cannot be empty.",
            "min_length": "Password too short.",
        },
    )

    class Meta:
        model = Account
        fields = ('id', 'email', 'password', )

I've replaced your len check with the min_length argument on the password field.

This offloads all of your validation to Django REST framework, which should make upgrades easier in the future. You can still override validate_password if you need additional custom validation, but for now I've removed it since it would be empty.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your answer! As I want to return language specific error messages, I thought it would be a better idea to call a function, which handles the different messages for the same error. Is there any possibility to call a function from the error_message argument?
I would recommend looking into Django's translation utilities. It is what DRF will be using for translations, and I've been told it works well. The other option is to override the exception handler and manually do the translations there.
5

Found a solution:

I had to validate the values with the to_internal_value function because validation is run in a specific order (thanks to Kevin Brown): order of validation

Now my improved code is as follows:

class AccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        fields = ('id', 'email', 'password',)

    def to_internal_value(self, data):
        password = data.get('password')
        """
        Validate Password.
        """
        if not password:
            raise serializers.ValidationError({'password': 'Password cannot be empty!'})
        elif len(password) < 5:
            raise serializers.ValidationError({'password': 'Password to short...'})

        return {
            'password': password
        }

I hope this post is useful for somebody :)

2 Comments

Why does it work....? It seems that the validate_emtpy_values is executed before to_internal_value in run_validation function in the source code
@soooooot serializer.to_internal_value should be called before serializer.run_validation. I've updated the order of validation answer to include links to the source code, so you can trace the calls.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.