Django Rest Framework 中嵌套序列化程序的唯一验证

Posted

技术标签:

【中文标题】Django Rest Framework 中嵌套序列化程序的唯一验证【英文标题】:Unique validation on nested serializer on Django Rest Framework 【发布时间】:2016-11-21 03:31:00 【问题描述】:

我有一个这样的案例,您有一个具有唯一字段的自定义嵌套序列化程序关系。示例案例:

class GenreSerializer(serializers.ModelSerializer):

    class Meta:
        fields = ('name',) #This field is unique
        model = Genre

class BookSerializer(serializers.ModelSerializer):

    genre = GenreSerializer()

    class Meta:
        model = Book
        fields = ('name', 'genre')

    def create(self, validated_data):
        genre = validated_data.pop('genre')
        genre = Genre.objects.get(**genre)
        return Book.objects.create(genre=genre, **validated_data)

问题:当我尝试保存一个像 "name":"The Prince", "genre": "name":"History" 的 json 对象时,DRF 尝试验证流派对象唯一约束,如果存在 "History" 会抛出异常,因为名称为 "History" 的流派必须是唯一的,这是真的,但我只是想关联对象而不是一起创建。

非常感谢!!

【问题讨论】:

【参考方案1】:

您应该删除嵌套序列化程序的唯一验证器:

class GenreSerializer(serializers.ModelSerializer):

    class Meta:
        fields = ('name',) #This field is unique
        model = Genre
        extra_kwargs = 
            'name': 'validators': [],
        

您可能需要先打印序列化程序,以确保该字段上没有其他验证程序。如果您有一些,则必须将它们包括在列表中。

编辑:如果你需要确保创建的唯一性约束,你应该在serializer.is_valid被调用之后和serializer.save之前在视图中进行。

【讨论】:

非常感谢!但是,当我使用嵌套序列化程序保存类型实例时,如果我需要在嵌套序列化程序中进行验证?有没有办法只检查我是否创建了 Genre 实例而不是创建 Book 实例?再次感谢您! 这应该是第二个验证步骤的一部分——比如在创建/更新部分通过引发 ValidationError。 这真的很有帮助!非常感谢! 另见this和this了解更多信息。 perform_create 是一个很好的匹配,因为您可以避免从视图集的 create 复制代码。也可以在序列化程序的create/update 中完成,尽管我不喜欢从那里引发验证错误的想法。最后,您还可以将序列化程序的保存设置为无操作,并使用业务层执行最后一英里验证并处理相关逻辑。【参考方案2】:

发生这种情况是因为嵌套序列化程序 (GenreSerializer) 需要一个对象实例来正确验证唯一约束(例如将 exclude 子句放入验证时使用的查询集),默认情况下,序列化程序不会通过当运行to_internal_value() 方法时,与归档相关的对象的实例是嵌套的序列化程序。见here

解决这个问题的另一种方法是覆盖父序列化器上的get_fields()方法并传递相关对象的实例

class BookSerializer(serializers.ModelSerializer):

    def get_fields(self):
        fields = super(BookSerializer, self).get_fields()
        try: # Handle DoesNotExist exceptions (you may need it)
            if self.instance and self.instance.genre:
                fields['genre'].instance = self.instance.genre
        except Genre.DoesNotExist:
            pass
        return fields

【讨论】:

您始终设置当前的流派对象。如果我们处于更新情况,这可能已经改变。所以只有“旧”对象会被验证。那是一个错误,我猜。【参考方案3】:

一起删除UniqueValidator 使用

'name': 'validators': []

您需要自己验证 Unique 条目而忽略当前对象,因为当其他人尝试保存相同的名称时不会收到 500 错误,这样的事情会起作用:

    def validate_name(self, value):
        check_query = Genre.objects.filter(name=value)
        if self.instance:
            check_query = check_query.exclude(pk=self.instance.pk)

        if self.parent is not None and self.parent.instance is not None:
            genre = getattr(self.parent.instance, self.field_name)
            check_query = check_query.exclude(pk=genre.pk)

        if check_query.exists():
            raise serializers.ValidationError('A Genre with this name already exists
.')
        return value

调用方法validate_<field> 来验证所有字段,请参阅docs。

【讨论】:

以上是关于Django Rest Framework 中嵌套序列化程序的唯一验证的主要内容,如果未能解决你的问题,请参考以下文章

需要 Django Rest Framework 嵌套序列化程序 = False 错误

在 Django REST Framework 中处理嵌套对象表示

Django REST Framework:在嵌套对象中定义字段?

django-rest-framework、多表模型继承、ModelSerializers 和嵌套序列化器

在 Django Rest Framework 中正确更新嵌套序列化程序

Django Rest Framework 中嵌套序列化程序的唯一验证