DRF: valida los datos del serializador nested al crear, pero no al actualizar

Cuando se usan serializadores nesteds grabables en DRF, existe un problema conocido con la validación de campos únicos eventuales y la prevención de la actualización del serializador principal. Este problema se ha preguntado muchas veces en preguntas como estas:

  1. Validación única en el serializador nested en Django Rest Framework
  2. El marco de descanso de Django no crea un objeto con FK a un modelo con campo único = Verdadero

Por simplicidad tomemos el ejemplo de la primera pregunta:

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 

Ahora el problema es que la validación de unicidad también se elimina para la creación. Esto podría ser interceptado en la vista, por ejemplo:

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

Sin embargo, esto no se siente realmente bien porque estamos validando la singularidad del Genre en BookViewSet .

La otra opción es implementar la validación en el método create() de BookSerializer como se explica en la segunda pregunta (consulte la lista anterior).

Lo que realmente extraño en ambas soluciones es que el error de validación no se adjunta al name de campo del Genre del modelo y se pierde la entrada del usuario en el formulario.

Lo que me gustaría es agregar el error de validación para Genre.name a los errores de validación existentes, mantener la entrada del usuario y hacer esto solo para crear, no para actualizar.

Mis ideas fueron algo como esto:

 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') 

¿Es esto posible o hay alguna otra forma de lograr el objective mencionado anteriormente: mantener la entrada del usuario y agregar el error de validación adjunto al name del campo a la lista de errores de validación existentes al crear una nueva instancia?

Así es como lo hice:

 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 

De esta manera, la validación se activa solo cuando se crea un nuevo objeto Genre ( POST ), no cuando se actualiza ( PUT ).
Cuando se crea un nuevo objeto Book , la validación para Genre se propaga al serializador nested.
Todas las entradas del formulario se conservan después de la validación y el mensaje de error se adjunta al name campo.

Que en realidad cumple todos mis criterios. Aunque no tengo la sensación de que esta sea la forma correcta de hacerlo. Todavía me gustaría saber cómo puedo llamar manualmente el UniqueValidator en validate_name , en lugar de reinventar esa validación.

EDITAR:

Descubrí una forma de llamar al UniqueValidator en el método:

 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 

Lo que realmente extraño en ambas soluciones es que el error de validación no se adjunta al nombre de campo del género del modelo y se pierde la entrada del usuario en el formulario.

Esto es sencillo:

 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'] } }) )