Question similar to this
django rest framework return file
Trying to apply a similar solution to return an executable python binary in Django Rest ApiView:
from wsgiref.util import FileWrapper
bin_file = open(f'cli/builds/dist/cli', 'rb')
response = Response(FileWrapper(bin_file), content_type='application/octet-stream')
response['Content-Disposition'] = f'attachment; filename="cli"'
response.status_code = status.HTTP_200_OK
return response
getting Object of type 'FileWrapper' is not JSON serializable error.
Referring to the previously mentioned SO topic - this solution is warking for a zip file.
Question - why doesn't it work for my setting, returning the python executable?
python 3.6.5,
djangorestframework==3.8.2
tried both Response and HttpResponse classes
Try to use HttpResponse instead of DRF's Response,
from wsgiref.util import FileWrapper
from django.http.response import HttpResponse
bin_file = open(f'cli/builds/dist/cli', 'rb')
response = HttpResponse(FileWrapper(bin_file), content_type='application/octet-stream')
response['Content-Disposition'] = f'attachment; filename="cli"'
response.status_code = status.HTTP_200_OK
return response
Related
I am given a zip file that I need to send to UI from where it should be downloaded for the user.
While testing on POSTMAN, using Send & Download button associated with POST, I am able to download the file. But when opening it, it says:
Windows cannot open the folder. The Compressed(zipped) folder <zip path> is invalid
Here's the code I am trying:
from django.response import Response
from rest_framework.views import APIView
def get_response():
with open(zip_file_path.zip, 'r', encoding='ISO-8859-1') as f:
file_data = f.read()
response = Response(file_data, content_type='application/zip')
response['Content-Disposition'] = 'attachment; filename="ReportTest.zip"'
return response
class GenerateZIP(APIView):
def post(self, request):
zip_file_response = get_response()
return zip_file_response
The zip file read is valid as it's already on the local.
Any ideas?
Rather than generate the response yourself, you can use a FileResponse which does it for you
from django.http import FileResponse
from rest_framework.views import APIView
class GenerateZIP(APIView):
def post(self, request):
return FileResponse(
open('zip_file_path.zip', 'rb'),
as_attachment=True,
filename='ReportTest.zip'
)
I wrote a really simple django project to test xlsxwriter. I can open the excel file, but when I name the file 'filename.xlsx', the file is downloaded as 'filename.xlsx.xls'. How can I fix this?
from django.shortcuts import render
from django.http import HttpResponse
from .excel import get_excel
def home_view(request):
response = HttpResponse(content_type='application/vnd.ms-excel')
response['Content-Disposition'] = 'attachment; filename=filename.xlsx'
excel_data = get_excel()
response.write(excel_data)
return response
XSLX is an OpenXML format, so the mimetype is different, it uses:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
you thus should change this to:
def home_view(request):
response = HttpResponse(
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
response['Content-Disposition'] = 'attachment; filename=filename.xlsx'
excel_data = get_excel()
response.write(excel_data)
return response
Django==1.11.12
If the file exists, the view function creates StreamingHttpResponse and all is fine. But if the file does not exist, the view function should redirect. The problem is, it does not redirect but instead prompt up to ask to save as a file, which has the content of redirected html.
import os
from wsgiref.util import FileWrapper
import mimetypes
from django.http import StreamingHttpResponse
from django.shortcuts import render, redirect
def download(request, file_path):
try:
response = StreamingHttpResponse(
FileWrapper(open(file_path, 'rb'), chunk_size),
content_type=mimetypes.guess_type(file_path)[0])
response['Content-Length'] = os.path.getsize(file_path)
response['Content-Disposition'] = "attachment; filename=a_file.xlsx")
except FileNotFoundError as e:
response = redirect("home")
return response
It turns out that the problem lays in template, where the download link is <a href="xxx" download> and the download attribute force the download.
I need to return generated file download as a Django REST Framework response. I tried the following:
def retrieve(self, request, *args, **kwargs):
template = webodt.ODFTemplate('test.odt')
queryset = Pupils.objects.get(id=kwargs['pk'])
serializer = StudentSerializer(queryset)
context = dict(serializer.data)
document = template.render(Context(context))
doc = converter().convert(document, format='doc')
res = HttpResponse(
FileWrapper(doc),
content_type='application/msword'
)
res['Content-Disposition'] = u'attachment; filename="%s_%s.zip"' % (context[u'surname'], context[u'name'])
return res
But it returns a msword document as json.
How do I make it start downloading as file instead?
Here's an example of returning a file download directly from DRF. The trick is to use a custom renderer so you can return a Response directly from the view:
from django.http import FileResponse
from rest_framework import viewsets, renderers
from rest_framework.decorators import action
class PassthroughRenderer(renderers.BaseRenderer):
"""
Return data as-is. View should supply a Response.
"""
media_type = ''
format = ''
def render(self, data, accepted_media_type=None, renderer_context=None):
return data
class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Example.objects.all()
#action(methods=['get'], detail=True, renderer_classes=(PassthroughRenderer,))
def download(self, *args, **kwargs):
instance = self.get_object()
# get an open file handle (I'm just using a file attached to the model for this example):
file_handle = instance.file.open()
# send file
response = FileResponse(file_handle, content_type='whatever')
response['Content-Length'] = instance.file.size
response['Content-Disposition'] = 'attachment; filename="%s"' % instance.file.name
return response
Note I'm using a custom endpoint download instead of the default endpoint retrieve, because that makes it easy to override the renderer just for this endpoint instead of for the whole viewset -- and it tends to make sense for list and detail to return regular JSON anyway. If you wanted to selectively return a file download you could add more logic to the custom renderer.
This may work for you:
file_path = file_url
FilePointer = open(file_path,"r")
response = HttpResponse(FilePointer,content_type='application/msword')
response['Content-Disposition'] = 'attachment; filename=NameOfFile'
return response.
For FrontEnd code refer this
I am using DRF and i found a view code to download file, which would be like
from rest_framework import generics
from django.http import HttpResponse
from wsgiref.util import FileWrapper
class FileDownloadListAPIView(generics.ListAPIView):
def get(self, request, id, format=None):
queryset = Example.objects.get(id=id)
file_handle = queryset.file.path
document = open(file_handle, 'rb')
response = HttpResponse(FileWrapper(document), content_type='application/msword')
response['Content-Disposition'] = 'attachment; filename="%s"' % queryset.file.name
return response
and url.py will be
path('download/<int:id>/',FileDownloadListAPIView.as_view())
I am using React.js in frontend and i get a response like
handleDownload(id, filename) {
fetch(`http://127.0.0.1:8000/example/download/${id}/`).then(
response => {
response.blob().then(blob => {
let url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
console.log(url);
a.href = url;
a.download = filename;
a.click();
});
});
}
and after i got successful in downloading a file which also opens correctly and i hope this gonna work. Thanks
For me, using Python 3.6, Django 3.0, and DRF 3.10, The problem came from using the wrong type of response. I needed to use a django.http.HttpResponse, as seen below:
from django.http import HttpResponse
...
with open('file.csv', 'r') as file:
response = HttpResponse(file, content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=file.csv'
return response
I solved my problem by saving file in media folder and sending of the link of it to front-end.
#permission_classes((permissions.IsAdminUser,))
class StudentDocxViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
def retrieve(self, request, *args, **kwargs):
template = webodt.ODFTemplate('test.odt')
queryset = Pupils.objects.get(id=kwargs['pk'])
serializer = StudentSerializer(queryset)
context = dict(serializer.data)
document = template.render(Context(context))
doc = converter().convert(document, format='doc')
p = u'docs/cards/%s/%s_%s.doc' % (datetime.now().date(), context[u'surname'], context[u'name'])
path = default_storage.save(p, doc)
return response.Response(u'/media/' + path)
And handled this like in my front-end (AngularJS SPA)
$http(req).success(function (url) {
console.log(url);
window.location = url;
})
In models.py
class Attachment(models.Model):
file = models.FileField(upload_to=attachment_directory_path, blank=True, null=True)
...
#property
def filename(self):
return self.file.name.split('/')[-1:][0]
in views.py
import mimetypes
from django.http import FileResponse
class AttachmentViewSet(ModelViewSet):
...
#action(methods=['GET'], detail=True)
def download(self, request, **kwargs):
att = self.get_object()
file_handle = att.file.open()
mimetype, _ = mimetypes.guess_type(att.file.path)
response = FileResponse(file_handle, content_type=mimetype)
response['Content-Length'] = att.file.size
response['Content-Disposition'] = "attachment; filename={}".format(att.filename)
return response
and in frontend, I used axios for download files. api is axios client.
export function fileDownload(url, filename){
return api.get(url, { responseType: 'blob' })
.then((response)=>{
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
})
}
hope that it helps
Using django-downloadview this can be done like so:
from rest_framework.decorators import action
from django_downloadview import ObjectDownloadView
class DocumentViewSet(viewsets.ReadOnlyModelViewSet):
#action(detail=True)
def download(self, request, pk):
return ObjectDownloadView.as_view(
model=, # your model here
)(request, pk=pk)
The viewset can then be registered via DRF routers.
I need to send a pdf file and some other parameters in response to a get API call using django rest framework.
How can I do it?
I tried this but it gives an error <django.http.HttpResponse object at 0x7f7d32ffafd0> is not JSON serializable.
#detail_route(methods=['get'])
def fetch_report(self, request, *args, **kwargs):
short_report = open("somePdfFile", 'rb')
response = HttpResponse(FileWrapper(short_report), content_type='application/pdf')
return Response({'detail': 'this works',
'report': response})
The problem here is that you are trying to return a mix of JSON and PDF, which either isn't what you are looking for or is going to return a giant base64-encoded response. PDF is a binary format and JSON is a text format, and you can't really mix them well.
Within a DRF view you can directly return a Django response, which you already appear to be generating (the HttpResponse), and DRF will pass it through and skip the renderers. This is useful in cases like this, as it allows you to return a binary response such as an image or PDF without worrying about DRF's rendering layer causing problems.
#detail_route(methods=['get'])
def fetch_report(self, request, *args, **kwargs):
short_report = open("somePdfFile", 'rb')
response = HttpResponse(FileWrapper(short_report), content_type='application/pdf')
return response
The alternative is to encode the PDF as text, using something like base64 encoding. This will dramatically increase your response sizes, but it will allow you to use DRF's rendering layer without problems.
#detail_route(methods=['get'])
def fetch_report(self, request, *args, **kwargs):
import base64
short_report = open("somePdfFile", 'rb')
report_encoded = base64.b64encode(short_report.read())
return Response({'detail': 'this works',
'report': report_encoded})
But the route I would recommend here is to generate the PDF and store it in either your media storage, or an alternative private location, and provide a direct link to it in your response. This way you don't need to worry about the encoding issues, don't need to directly return the PDF, and don't need to worry about directly serving the PDF.
If you really need to serve binary files directly from your backend (e.g. when they're being generated dynamically) you can use a custom renderer:
from rest_framework.renderers import BaseRenderer
class BinaryFileRenderer(BaseRenderer):
media_type = 'application/octet-stream'
format = None
charset = None
render_style = 'binary'
def render(self, data, media_type=None, renderer_context=None):
return data
Then use it in your action in a viewset:
from rest_framework.decorators import action
#action(detail=True, methods=['get'], renderer_classes=(BinaryFileRenderer,))
def download(self, request, *args, **kwargs):
with open('/path/to/file.pdf', 'rb') as report:
return Response(
report.read(),
headers={'Content-Disposition': 'attachment; filename="file.pdf"'},
content_type='application/pdf')
You can use DRF-PDF project with PDFFileResponse:
from rest_framework import status
from rest_framework.views import APIView
from drf_pdf.response import PDFFileResponse
from drf_pdf.renderer import PDFRenderer
class PDFHandler(APIView):
renderer_classes = (PDFRenderer, )
def get(self, request):
return PDFFileResponse(
file_path='/path/to/file.pdf',
status=status.HTTP_200_OK
)
But, maybe you cannot respond in both formats (json and stream).
You can send pdf file as a response to any request without installing any packages.
You can do the following.
def get(request, *args, **kwargs):
# some code
# some code
with fs.open("path/to/file/Report.pdf") as pdf:
response = HttpResponse(pdf, content_type='application/pdf')
filename = "Report.pdf"
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
return response
Now, open your postman and hit a get/post request.
Important: Before clicking send button in postman, select the option send and download.
With DRF you can write as following:
pdf_file_in_bytes = AnyModel.file.read() # file is a FileField
pdf_file_name = "PDF's Download Name"
response = Response(
headers={'Content-Disposition': f'attachment; filename={pdf_file_name}'},
content_type='application/pdf'
)
response.content = pdf_file_in_bytes
return response