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

Har*_*rel 26 django rest json multipartform-data django-rest-framework

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

但是,我似乎无法做到这两点.如果我将带有文件的有效负载作为多部分请求发送,则在request.data中以错误的方式提供JSON有效负载(第一个文本部分,直到第一个冒号为键,其余为数据).我可以很好地发送标准表单参数中的参数 - 但我的其余API接受JSON有效负载,我希望保持一致.request.body因为它引发而无法读取*** RawPostDataException: You cannot access body after reading from request's data stream

例如,请求正文中的文件和此有效负载:
{"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"}']}>

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

编辑:有人建议这可能是Django REST Framework上传图片的副本 :"提交的数据不是文件".它不是.上传和请求是在​​multipart中完成的,请记住文件并上传它很好.我甚至可以使用标准表单变量来完成请求.但我想知道我是否可以在那里获得JSON有效负载.

Nit*_*hin 6

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

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

型号

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')
Run Code Online (Sandbox Code Playgroud)

serializers.py->无需特殊更改,由于可写的ManyToMany Field含义,此处没有显示我的序列化程序太长。

views.py

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

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

utils.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)
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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


小智 5

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

序列化器

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)
Run Code Online (Sandbox Code Playgroud)

看法

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)
Run Code Online (Sandbox Code Playgroud)

示例测试:

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)
Run Code Online (Sandbox Code Playgroud)

  • 但这不是发送 JSON。 (10认同)

the*_*tuf 5

我知道这是一个旧线程,但是我只是碰到了这一点。我不得不使用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)
Run Code Online (Sandbox Code Playgroud)

我使用的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'
  }
});
Run Code Online (Sandbox Code Playgroud)