46
votes

The scenario is quite straight-forward:

I have a model with some fields that are required. Let's say one of them is a TextField which can't be blank. I also have a ModelSerializer (Django Rest Framework) that represents that model.

When an empty string is used to set that field through the serializer the error returned comes from the model itself (This field can't be blank).

I would like to override the error messages only in the serializer level, without the need to explicitly re-specifying every field in the serializer (which I believe is against the DRY principle), having to write a validate_ method for each field and raise my own ValidationError or having to change the error messages in the Model level (because sometimes the context of the error message matters to my use-case and the error message should be given accordingly).

In other words, is there a way to override error messages in the serializer level as easy as it is for a ModelForm:

class MyModelForm(ModelForm):
    class Meta:
        model = MyModel
        error_messages = {"field1": {"required": _("For some reason this is a custom error message overriding the model's default")}}
9
DRF is calling django field validators at time of validation. So errors will be coming from here, not from DRF. The idea is to try specify error message on model or field level, because as I can see there are no way to override these messages with DRF.coldmind

9 Answers

47
votes

EDIT: I see that this question still receives some views, so it is important to note that there's another approach, much cleaner than the original answer I posted here.

You can just use the extra_kwargs attribute of the serializer's Meta class, like so:

class UserSerializer(ModelSerializer):

    class Meta:
        model = User
        extra_kwargs = {"username": {"error_messages": {"required": "Give yourself a username"}}}

Original answer:

Using @mariodev 's answer I created a new class in my project that does that:

from rest_framework.serializers import ModelSerializer, ModelSerializerOptions

class CustomErrorMessagesModelSerializerOptions(ModelSerializerOptions):
    """
    Meta class options for CustomErrorMessagesModelSerializerOptions
    """
    def __init__(self, meta):
        super(CustomErrorMessagesModelSerializerOptions, self).__init__(meta)
        self.error_messages = getattr(meta, 'error_messages', {})

class CustomErrorMessagesModelSerializer(ModelSerializer):
    _options_class = CustomErrorMessagesModelSerializerOptions

    def __init__(self, *args, **kwargs):
        super(CustomErrorMessagesModelSerializer, self).__init__(*args, **kwargs)

        # Run through all error messages provided in the Meta class and update
        for field_name, err_dict in self.opts.error_messages.iteritems():
            self.fields[field_name].error_messages.update(err_dict)

The first one gives the possibility to add a new Meta class attribute to the serializer as with the ModelForm. The second one inherits from ModelSerializer and uses @mariodev's technique to update the error messages.

All is left to do, is just inherit it, and do something like that:

class UserSerializer(CustomErrorMessagesModelSerializer):
    class Meta:
        model = User
        error_messages = {"username": {"required": "Give yourself a username"}}
24
votes

In your serializer:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User

    def __init__(self, *args, **kwargs):
        super(UserSerializer, self).__init__(*args, **kwargs)

        self.fields['username'].error_messages['required'] = u'My custom required msg'

Please notice that some error messages consist of %s placeholders like:

'invalid': _("'%s' value must be either True or False."),

for BooleanField.

So you need to go over default_error_messages part in each field type in the DRF's fields.py, to use it properly.

19
votes

I tried to create a simple Serializer rather than a ModelSerializer. Probably because of that the accepted answer with extra_kwargs by Gabriel Amram didn't work for me. Another top answer by @mariodev did work but I was looking for a more elegant solution and found one. Turns out that the Field class accepts error_messages as a parameter, which is a dictionary that overrides the default error messages. Here is the reference to the docs. It's the same format as described in the accepted answers. Here is an example:

from rest_framework import serializers

class MySerializer(serializers.Serializer):
    client_id = serializers.IntegerField(required=True, error_messages={'required': 'Custom error message'})
15
votes

unique seemed to be ignored from error_messages, so I had to take a different approach.

email = serializers.EmailField(validators=[
    UniqueValidator(
        queryset=models.Client.objects.all(),
        message="My custom error",
    )]
)

It's simpler (yet less flexible, less reusable) than @gabriel-amram's, but far less hacky than @mariodev's.

4
votes

Another approach for UniqueValidator (for using with ModelSerializer):

def __init__(self, *args, **kwargs):
    super(UserSerializer, self).__init__(*args, **kwargs)
    # Find UniqueValidator and set custom message
    for validator in self.fields['email'].validators:
        if isinstance(validator, validators.UniqueValidator):
            validator.message = _('This email already exist on this site')
3
votes

DRF3.0 expects us to explicitly define the validators for fields if we wish to override the default model validators. This can be done by passing extra_kwargs and explicitly defining the validators for whichever field you seem necessary. Also you can even specify your own custom validator which can be reused again for different fields or even other serializers

http://www.django-rest-framework.org/api-guide/serializers/#validation

http://www.django-rest-framework.org/api-guide/validators/#validation-in-rest-framework

# my_app/validators.py
def validate_required(value):
    # whatever validation logic you need
    if value == '' or value is None:
        raise serializers.ValidationError('This field is required.')

# my_app/serializers.py
class MyModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = MyModel
        extra_kwargs = {"field1": {"validators": [validators.validate_required,]}}
1
votes

I just spent an hour ripping my hair out over this, so figured I'd post an update here in case anyone else finds it useful.

I'm using djangorestframework version 3.10.3, and for whatever reason, it seems that drf no longer uses the 'required' key in the error_messages dict to allow customization of an error message for a missing value. Instead it uses 'blank'.

class SampleSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = SampleModel
        fields = (
            'description',
        )

        extra_kwargs = {
            'description': {'error_messages': {'blank': "Please provide a description"}},
        }
0
votes

Just a note since I played with this for awhile, if you're using something like a URLField that just adds a URLValidator, it doesn't seem to use the error_messages, so I did something similar to @Hugo's answer:

class Meta:
    extra_kwargs = {"url_field": {"validators": [validators.URLValidator(message="My error message")]}}
-1
votes

This is the code that inherits the model's error message.
There is also a module, so download it if you want. If there's a problem, leave it in the comments.

https://pypi.org/project/django-rest-inherits-error-messages/#files

from rest_framework import serializers
from rest_framework.relations import HyperlinkedRelatedField
from rest_framework.utils.field_mapping import get_nested_relation_kwargs

class InheritsModelSerializer(serializers.ModelSerializer):
    def build_field(self, field_name, info, model_class, nested_depth):
        '''
        inherits the error_messages of the model
        '''
        result: tuple = super().build_field(field_name, info, model_class, nested_depth)

        field = model_class._meta.get_field(field_name)
        error_messages = field.error_messages

        if error_messages:
            result[1]['error_messages'] = field.error_messages

        return result