DRF:在创建时验证嵌套的序列化数据,但在更新

时间:2018-01-15 16:28:07

标签: python django django-rest-framework

在DRF中使用可写嵌套序列化程序时,验证最终唯一字段并阻止更新父序列化程序存在已知问题。在这样的问题中已经多次询问过这个问题:

  1. Unique validation on nested serializer on Django Rest Framework
  2. Django rest framework not creating object with FK to a model with unique=True field
  3. 为简单起见,我们以第一个问题为例:

    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
    

    现在的问题是,唯一性验证也会被删除以进行创建。这可以在视图中截获,例如:

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

    然而,这感觉不太正确,因为我们正在验证GenreBookViewSet的唯一性。

    另一种选择是在create() BookSerializer方法中实施验证,如第二个问题中所述(参见上面的列表)。

    我在两个解决方案中真正错过的是验证错误未附加到模型name的字段Genre,并且表单中的用户输入丢失。

    我想要的是将Genre.name的验证错误添加到现有的验证错误中,保留用户输入并仅在创建时执行此操作,而不是用于更新。

    我的想法是这样的:

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

    这是否可行或是否有其他方法可以实现前面提到的目标 - 在创建新实例时,保持用户输入并将字段name附加的验证错误添加到现有验证错误列表中?

3 个答案:

答案 0 :(得分:6)

我就这样做了:

Genre

通过这种方式,仅在创建新POST对象(PUT)时触发验证,而不是在更新(Book)时触发验证。 创建新的Genre对象后,name的验证将传播到嵌套的序列化程序。
验证后将保留所有表单输入,并将错误消息附加到字段UniqueValidator

这实际上符合我的所有标准。虽然我不觉得这是正确的做法。我仍然想知道如何在validate_name中手动调用UniqueValidator,而不是重新进行验证。

编辑:

我找到了一种如何在方法中调用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 的方法:

domain.com/sign-in
domain.com/cookie-policy

答案 1 :(得分:1)

  

我在两个解决方案中真正错过的是验证错误未附加到模型类型的字段名称,并且表单中的用户输入丢失。

这是直截了当的:

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

答案 2 :(得分:0)

除了@cezar的答案外,我想知道如何不仅根据条件进行覆盖,而且要完全覆盖和忽略任何字段。

通过覆盖to_internal_value的ParentSerialzer,可以“跳过”子序列化程序验证,或编写自定义序列化程序验证以覆盖子序列化程序:

class ParentSerializer(serializers.ModelSerializer):
    children = ChildSerializer(many=True)

    def to_internal_value(self, *args, **kwargs):
        try:
            # runs the child serializers
            return super().to_internal_value(*args, **kwargs)
        except ValidationError as e:
            # fails, and then overrides the child errors with the parent error
            return self.validate(self.initial_data)

    def validate(self, attrs):
        errors = {}
        errors['custom_override_error'] = 'this ignores and overrides the children serializer errors'
        if len(errors):
            raise ValidationError(errors)
        return attrs
    class Meta:
        model = Parent