使用 Django Rest Framework,如何上传文件并发送 JSON 有效负载?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30176570/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:53:49  来源:igfitidea点击:

Using Django Rest Framework, how can I upload a file AND send a JSON payload?

jsondjangorestdjango-rest-frameworkmultipartform-data

提问by Harel

I am trying to write a Django Rest Framework API handler that can receive a file as well as a JSON payload. I've set the MultiPartParser as the handler parser.

我正在尝试编写一个 Django Rest Framework API 处理程序,它可以接收文件和 JSON 负载。我已将 MultiPartParser 设置为处理程序解析器。

However, it seems I cannot do both. If I send the payload with the file as a multi part request, the JSON payload is available in a mangled manner in the request.data (first text part until the first colon as the key, the rest is the data). I can send the parameters in standard form parameters just fine - but the rest of my API accepts JSON payloads and I wanted to be consistent. The request.body cannot be read as it raises *** RawPostDataException: You cannot access body after reading from request's data stream

但是,似乎我不能两者兼而有之。如果我将有效负载与文件一起作为多部分请求发送,则 JSON 有效负载在 request.data 中以重整的方式可用(第一个文本部分直到第一个冒号作为键,其余部分是数据)。我可以以标准形式参数发送参数就好了 - 但我的 API 的其余部分接受 JSON 有效负载,我希望保持一致。request.body 引发时无法读取*** RawPostDataException: You cannot access body after reading from request's data stream

For example, a file and this payload in the request body:
{"title":"Document Title", "description":"Doc Description"}
Becomes:
<QueryDict: {u'fileUpload': [<InMemoryUploadedFile: 20150504_115355.jpg (image/jpeg)>, <InMemoryUploadedFile: Front end lead.doc (application/msword)>], u'{%22title%22': [u'"Document Title", "description":"Doc Description"}']}>

例如,请求正文中的文件和此有效负载:
{"title":"Document Title", "description":"Doc Description"}
变为:
<QueryDict: {u'fileUpload': [<InMemoryUploadedFile: 20150504_115355.jpg (image/jpeg)>, <InMemoryUploadedFile: Front end lead.doc (application/msword)>], u'{%22title%22': [u'"Document Title", "description":"Doc Description"}']}>

Is there a way to do this? Can I eat my cake, keep it and not gain any weight?

有没有办法做到这一点?我可以吃我的蛋糕,保持它而不增加任何重量吗?

Edit: It was suggested that this might be a copy of Django REST Framework upload image: "The submitted data was not a file". It is not. The upload and request is done in multipart, and keep in mind the file and upload of it is fine. I can even complete the request with standard form variables. But I want to see if I can get a JSON payload in there instead.

编辑:有人建议这可能是Django REST Framework 上传图片的副本 :“提交的数据不是文件”。它不是。上传和请求是多部分完成的,记住文件和上传是好的。我什至可以用标准的表单变量完成请求。但我想看看我是否可以在那里获得 JSON 有效负载。

回答by Nithin

For someone who needs to upload a file and send some data, there is no straight fwd way you can get it to work. There is an open issuein json api specs for this. One possibility i have seen is to use multipart/relatedas shown here, but i think its very hard to implement it in drf.

对于需要上传文件并发送一些数据的人来说,没有直接的方法可以让它工作。json api 规范中有一个未解决的问题。我看到的一种可能性是使用此处multipart/related所示,但我认为它很难在 drf 中实现。

Finally what i had implemented was to send the request as formdata. You would send each file as fileand all other data as text. Now for sending the data as text you can have a single key called dataand send the whole json as string in value.

最后我实现的是将请求作为formdata. 您可以将每个文件作为文件发送,将所有其他数据作为文本发送。现在要将数据作为文本发送,您可以使用一个名为data 的键,并将整个 json 作为值中的字符串发送。

Models.py

模型.py

class Posts(models.Model):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    caption = models.TextField(max_length=1000)
    media = models.ImageField(blank=True, default="", upload_to="posts/")
    tags = models.ManyToManyField('Tags', related_name='posts')

serializers.py -> no special changes needed, not showing my serializer here as its too lengthy because of the writable ManyToMany Field implimentation.

serializers.py -> 不需要特殊更改,由于可写的 ManyToMany 字段实现,这里没有显示我的序列化程序太长。

views.py

视图.py

class PostsViewset(viewsets.ModelViewSet):
    serializer_class = PostsSerializer
    parser_classes = (MultipartJsonParser, parsers.JSONParser)
    queryset = Posts.objects.all()
    lookup_field = 'id'

You will need custom parser as shown below for parsing json.

您将需要如下所示的自定义解析器来解析 json。

utils.py

实用程序.py

from django.http import QueryDict
import json
from rest_framework import parsers

class MultipartJsonParser(parsers.MultiPartParser):

    def parse(self, stream, media_type=None, parser_context=None):
        result = super().parse(
            stream,
            media_type=media_type,
            parser_context=parser_context
        )
        data = {}
        # find the data field and parse it
        data = json.loads(result.data["data"])
        qdict = QueryDict('', mutable=True)
        qdict.update(data)
        return parsers.DataAndFiles(qdict, result.files)

