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.
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.
JSONParser. Parses JSON request content. request. data will be populated with a dictionary of data. .media_type: application/json.
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.
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
EDIT:
see this extended answer if you want to send each data as key value pair
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' } });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With