Search code examples
pythondjangopdfdownloadbytesio

Django - can't download file (showed as text instead)


I have a pdf in my static files, I need to add it some text and let the user download it, but the download part gives me some problems, here is part of my code:

views.py

import io
from werkzeug.wsgi import FileWrapper
from pathlib import Path
from PyPDF2 import PdfFileWriter, PdfFileReader
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from django.http import HttpResponse, FileResponse

def downloadpdf(request):
    pdf_path = (Path.home()/"bot"/"mysite"/"static"/"images"/"tesseragenerica.pdf")

    # 1
    pdf_reader = PdfFileReader(str(pdf_path))
    packet = io.BytesIO()

    # create a new PDF with Reportlab
    can = canvas.Canvas(packet, pagesize=letter)
    testo = "text to add" #in future "text to add" will be the username
    can.drawString(5, 5, testo)
    can.save()

    #move to the beginning of the StringIO buffer
    packet.seek(0)

    new_pdf = PdfFileReader(packet)

    # read your existing PDF
    existing_pdf = PdfFileReader(open("/home/myusername/bot/mysite/static/images/tesseragenerica.pdf", "rb"))

    output = PdfFileWriter()

    # add the "watermark" (which is the new pdf) on the existing page
    page = existing_pdf.getPage(0)
    page.mergePage(new_pdf.getPage(0))
    output.addPage(page)

    # finally, write "output" to a real file
    outputStream = open("destination3.pdf", "wb")
    output.write(outputStream)
    outputStream.close()
    return FileResponse(FileWrapper(packet), as_attachment=True, filename='hello.pdf')

The code for adding text to pdf is correct because in local it works so i think the problem is what i'm putting as the first argument of FileResponse, if I put FileWrapper(packet) for example instead of making me download the file i see this on my browser:

%PDF-1.3 %���� ReportLab Generated PDF document http://www.reportlab.com 1 0 obj << /F1 2 0 R >> endobj 2 0 obj << /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> endobj 3 0 obj << /Contents 7 0 R /MediaBox [ 0 0 612 792 ] /Parent 6 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj 4 0 obj << /PageMode /UseNone /Pages 6 0 R /Type /Catalog >> endobj 5 0 obj << /Author (anonymous) /CreationDate (D:20210718122806+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20210718122806+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) /Subject (unspecified) /Title (untitled) /Trapped /False >> endobj 6 0 obj << /Count 1 /Kids [ 3 0 R ] /Type /Pages >> endobj 7 0 obj << /Filter [ /ASCII85Decode /FlateDecode ] /Length 97 >> stream GapQh0E=F,0U\H3T\pNYT^QKk?tc>IP,;W#U1^23ihPEM_?CW4KISh__N8"+NKVeK&quKrKuB2i9neZ[Kb,ht63StAffBaV~>endstream endobj xref 0 8 0000000000 65535 f 0000000073 00000 n 0000000104 00000 n 0000000211 00000 n 0000000404 00000 n 0000000472 00000 n 0000000768 00000 n 0000000827 00000 n trailer << /ID [<75dce57879667ca53ee6edf7352f4f98><75dce57879667ca53ee6edf7352f4f98>] % ReportLab generated PDF document -- digest (http://www.reportlab.com) /Info 5 0 R /Root 4 0 R /Size 8 >> startxref 1013 %%EOF

Which i think it's a pdf file but can't understand why the browser show me it instead of downloading it. I think (and hope) that i'm close to the solution but i don't know what i'm doing wrong. What can I do to make the pdf downloadable? Thanks


Solution

  • The browser shows the file response if it is a recognizable format to the browser. As browsers recognize it, the browsers shows a preview. If you want to force the user to download it, there is two methods.

    First, if you are linking to the download url, add a download attribute to the link from html. <a href="download/example.pdf" download>download me pls</a>

    If you want to do it from python, or have to do it in python, then you can return an HttpResponse object, but with a content_type that forces a download. Example:

    def download(request, path):
        file_path = os.path.join(settings.MEDIA_ROOT, path)
        if os.path.exists(file_path):
            with open(file_path, 'rb') as fh:
                response = HttpResponse(fh.read(), content_type="application/force-download")
                response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
                return response
        raise Http404
    

    Resource: Having Django serve downloadable files