Dynamically-generated PDF to download

290 views
Skip to first unread message

Adam Morris

unread,
Jan 25, 2015, 1:37:18 AM1/25/15
to pylons-...@googlegroups.com
I have a view that serves up a page that whose template/css contain both define screen and print media. Which means the view's renderer provides a page that serves up html that is formatted for the browser (screen media) and if the user prints it formatted for the page (the print media). The CSS that does this is:

  <link rel="stylesheet" href="${static_url}css/reports.css" media="screen">
  <link rel="stylesheet" href="${static_url}css/reports_pdf.css" media="print">

I want to add a view that returns a FileResponse, as rendered by the print media, so that particular location results in a download of that PDF, but I don't want to repeat code.

I don't want to serve a static asset, I want the PDF to be dynamically created.

Any ideas on how to tackle this best?

Adam

Wichert Akkerman

unread,
Jan 25, 2015, 4:59:38 AM1/25/15
to pylons-...@googlegroups.com

The basic approach is: generate your data (in this case the PDF file), create a response object using your data, set the right headers for it and return it from your view.

Wichert.

Adam Morris

unread,
Jan 25, 2015, 8:40:53 AM1/25/15
to pylons-...@googlegroups.com
Okay, I get that bit now, and coded it up, but when I go to render it, I use the pyramid.renderers.render object but obviously it ends up downloading a corrupted file because it's not even in PDF format. 

So does reportlab have something to take that html/css and paint it to a Canvas or something?

The overall idea here is that I was hoping to be able to define how I want the PDF output to look at by using CSS.


    if pdf:
       
# I need to build the Response object myself
       
from pyramid.renderers import render
       
from pyramid.response import Response

        result
= render('frontend:templates/student_pyp_report.pt',
                    dict
(
                        title
=title,
                        report
=report,
                        student
=student,
                        pdf
=True
                       
),
                    request
=request)


        response
= Response(
            result
,
            content_disposition
="attachment; filename={}.pdf".format("test"),
            content_type
= "application/pdf",
            charset
="utf-8"
           
)

       
return response

    else: 
        return dict(
            title=title,
            report= report,
            student=student,
            pdf=False
            )

Wichert Akkerman

unread,
Jan 25, 2015, 12:17:19 PM1/25/15
to pylons-...@googlegroups.com
On 25 Jan 2015, at 14:40, Adam Morris <adam....@igbis.edu.my> wrote:

Okay, I get that bit now, and coded it up, but when I go to render it, I use the pyramid.renderers.render object but obviously it ends up downloading a corrupted file because it's not even in PDF format. 

You are still rendering a .pt file, so I’m guessing you are generating HTML, not PDF. If you want to generate PDF you will need to use a PDF library.

So does reportlab have something to take that html/css and paint it to a Canvas or something?

I use z3c.rml, which uses RML as input to generate PDF. If you want to use HTML & CSS you’ll need to find a library that can convert HTML to PDF.

Wichert.

Adam Morris

unread,
Jan 25, 2015, 7:20:55 PM1/25/15
to pylons-...@googlegroups.com
Okay, I've identified pdfkit but with the following code I get a blank pdf downloaded, I must be doing something wrong...:

        from pyramid.renderers import render
       
from pyramid.response import Response

       
import pdfkit
       
import StringIO



        result
= render('frontend:templates/student_pyp_report.pt',
                    dict
(
                        title
=title,
                        report
=report,
                        student
=student,
                        pdf
=True
                       
),
                    request
=request)



        pdffileio
= StringIO.StringIO()
        pdffileio
.write(result)


        pdf_as_string
= pdfkit.from_file(pdffileio, False)   # False means return it as a string


        response
= Response(
            pdf_as_string
,

            content_disposition
="attachment; filename={}.pdf".format("test"),
            content_type
= "application/pdf",
            charset
="utf-8"
           
)



       
#pdffileio.close()


       
return response


and the pdf_as_string is just this:

