Validating list of data when many=True

Viewed 1324

Suppose the following model and serializer are given.

class Human(models.Model):
    name = models.TextField(unique=True)

class HumanSer(serializers.ModelSerializer):
    class Meta:
        model = Human
        fields = '__all__'

I would like to validate that when a list of names is given, the names in that list do not repeat, without hitting the database (more on this below).

data = [{'name': 'John}, {'name': 'John'}]
ser = HumanSer(data=data, many=True)

In a perfect world I would add validate_name method, access all previously validated entries, and raise `ValidationError.

This doesn't seem possible at the moment, so the options I have found are:

  1. Access .initial_data and do the comparison. This seems not very reliable since I have to be careful, because I am working with the raw unvalidated data; pay attention to whether the data is a list or not, etc.
  2. Do nothing, in which case .is_valid() returns True, but .save() throws an exception. Is this case the REST call fails as a whole and I can't point to the user which name is the duplicate.
1 Answers

If you only concern about unique items in input list then take a look on ListSerializer

class HumanListSerializer(serializers.ListSerializer):

    def validate(self, data):
        validation_set = set()

        for item in data:
            if item["name"] in validation_set:
                raise serializers.ValidationError(f"duplicated item: {item['name']}")
            else:
                validation_set.add(item["name"])

        return data


class HumanSerializer(serializers.Serializer):
    name = serializers.CharField()

    class Meta:
        list_serializer_class = HumanListSerializer


if __name__ == '__main__':
    data = [{'name': 'John', "xxx": "xz"}, {'name': 'John'}]
    ser = HumanSerializer(data=data, many=True)
    print(f"is valid: {ser.is_valid()}")
    print(f"errors: {ser.errors}")

# >>> is valid: False
# >>> errors: {'non_field_errors': [ErrorDetail(string='duplicated item: John', code='invalid')]}

But if you want validate uniqueness in whole db take a look on UniqueValidator

from rest_framework.validators import UniqueValidator

class HumanSer(serializers.ModelSerializer):
    name = serializers.CharField(
        validators=[UniqueValidator(queryset=Human.objects.all())]
    )

    class Meta:
        model = Human
        fields = '__all__'

(i'm not sure about performance of this method)

updated: as Iain Shelvington point out there is an open issue in which it probably needs to combine ListSerializer and UniqueValidator solutions.

Related