django-rest-framework 3.0在嵌套序列化程序中创建或更新


73

使用django-rest-framework 3.0并具有以下简单模型:

class Book(models.Model):
    title = models.CharField(max_length=50)


class Page(models.Model):
    book = models.ForeignKey(Books, related_name='related_book')
    text = models.CharField(max_length=500)

并给出此JSON请求:

{
   "book_id":1,
   "pages":[
      {
         "page_id":2,
         "text":"loremipsum"
      },
      {
         "page_id":4,
         "text":"loremipsum"
      }
   ]
}

如何编写嵌套的序列化程序来处理此JSON,并为page给定的每个JSONbook创建一个新页面或更新(如果存在)。

class RequestSerializer(serializers.Serializer):
    book_id = serializers.IntegerField()
    page = PageSerializer(many=True)


class PageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Page

我知道用实例化序列化程序instance会更新当前序列化程序,但是我应该如何在create嵌套序列化程序的方法中使用它呢?

Answers:


107

首先,您要支持创建新的图书实例还是仅更新现有的图书实例?

如果您只想创建新的图书实例,则可以执行以下操作...

class PageSerializer(serializers.Serializer):
    text = serializers.CharField(max_length=500)

class BookSerializer(serializers.Serializer):
    page = PageSerializer(many=True)
    title = serializers.CharField(max_length=50)

    def create(self, validated_data):
        # Create the book instance
        book = Book.objects.create(title=validated_data['title'])

        # Create or update each page instance
        for item in validated_data['pages']:
            page = Page(id=item['page_id'], text=item['text'], book=book)
            page.save()

        return book

请注意,我没有book_id此处添加。在创建图书实例时,我们将不会包含图书ID。当我们更新图书实例时,通常会将图书ID包含在URL中,而不是包含在请求数据中。

如果要同时支持创建和更新工作簿实例,则需要考虑如何处理请求中未包含的页面,但是 当前与书实例相关联。

您可能会选择静默忽略那些页面并将其保留为原样,您可能想要引发验证错误,或者可能想要删除它们。

假设您要删除请求中未包含的所有页面。

def create(self, validated_data):
    # As before.
    ...

def update(self, instance, validated_data):
    # Update the book instance
    instance.title = validated_data['title']
    instance.save()

    # Delete any pages not included in the request
    page_ids = [item['page_id'] for item in validated_data['pages']]
    for page in instance.books:
        if page.id not in page_ids:
            page.delete()

    # Create or update page instances that are in the request
    for item in validated_data['pages']:
        page = Page(id=item['page_id'], text=item['text'], book=instance)
        page.save()

    return instance

也有可能,你可能希望支持本书的更新,而不是支持创作,在这种情况下,只有包括update()方法。

您还可以通过多种方式减少查询数量,例如。使用批量创建/删除操作,但以上操作将以非常简单的方式完成工作。

如您所见,在处理嵌套数据时,您可能想要的行为类型有些微妙,因此请仔细考虑在各种情况下期望的行为。

另请注意,我Serializer在上面的示例中一直使用而不是ModelSerializer。在这种情况下,仅将所有字段显式包含在serializer类中,而不是依赖于自动ModelSerializer默认情况下生成的一。


1
you might want to only support book updates ... , only include the update() method。在这种情况下,instancein更新方法将如何用现有书籍填充?
山姆R.

1
谢谢汤姆。我知道了
山姆R.

2
为什么覆盖在序列化器中而不在视图中,例如django-rest-framework.org/api-guide/viewsets/…
CheapD AKA 2015年

3
@TomChristie您能帮我个忙,看看我创建嵌套资源的尝试吗?在尝试django-rest-framework-nested-resourcedrf-extensionsdrf-nested-router之后,我一无所获-都没有成功。我很高兴切换到实际可行的方法。
JJD

2
@TomChristie如果我使用ModelSerializer而不是Serializer,它将过滤掉page_id
萨桑

3

您可以简单地使用drf-writable-nested。它会自动使您的嵌套序列化器可写和可更新。

在你里面serializers.py

from drf_writable_nested import WritableNestedModelSerializer

class RequestSerializer(WritableNestedModelSerializer):
    book_id = serializers.IntegerField()
    page = PageSerializer(many=True)


class PageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Page

就是这样!

如果您不需要两者兼备,该库也仅支持使用createandupdate逻辑之一。


如何仅通过ID允许创建和编辑嵌套实体?
Alex78191

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.