Django Rest Framework:如何将数据传递给嵌套的序列化器并仅在自定义验证后创建对象

Posted

技术标签:

【中文标题】Django Rest Framework:如何将数据传递给嵌套的序列化器并仅在自定义验证后创建对象【英文标题】:Django Rest Framework: How to pass data to a nested Serializer and create an object only after custom validation 【发布时间】:2019-02-20 08:51:12 【问题描述】:

我有两个模型:

class Book(AppModel):
    title = models.CharField(max_length=255)

class Link(AppModel):
    link = models.CharField(max_length=255)

class Page(AppModel):
    book= models.ForeignKey("Book", related_name="pages", on_delete=models.CASCADE)
    link = models.ForeignKey("Link", related_name="pages", on_delete=models.CASCADE)
    page_no = models.IntegerField()
    text = models.TextField()

serializers

class LinkSerializer(serializers.ModelSerializer):
    class Meta:
       model = Link
       fields = ['link']

class PageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Page
        fields = ('link','text','page_no')

    def validate_text(self, value):
        #some validation is done here.

    def validate_link(self, value):
        #some validation is done here.

class BookSerializer(serializers.ModelSerializer):
    pages = PageSerializer(many=True)
    class Meta:
        model = Book
        fields = ('title','pages')

    @transaction.atomic
    def create(self, validated_data):
        pages_data= validated_data.pop('pages')
        book = self.Meta.model.objects.create(**validated_data)
        for page_data in pages_data:
            Page.objects.create(book=book, **page_data)
        return book

PageSerializer 中有一个validate_text 方法。 create 方法永远不会调用 PageSerializer 并且 page_data 永远不会被验证。

所以我尝试了另一种方法:

@transaction.atomic
def create(self, validated_data):
    pages_data = validated_data.pop('pages')
    book = self.Meta.model.objects.create(**validated_data)
    for page_data in pages_data:
        page = Page(book=book)
        page_serializer = PageSerializer(page, data = page_data)
        if page_serializer.is_valid():
            page_serializer.save()
        else:
            raise serializers.ValidationError(page_serializer.errors)
    return book

发布的数据:


    "title": "Book Title",
    "pages": [
        
            "link": 1, "page_no": 52, "text": "sometext"
        
    ]

但是上面的方法会报错:


    "link": [
        "Incorrect type. Expected pk value, received Link."
    ]

我还发现了导致此错误的原因:虽然我正在发布具有 Linkpk1 的数据,但从 BookSerializer 传递到 PageSerializer 时的数据显示如下:@ 987654337@

为什么Link 的实例传递给PageSerializer 而我发送的是Linkpk

【问题讨论】:

PageSerializer 需要一个 Comment 实例,而您正在传递一个 Page 实例 @JPG 抱歉,这是一个错误。这是Page 实例。这些不是我的实际代码。仅用于说明。 【参考方案1】:

当您调用serializer.is_valid(raise_exception=True/False) 时,它会自动调用嵌套序列化程序的验证函数。当您调用serializer.save(**kwargs) 时,序列化程序会将经过验证的数据传递到您的create(self, validated_data)update(self, instance, validated_data) 序列化程序函数中。此外,在经过验证的数据中,您的 ForeignKey 字段返回了一个对象。

def create(self, validated_data):
    pages_data = validated_data.pop('pages') # ['link': Linkobject, ...]
    book= self.Meta.model.objects.create(**validated_data)
    for page_data in pages_data:
        page = Page(book=book)
        page_serializer = PageSerializer(page, data = page_data) # here you are sending object to validation again
        if page_serializer.is_valid():
            page_serializer.save()
        else:
            raise serializers.ValidationError(page_serializer.errors)
    return book

【讨论】:

