DRF: Validating serializer nested data when creating, but not when updating

When using writeable nested serializers in DRF, there is a known problem with checking for possible unique fields and preventing the parent serializer from updating. This problem was repeatedly asked in such questions:

For simplicity, we take an example from the first question:

class GenreSerializer(serializers.ModelSerializer): class Meta: fields = ('name',) #This field is unique model = Genre extra_kwargs = { 'name': {'validators': []}, } class BookSerializer(serializers.ModelSerializer): genre = GenreSerializer() class Meta: model = Book fields = ('name', 'genre') def create(self, validated_data): # implement creating def update(self, instance, validated_data): # implement updating 

Now the problem is that the validation of uniqueness also falls for creation. This can be intercepted in a view, for example:

 class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer = BookSerializer def perform_create(self): # implement logic and raise ValidationError 

However, this is not entirely correct, because we test the uniqueness of Genre in BookViewSet .

Another option is to implement verification in the create() BookSerializer , as explained in the second question (see the list above).

What I really missed in both solutions is that the validation error is not tied to the name field of the Genre model and user input in the form is lost.

I would like to add a validation error for Genre.name to existing validation errors, save the user login and only do this for creation, not for updating.

My ideas were something like this:

 class GenreSerializer(serializers.ModelSerializer): # ... def validate_name(self, value): # is it possible to check here if it is create or update? if create: # this is a placeholder for the logic if self.Meta.model.objects.filter(name=value).exists(): raise ValidationError('A genre with this name already exists.') return value # or to override the __init__ method def __init__(self, *args, **kwargs): super(GenreSerializer, self).__init__(*args, **kwargs) # check if create or update if create: self.fields['name'].validators.append('validation logic') 

Is this possible or is there any other way to achieve the above goal - to save the userโ€™s input and add the verification error attached to the name field to the list of existing verification errors when creating a new instance?

0
source share
2 answers

Here is how I did it:

 class GenreSerializer(serializers.ModelSerializer): # ... snip ... def validate_name(self, value): if self.context['request']._request.method == 'POST': if self.Meta.model.objects.filter(name=value).exists(): raise ValidationError('A genre with this name already exists.') return value 

Thus, the check is only triggered when a new Genre object ( POST ) is created, and not when it is updated ( PUT ).
When a new Book object is created, the check for Genre extends to the nested serializer.
All form inputs are saved after verification and the error message is attached to the name field.

It really matches all my criteria. Although I have no feeling that this is the right way to do this. I would still like to know how I can manually call UniqueValidator in validate_name instead of reusing this check.

EDIT:

I found out how to call UniqueValidator in a method:

 def validate_name(self, value): if self.context['request']._request.method == 'POST': unique = UniqueValidator( self.Meta.model.objects.all(), message='Genre with this name already exists.' ) unique.set_context(self.fields['name']) unique(value) return value 
+1
source

What I really missed in both solutions is that the validation error is not tied to the Genre model field name, and user input on the form is lost.

This is straightforward:

 from rest_framework import exceptions class BookViewSet(viewsets.ModelViewSet): .... def perform_create(self, serializer): if check_failed(): raise exceptions.ValidationError( exceptions._get_error_details({ 'genre': { 'name': ['must be unique'] } }) ) 
0
source

All Articles