【问题标题】:DRF: Validate nested serializer data when creating, but not when updatingDRF:在创建时验证嵌套的序列化程序数据,但在更新时不验证
【发布时间】:2018-06-24 07:43:15
【问题描述】:

在 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

为简单起见,我们以第一个问题为例:

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 中的唯一性。

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

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

我想要将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 的验证错误添加到现有验证错误列表中?

【问题讨论】:

  • “保持使用过的输入”是什么意思?
  • “保留用户输入”是指在验证后保留表单中的所有用户输入。
  • 我不明白。嵌套表单不起作用。
  • @Linovia 我发布了自己的答案,保留了表单中的用户输入。

标签: python django django-rest-framework


【解决方案1】:

我就是这样做的:

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

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

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

编辑:

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

【讨论】:

    【解决方案2】:

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

    这是直截了当的:

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

    【讨论】:

    • 点 (exceptions. ) 后面的空格是错字吗?是否可以在检查中以编程方式调用UniqueValidator 而不是提供自定义消息错误?
    • 是的,这是一个错字,您可以致电 UniqueValidator,但我认为您不会在异常中获得字段名称。
    【解决方案3】:

    除了@cezar 的回答,我想知道如何不仅基于条件覆盖,而且完全覆盖和忽略任何字段。

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

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-19
      • 1970-01-01
      • 2020-08-14
      • 2015-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-06
      相关资源
      最近更新 更多