python - DRF : Validate nested serializer data when creating, 但更新时没有

标签 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

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

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

现在的问题是唯一性验证在创建时也被删除了。这可以在 View 中被拦截,例如:

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer = BookSerializer

    def perform_create(self):
        # implement logic and raise ValidationError

但是这感觉不太对,因为我们正在验证 BookViewSetGenre 的唯一性。

另一种选择是在 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 的验证错误添加到现有验证错误列表中?

最佳答案

我是这样做的:

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

编辑(2022-09-09):

self.context['request']._request.method 相比,使用 self.context['request'].method 更简单、更清晰。

使用 Django REST Framework 3.13 进行了尝试和测试。

关于python - DRF : Validate nested serializer data when creating, 但更新时没有,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48267017/

相关文章:

python - 安装Python的Mageck包时修复C编译环境错误的问题

python - 为什么你可以'\xe 5' be decoded but not '\xe5'?

python - 如何让 Django 从 my.cnf 读取 MySQL 密码?

Django if 标签在这里不起作用

Django 模板 for 循环并显示前 X 个匹配项

json - 超出范围的浮点值与 Django 渲染不兼容 JSON

python - 有人可以建议如何实现自定义 Django PasswordResetView 吗?

python - pandas - 如何根据日期组织数据框并将新值分配给列

django - 如何在Django Admin中将user.is_staff默认设置为True?

django - 有条件地选择序列化程序