21

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}) 
1
  • You cannot do this in one step. Either the answer is PDF, or the answer is JSON. You could encode the content of the pdf into a JSON string, but it will be (doable but) hard to get it to save on the client. Usually this kind of stuff is done in a 2-step process: first get the JSON answer, then, if it succeeded, request the actual file. Commented May 25, 2015 at 14:22

5 Answers 5

23

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.

Sign up to request clarification or add additional context in comments.

Comments

16

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') 

Comments

2

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

Comments

2

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 

1 Comment

I find this the best approach since it uses the rest_framework.response.Response object and need no dependencies.
1

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.

1 Comment

This works perfectly if you want to download a document. Tnx man.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.