web-dev-qa-db-ja.com

Django Rest Frameworkを使用して、ファイルをアップロードしてJSONペイロードを送信するにはどうすればよいですか?

JSONペイロードだけでなくファイルも受信できるDjango Rest Framework APIハンドラーを作成しようとしています。MultiPartParserをハンドラーパーサーとして設定しました。

ただし、両方を行うことはできません。ファイルと一緒にペイロードをマルチパートリクエストとして送信すると、JSONペイロードはrequest.dataでマングル形式で利用できます(キーとして最初のコロンまで、残りはデータです)。パラメーターを標準のフォームパラメーターで問題なく送信できますが、残りのAPIはJSONペイロードを受け入れ、一貫性を保ちたいと考えました。 _*** RawPostDataException: You cannot access body after reading from request's data stream_が発生するため、request.bodyを読み取ることができません

たとえば、リクエスト本文のファイルとこのペイロード:
_{"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 upload image: "提出されたデータはファイルではありませんでした" のコピーである可能性があります。リクエストはマルチパートで行われ、ファイルとアップロードは問題ありません。標準のフォーム変数でリクエストを完了することもできますが、代わりにJSONペイロードを取得できるかどうかを確認したいと思います。

25
Harel

ファイルをアップロードしてデータを送信する必要がある人にとっては、それを機能させるための直接的な方法はありません。このためのJSON API仕様には 未解決の問題 があります。私が見た可能性の1つは、 here に示すようにmultipart/relatedを使用することですが、DRFで実装するのは非常に難しいと思います。

最後に、リクエストをformdataとして送信することで実装しました。各ファイルをfileとして送信し、他のすべてのデータをテキストとして送信します。これで、データをテキストとして送信するために、dataと呼ばれる単一のキーを使用し、json全体を値の文字列として送信できます。

Models.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->特別な変更は必要ありません。書き込み可能なManyToManyフィールド実装のため、ここではシリアライザーが長すぎると表示されません。

views.py

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

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)

郵便配達員のリクエスト例 case2

編集:

各データをキーと値のペアとして送信する場合は、 this 拡張回答を参照してください

6
nithin

私はこれが古いスレッドであることを知っていますが、私はこれに出くわしました。ファイルと追加のデータを一緒に取得するには、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)

ng-file-uploadを使用する私のAngularJSコードは次のとおりです。

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

製品オブジェクトを作成/更新するために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)

見る

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)

テスト例:

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)
3
sarc360

これがオプションである場合、マルチパート投稿と通常のビューを使用するのは非常に簡単です。

JSONをフィールドとして送信し、ファイルをファイルとして送信してから、1つのビューで処理します。

簡単なpythonクライアントとDjangoサーバー:

クライアント-複数のファイルと任意の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)

サーバー-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)
0
user1656671