django REST框架嵌套的序列化程序和POST嵌套的JSON文件

时间:2017-12-05 18:41:33

标签: javascript django http vue.js django-rest-framework

如何将包含文件(图像)的嵌套数据的POST请求发送到django REST嵌套序列化程序?

鉴于此JS对象:

bookData: {
    title: 'Anne of Green Gables',
    coverImage: File(123456),
    pages: 123,
    author: {
        name: 'Lucy Maud Montgomery',
        born: 1874,
        profilepicture_set: [
            {file: File(234567), description: 'Young L. M. Montgomery'},
            {file: File(234568), description: 'Old L. M. Montgomery}
        ],
        quote_set: [
            {text: "I'm so glad I live in a world where there are Octobers."},
            {text: "True friends are always together in spirit."},
        ]
    },

}

我想向我的django REST API发送POST请求(我在前端使用VueJS,但这并不重要)。

# views.py
class CreateBookView(generics.CreateAPIView):
    serializer_class = CreateBookSerializer
    queryset = Book.objects.all()

# serializers.py
class CreateBookSerializer(serializers.ModelSerializer):
    author = CreateAuthorSerializer()

    class Meta:
        model = Book
        fields = ('title', 'pages', 'author')

    @transaction.atomic
    def create(self, validated_data):
        author_data = validated_data.pop('author')
        uploader = self.context['request'].user
        book = Book.objects.create(uploader=uploader, **validated_data)

        Author.objects.create(book=book, **author_data)

        return book


# models.py
class AuthorManager(models.Manager):

    def create(self, **author_data):
        quotes_data = author_data.pop('quote_set')
        photos_data = author_data.pop('profilepicture_set')

        author = Author(**author_data)
        author.save()

        for quote_data in quotes_data:
            Quote.objects.create(author=author, **quote_data)

        for photo_data in photos_data :
            ProfilePicture.objects.create(author=author, **photo_data)

        return author

## Skipping the CreateAuthorSerializer, Quote and ProfilePicture Managers as they are analogous.
## Assume one Author can only write one Book (OneToOneField). No need to look the name up for existing Authors.

编辑

我想添加一些关于我如何在VueJS的前端发送数据的信息。

sendData(){
    var fd = new FormData()
    var bookData = {
        title: this.$store.getters.title, # 'Anne of Green Gables"
        coverImage: this.$store.getters.coverImage, # File(somesize)
        pages: this.$store.getters.pages, # 123
        author: this.$store.getters.author, # nested object 
        ...
    }
    fd = objectToFormData(bookData, fd) # external function, see below
    this.$http.post('api/endpoint/create/', fd, headers: {
        'Content-Type': 'multipart/form-data',
        'X-CSRFToken': Cookies.get('csrftoken'),
        }
    }).then(...)
}

点击按钮时会调用sendData()objectToFormData是来自npm (click)的外部库,它将嵌套对象转换为平面形式。

此解决方案的问题(以及@Saji Xavier的解决方案)是request.data包含一个丑陋的QueryDict,它远离原始的bookData对象结构,并且不被REST序列化程序接受(即author字段缺失 - 这是真的,对象已被展平)。

<QueryDict: {
'title': ['Anne of Green Gables'], 
'coverImage':[InMemoryUploadedFile: cover.jpg (image/jpeg)],
'pages': ['123'], 
'name': ['Lucy Maud Montgomery'], 
'born': ['1874'],
'profilepicture_set[][description]': ['Young L. M. Montgomery', 'Old L. M. Montgomery'], 
'profilepicture_set[][file]': [
    InMemoryUploadedFile: young.jpg (image/jpeg), 
    InMemoryUploadedFile: old.jpg (image/jpeg)
    ],
'quote_set[][text]': [
    "I'm so glad I live in a world where there are Octobers.",
    "True friends are always together in spirit."
    ]
}>

我现在如何处理它?这看起来太丑了。我能想到的是覆盖create()函数。

class CreateBookView(generics.CreateAPIView):
    (...)
    def create(self, request, *args, **kwargs):
        book_data = {}
        book_data['title'] = request.data['title']
        book_data['pages'] = request.data['pages']
        book_data['cover_image'] = request.data['coverImage']
        book_data['author'] = {}
        book_data['author']['name'] = request.data['name']
        book_data['author']['born'] = request.data['born']
        (...)
        serializer = self.get_serializer(data=book_data)
        (...)

这是一个非常难看的解决方案,对于具有多个字段和多个嵌套级别的模型无效(本书/作者只是一个玩具示例)。

我该如何处理?我是否改变了POST的方式(获得一个不错的request.data格式)还是我处理这个丑陋的方式?

1 个答案:

答案 0 :(得分:0)

根据DRF documentation

  

大多数解析器,例如JSON不支持文件上传。 Django的&#39; S   常规FILE_UPLOAD_HANDLERS用于处理上传的文件。

一种选择是使用Javascript FormData API发送多部分数据(与VueJS / AngularJS一起使用)

var fd = new FormData();
fd.append('title', 'Anne of Green Gables')
fd.append('author_name', 'Lucy Maud Montgomery')
fd.append('author_profilepicture_set', files[])
$http.post('url', fd, {
   headers: {
       'Content-Type': 'multipart/form-data'
   }
});