Loading page (1/2)\n[>                                                           ] 0%\r[======>                                                     ] 10%\r[============================================================] 100%\rPrinting pages (2/2)                                               \n%PDF-1.4\n1 0 obj\n<<\n/Title (\xfe\xff)\n/Creator (\xfe\xff)\n/Producer (\xfe\xff\x00Q\x00t\x00 \x004\x00.\x008\x00.\x006)\n/CreationDate (D:20150126001805)\n>>\nendobj\n2 0 obj\n<<\n/Type /Catalog\n/Pages 3 0 R\n>>\nendobj\n4 0 obj\n<<\n/Type /ExtGState\n/SA true\n/SM 0.02\n/ca 1.0\n/CA 1.0\n/AIS false\n/SMask /None>>\nendobj\n5 0 obj\n[/Pattern /DeviceRGB]\nendobj\n6 0 obj\n<<\n/Type /Page\n/Parent 3 0 R\n/Contents 7 0 R\n/Resources 9 0 R\n/Annots 10 0 R\n/MediaBox [0 0 595 842]\n>>\nendobj\n9 0 obj\n<<\n/ColorSpace <<\n/PCSp 5 0 R\n/CSp /DeviceRGB\n/CSpg /DeviceGray\n>>\n/ExtGState <<\n/GSa 4 0 R\n>>\n/Pattern <<\n>>\n/Font <<\n>>\n/XObject <<\n>>\n>>\nendobj\n10 0 obj\n[ ]\nendobj\n7 0 obj\n<<\n/Length 8 0 R\n/Filter /FlateDecode\n>>\nstream\nx\x9c\xadP=\x0b\x021\x0c\xdd\xf3+2\x0b\xf6\xd2\x16\xda\xde|\x83\xe0 \x1c\x1d\x1c\xc4A\xce/\xc4;\xac7\xf8\xf7M?\x94\xc3I\xd0\x04\xfa\xf2B>^S-\xfc\x0eO#V\x8d\xbfaW\xb0\xf1@\x82\x0ce\xc3\xe8\xf3iB9\xa1U\x0e\x9d\xd4\xc2\xd8\x9a\r\xbb\x1e\x02\x06h\xa1\xe57b\x00\xa9Rs\x01.x\xad\xc9C\xc7n\x80*\x0b\x80\x9c\xf1\xcd\x8a\xa3\x07*\\2\xbb\xe0f\xcb\xb0/3cA\x0f\xae6Z\xd4i#\xe1uJ\xa5&k\x84$\xe58O\x9f4\x16\x9fa=\xc3\x81\x85\xd5\xc2\x90\xb5\xd2\x92Nk?\xe8/B\xc3\xbbU&\x9f\xb6~\xb17e\xb5f\xf1F\xe2\xfd\x00\xc7\xf8\xf7r\xd1\xbf]\x13[x\x02\xe7@lMendstream\nendobj\n8 0 obj\n194\nendobj\n3 0 obj\n<<\n/Type /Pages\n/Kids \n[\n6 0 R\n]\n/Count 1\n/ProcSet [/PDF /Text /ImageB /ImageC]\n>>\nendobj\nxref\n0 11\n0000000000 65535 f \n0000000009 00000 n \n0000000120 00000 n \n0000000881 00000 n \n0000000169 00000 n \n0000000264 00000 n \n0000000301 00000 n \n0000000595 00000 n \n0000000862 00000 n \n0000000420 00000 n \n0000000575 00000 n \ntrailer\n<<\n/Size 11\n/Info 1 0 R\n/Root 2 0 R\n>>\nstartxref\n979\n%%EOF\n[>                                                           ] \rDone                                                           \n

Adam Morris

unread,
Jan 25, 2015, 11:41:07 PM1/25/15
to pylons-...@googlegroups.com
Got it working now. Problem was that it was that pdfkit was outputting everything... even verbose stuff, would have thought that would have been off. Thanks for your help.

Jonathan Vanasco

unread,
Jan 26, 2015, 11:16:07 AM1/26/15
to pylons-...@googlegroups.com
Just want to give a quick warning that a lot of the pdf libraries use crazy amounts of memory.   I have no experience with pdfkit, and it may behave differently.

If it causes issues with memory/performance, I usually handle this stuff in Pyramid one of two ways:

a- I segment out the PDF generating routes to run on their own server/instance.  For example: I run 2 pyramid apps via uwsgi.  they're both the same codebase, but one is only given the PDF traffic and the other is given everything but the pdf traffic.

b- I push the PDF work into Celery and then use browser polling to refresh until the PDF is ready.  (there are better ways to handle offloading to celery, that's just the dumbest/easiest).


Tom Lazar

unread,
Jan 30, 2015, 6:14:09 AM1/30/15
to pylons-...@googlegroups.com
Fyi  I've had good experiences using phantomjs

Sent from a phone, please excuse the brevity.
--
You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pylons-discus...@googlegroups.com.
To post to this group, send email to pylons-...@googlegroups.com.
Visit this group at http://groups.google.com/group/pylons-discuss.
For more options, visit https://groups.google.com/d/optout.

Alex

unread,
Oct 19, 2018, 6:20:51 AM10/19/18
to pylons-discuss
I know this thread is quite old but it's probably still relevant. We created a report tool since generating pdf reports in a web application is a common problem and none of the existing solutions were optimal for us. We recently released the first stable version 1.0 of our tool ReportBro:

https://www.reportbro.com

ReportBro contains a designer (javascript plugin) to create report templates which can easily be integrated in a web application. Server-side we developed a python package - which you can either download or install via pip - to create the pdf (or xlsx) file with a given report template.

Alex
Reply all
Reply to author
Forward
0 new messages