Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

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

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"}']}>

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.

like image 650
Harel Avatar asked May 11 '15 19:05

Harel


People also ask

How do I handle file upload in Django REST Framework?

So you have two choices: let ModelViewSet and ModelSerializer handle the job and send the request using content-type=multipart/form-data; set the field in ModelSerializer as Base64ImageField (or) Base64FileField and tell your client to encode the file to Base64 and set the content-type=application/json.

What is JSON parser in Django?

JSONParser. Parses JSON request content. request. data will be populated with a dictionary of data. .media_type: application/json.

What is payload in Django?

django-filer stores the meta-data of each file in the database, while the files payload is stored on disk. This is fine, since large binary data shall only exceptionally be stored in a relational database.


2 Answers

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 issue in json api specs for this. One possibility i have seen is to use multipart/related as shown here, but i think its very hard to implement it in drf.

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

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 -> no special changes needed, not showing my serializer here as its too lengthy because of the writable ManyToMany Field implimentation.

views.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.

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) 

The request example in postman case2

EDIT:

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

like image 105
Nithin Avatar answered Oct 07 '22 07:10

Nithin


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

# 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-upload is:

file.upload = Upload.upload({   url: "/api/picture/upload/" + file.name,   data: {     file: file,     ftype: 'final',     caption: 'This is an image caption'   } }); 
like image 21
themanatuf Avatar answered Oct 07 '22 07:10

themanatuf