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ペイロードを取得できるかどうかを確認したいと思います。
ファイルをアップロードしてデータを送信する必要がある人にとっては、それを機能させるための直接的な方法はありません。このための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)
編集:
各データをキーと値のペアとして送信する場合は、 this 拡張回答を参照してください
私はこれが古いスレッドであることを知っていますが、私はこれに出くわしました。ファイルと追加のデータを一緒に取得するには、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'
}
});
製品オブジェクトを作成/更新するために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)
これがオプションである場合、マルチパート投稿と通常のビューを使用するのは非常に簡単です。
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)