Search code examples
pythondjangodjango-modelsdjango-rest-frameworkdjango-views

How to show UniqueTogetherValidator as field error instead of non-field error?


I have a serializer like this:

class ContactSerializer(serializers.ModelSerializer):
    class Meta:
        model = Contact
        fields = (
            'account', 'first_name', 'last_name', 'email',
            'phone_number',
        )
        validators = [
            UniqueTogetherValidator(
                queryset=Contact.objects.all(),
                fields=['account', 'phone_number'],
                message='A contact with this phone number is already exists.',
            ),
        ]

API returns the unique together validator errors as non_field_errors. I want to show it in the specific field. In this case phone_number.

How can I do that?


Solution

  • I made my own validator:

    class UniqueForeignValidator(UniqueValidator):
        message = 'The {field} must be unique within {foreign}'
        requires_context = True
    
        def __init__(self, queryset, foreign_field, message=None, lookup='exact'):
            super().__init__(queryset, message, lookup)
            self.foreign_field = foreign_field
    
        def __call__(self, value, serializer_field):
            foreign_pk = serializer_field.parent.initial_data[self.foreign_field]
            self.message = self.message.format(field=serializer_field.field_name, foreign=self.foreign_field)
            filter_kwargs = {f'{self.foreign_field}': foreign_pk}
            self.queryset = self.queryset.filter(**filter_kwargs)
            return super().__call__(value, serializer_field)
    

    For your example, it should be called like this:

    class ContactSerializer(serializers.ModelSerializer):
        phone_number = fields.CharField(
            validators=[
                UniqueForeignValidator(
                    queryset=Contact.objects.all(),
                    foreign_field='account'
                )
            ]
        )
        
        class Meta:
            model = Contact
            fields = (
                'account', 'first_name', 'last_name', 'email',
                'phone_number',
            )
    

    You can override the message when calling the validator in serializer

    UniqueForeignValidator(
        queryset=Contact.objects.all(),
        foreign_field='account',
        message='A contact with this phone number is already exists.'
    )