The request example in postman case2

邮递员中的请求示例 案例2

EDIT:

编辑:

see thisextended answer if you want to send each data as key value pair

如果您想将每个数据作为键值对发送,请参阅扩展答案

回答by themanatuf

I know this is an old thread, but I just came across this. I had to use MultiPartParserin order to get my file and extra data to come across together. Here's what my code looks like:

我知道这是一个旧线程,但我刚刚遇到了这个。我必须使用MultiPartParser才能将我的文件和额外数据放在一起。这是我的代码的样子:

# views.py
class FileUploadView(views.APIView):
    parser_classes = (MultiPartParser,)

    def put(self, request, filename, format=None):
        file_obj = request.data['file']
        ftype    = request.data['ftype']
        caption  = request.data['caption']
        # ...
        # do some stuff with uploaded file
        # ...
        return Response(status=204)

My AngularJS code using ng-file-uploadis:

我使用的 AngularJS 代码ng-file-upload是:

file.upload = Upload.upload({
  url: "/api/picture/upload/" + file.name,
  data: {
    file: file,
    ftype: 'final',
    caption: 'This is an image caption'
  }
});

回答by sarc360

I send JSON and an image to create/update a product object. Below is a create APIView that works for me.

我发送 JSON 和图像来创建/更新产品对象。下面是一个对我有用的创建 APIView。

Serializer

序列化器

class ProductCreateSerializer(serializers.ModelSerializer):
    class Meta:
         model = Product
        fields = [
            "id",
            "product_name",
            "product_description",
            "product_price",
          ]
    def create(self,validated_data):
         return Product.objects.create(**validated_data)

View

看法

from rest_framework  import generics,status
from rest_framework.parsers import FormParser,MultiPartParser

class ProductCreateAPIView(generics.CreateAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductCreateSerializer
    permission_classes = [IsAdminOrIsSelf,]
    parser_classes = (MultiPartParser,FormParser,)

    def perform_create(self,serializer,format=None):
        owner = self.request.user
        if self.request.data.get('image') is not None:
            product_image = self.request.data.get('image')
            serializer.save(owner=owner,product_image=product_image)
        else:
            serializer.save(owner=owner)

Example test:

示例测试:

def test_product_creation_with_image(self):
    url = reverse('products_create_api')
    self.client.login(username='testaccount',password='testaccount')
    data = {
        "product_name" : "Potatoes",
        "product_description" : "Amazing Potatoes",
        "image" : open("local-filename.jpg","rb")
    }
    response = self.client.post(url,data)
    self.assertEqual(response.status_code,status.HTTP_201_CREATED)

回答by user1656671

It is very simple to use a multipart post and a regular view, if this is an option.

如果这是一个选项,使用多部分帖子和常规视图非常简单。

You send the json as a field and files as files, then process in one view.

您将 json 作为字段发送,将文件作为文件发送,然后在一个视图中进行处理。

Here is a simple python client and a Django server:

这是一个简单的 python 客户端和一个 Django 服务器:

Client - sending multiple files and an arbitrary json-encoded object:

客户端 - 发送多个文件和任意的 json 编码对象:

import json
import requests

payload = {
    "field1": 1,
    "manifest": "special cakes",
    "nested": {"arbitrary":1, "object":[1,2,3]},
    "hello": "word" }

filenames = ["file1","file2"]
request_files = {}
url="example.com/upload"

for filename in filenames:
    request_files[filename] = open(filename, 'rb')

r = requests.post(url, data={'json':json.dumps(payload)}, files=request_files)

Server - consuming the json and saving the files:

服务器 - 使用 json 并保存文件:

@csrf_exempt
def upload(request):
    if request.method == 'POST':
        data = json.loads(request.POST['json']) 
        try:
            manifest = data['manifest']
            #process the json data

        except KeyError:
            HttpResponseServerError("Malformed data!")

        dir = os.path.join(settings.MEDIA_ROOT, "uploads")
        os.makedirs(dir, exist_ok=True)

        for file in request.FILES:
            path = os.path.join(dir,file)
            if not os.path.exists(path):
                save_uploaded_file(path, request.FILES[file])           

    else:
        return HttpResponseNotFound()

    return HttpResponse("Got json data")


def save_uploaded_file(path,f):
    with open(path, 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)

回答by Tobias Ernst

The following code worked for me.

以下代码对我有用。

from django.core.files.uploadedfile import SimpleUploadedFile
import requests
from typing import Dict

with open(file_path, 'rb') as f:
    file = SimpleUploadedFile('Your-Name', f.read())

    data: Dict[str,str]
    files: Dict[str,SimpleUploadedFile] = {'model_field_name': file}

    requests.put(url, headers=headers, data=data, files=files)
    requests.post(url, headers=headers, data=data, files=files)

'model_field_name'is the name of the FileFieldor ImageFieldin your Django model. You can pass other data as nameor locationas usual by using dataparameter.

'model_field_name'是Django 模型中FileField或的名称ImageField。你可以通过其他的数据,name或者location通过使用常用data参数。

Hope this helps.

希望这可以帮助。