这不能回答我的问题。当数据被传递到序列化器中的嵌套序列化器时,如果有任何带有id 的字段,则永远不会传递id,而是传递与该id 相关的对象。以及如何克服这一点?如何传递id 而不是object 为了做到这一点,您应该将链接定义为PageSerializer 中的整数字段,例如id=serializers.IntegerField() 事实并非如此。在book 中创建pages 时,不要在当前序列化程序中编写验证,如果我可以使用PageSerializer 本身的验证,那将是更干燥的方式。此外,这里的问题是 id 从未传递给嵌套序列化程序。相反,即使我将 id 发布到父序列化程序,对象本身也会被传递。 在这种情况下你不需要重写,你的序列化器的第一个定义就足够了。如果您想确保可以调试您的第一个定义的序列化程序验证。当您调用BookSerializers save 方法时,它会使用已验证的数据调用 save 方法,甚至 page_data 也已验证。 验证器没问题。我已经调试了一切。请检查问题。调试部分在底部。【参考方案2】:

使用嵌套序列化器验证嵌套对象:

@transaction.atomic
def create(self, validated_data):
    pages_data = validated_data.pop('pages') #pages data of a book
    book= self.Meta.model.objects.create(**validated_data)
    for page_data in pages_data:
        page = Page(book=book)
        page_serializer = PageSerializer(page, data = page_data)
        if page_serializer.is_valid(): #PageSerializer does the validation
            page_serializer.save()
        else:
            raise serializers.ValidationError(page_serializer.errors) #throws errors if any
    return book

假设您将数据发送为:


    "title": "Book Title",
    "pages": [
        "link":2,#<= this one here
        "page_no":52, 
        "text":"sometext"]

在上述数据中,我们发送了Link 对象的id。但是在上面定义的BookSerializercreate方法中,我们发送的数据变成了:


    "title": "Book Title",
    "pages": [
        "link":Link Object (2),#<= changed to the Link object with id 2
        "page_no":52, 
        "text":"sometext"]

PageSerializer 实际上意味着接收linkpk 值,即"link": 2 而不是"link":Link Object (2)。因此它会抛出错误:

"link": [ "Incorrect type. Expected pk value, received Link." ]

所以解决方法是重写嵌套序列化程序的to_internal_value 方法,将接收到的Link Object (2) 对象转换为其pk 值。

所以你的PageSerializer 类应该是:

class PageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Page
        fields = ('link','text','page_no')

    def to_internal_value(self, data): 
        link_data = data.get("link")
        if isinstance(link_data, Link): #if object is received
            data["link"] = link_data.pk # change to its pk value
        obj = super(PageSerializer, self).to_internal_value(data)
        return obj

    def validate_text(self, value):
        #some validation is done here.

    def validate_link(self, value):
        #some validation is done here.

和父序列化器:

class BookSerializer(serializers.ModelSerializer):
    pages = PageSerializer(many=True)
    class Meta:
        model = Book
        fields = ('title','pages')

    @transaction.atomic
    def create(self, validated_data):
        pages_data = validated_data.pop('pages')#pages data of a book
        book= self.Meta.model.objects.create(**validated_data)
        for page_data in pages_data:
            page = Page(book=book)
            page_serializer = PageSerializer(page, data = page_data)
            if page_serializer.is_valid(): #PageSerializer does the validation
                page_serializer.save()
            else:
                raise serializers.ValidationError(page_serializer.errors) #throws errors if any
        return book

这应该允许嵌套序列化程序进行验证,而不是在父序列化程序的 create 方法中编写验证并违反 DRY。

【讨论】:

以上是关于Django Rest Framework:如何将数据传递给嵌套的序列化器并仅在自定义验证后创建对象的主要内容,如果未能解决你的问题,请参考以下文章

如何仅使用 django 作为后端并使用 django-rest-framework 发布

Django - 如何通过 API 端点使用 rest_framework 动态地将多个对象添加到数据库中

如何使用 django-rest-framework 进行社交登录? [关闭]

如何将命名空间 url 添加到 django-rest-framework 路由器视图集

如何使用 Django Rest Framework 将 url 字段添加到序列化程序

如何使用 django-rest-framework 中的序列化器将相似数据合并到自定义字段中?