Unique validation on nested serializer on Django Rest Framework

15,433

Solution 1

You should drop the unique validator for the nested serializer:

class GenreSerializer(serializers.ModelSerializer):

    class Meta:
        fields = ('name',) #This field is unique
        model = Genre
        extra_kwargs = {
            'name': {'validators': []},
        }

You may want to print your serializer before to make sure you don't have other validators on that field. If you have some, you'll have to include them in the list.

Edit: If you need to ensure the uniqueness constraint for creation, you should do it in the view after the serializer.is_valid has been called and before serializer.save.

Solution 2

This happens because the nested serializer (GenreSerializer) needs an instance of the object to validate the unique constraint correctly (like put a exclude clause to the queryset used on validation) and by default, a serializer will not pass the instance of related objects to fileds the are nested serializers when runs the to_internal_value() method. See here

Another way to solve this problem is override the get_fields() method on parent serializer and pass the instance of related object

class BookSerializer(serializers.ModelSerializer):

    def get_fields(self):
        fields = super(BookSerializer, self).get_fields()
        try: # Handle DoesNotExist exceptions (you may need it)
            if self.instance and self.instance.genre:
                fields['genre'].instance = self.instance.genre
        except Genre.DoesNotExist:
            pass
        return fields

Solution 3

Together than remove the UniqueValidator using

'name': {'validators': []}

You need to validate the Unique entry yourself ignoring the current object, for not get an 500 error when another person try to save the same name, something like this will work:

    def validate_name(self, value):
        check_query = Genre.objects.filter(name=value)
        if self.instance:
            check_query = check_query.exclude(pk=self.instance.pk)

        if self.parent is not None and self.parent.instance is not None:
            genre = getattr(self.parent.instance, self.field_name)
            check_query = check_query.exclude(pk=genre.pk)

        if check_query.exists():
            raise serializers.ValidationError('A Genre with this name already exists
.')
        return value

A method validate_<field> is called for validate all your fields, see the docs.

Share:
15,433

Related videos on Youtube

Ignacio D. Favro
Author by

Ignacio D. Favro

Trust me, i'm engineer

Updated on July 05, 2022

Comments

  • Ignacio D. Favro
    Ignacio D. Favro almost 2 years

    I have a case like this, where you have a custom nested serializer relation with a unique field. Sample case:

    class GenreSerializer(serializers.ModelSerializer):
    
        class Meta:
            fields = ('name',) #This field is unique
            model = Genre
    
    class BookSerializer(serializers.ModelSerializer):
    
        genre = GenreSerializer()
    
        class Meta:
            model = Book
            fields = ('name', 'genre')
    
        def create(self, validated_data):
            genre = validated_data.pop('genre')
            genre = Genre.objects.get(**genre)
            return Book.objects.create(genre=genre, **validated_data)
    

    The problem: When i try to save a json object like {"name":"The Prince", "genre": {"name": "History"}} DRF try to validate the genre object unique constraint and if "History" exists throw me an exception because a genre with name "History" must be unique, and that's true but i just trying to relate the object and not create together.

    Thank you a lot!!

  • Ignacio D. Favro
    Ignacio D. Favro almost 8 years
    Thank you a lot! But if i need the validation in the nested serializer when i use it for save Genre instances? is there a way to check only if i creating a Genre instance and not if a creating a Book instance? Thank you again!
  • Linovia
    Linovia almost 8 years
    That should be part of a second validation step - say in the create/update part by raising ValidationError.
  • Ignacio D. Favro
    Ignacio D. Favro almost 8 years
    That's was really helpfull! Thank you a lot!
  • a2f0
    a2f0 over 6 years
    Also see this and this for more info.
  • cezar
    cezar over 6 years
    Is it best practice to validate the uniqueness in the view? If I use viewsets, should I put the validation in create or perform_create? I'd really like to see a simple example using UniqueValidator.
  • Linovia
    Linovia over 6 years
    perform_create is a good match because you'd avoid duplicating code from view set's create. Could also be done within the serializer's create/update though I don't like the idea of raising validation errors from there. Finally you could also make the serializer's save a no-op and use a business layer to perform the last mile validation and deal with the associated logic.
  • cezar
    cezar over 6 years
    Then perform_create should be implemented in BookViewSet, although it is about validating the Genre. This doesn't look very clean to me. What is your opinion on that?
  • Linovia
    Linovia over 6 years
    This is already answered and feel free to open another question for further explanation.
  • cezar
    cezar over 6 years
    @Linovia Thank you! I opened another question: stackoverflow.com/questions/48267017/…
  • Ron
    Ron over 3 years
    You are setting always the current genre object. If we are in an update case this might have changed. So only the "old" object will be validated. Thats a bug, I guess.