How to cleanly pass FPDF output object to os Download/Save dialogue box

1,543 views
Skip to first unread message

Peter

unread,
Oct 16, 2016, 12:24:10 AM10/16/16
to web2py-users


I have this code that doesn't work too well...


import
os
import sys
pdf = receipt_PDF()
pdf.add_page()
rcpt_filename = "RCPT_%s_%s.pdf" % (session.rcpt_number, session.rcpt_recipient)
rcpt_filename = rcpt_filename.replace( ' ', '_' )
if sys.platform.startswith( "linux" ) :
os.system( "xdg-open ./%s" %(rcpt_filename) )
else :
os.system( "./%s" %(rcpt_filename) )
#print "<pdf_invoice> inv_filename=%s" %(inv_filename)
response.headers['Content-Type'] = 'application/pdf'
#response.headers['Content-disposition'] = 'attachment; filename=%s' % rcpt_filename
pdf.output( rcpt_filename, dest='F' )


as it stands the code...
  • writes the PDF to disk with the required filename (path is simply the web2py directory) - this file can be readily opened later in Adobe Reader 9
  • it also opens a blank document in a browser reader (with information message - this PDF doc may not be displayed correctly) (user has lost Nav bar options)
  • finally it opens the requested document in Adobe Reader 9 (appears perfect i.e. no error messages)
  • the user has to use Adobe's 'page save as'  to save the file in their desired location
  • the user has to close Adobe and use 'Go Back' in the browser to clear the browser reader and return to viewing the original request page

what I would like it to do is...

  • leave the browser on the original page used to call the function
  • open the o/s' download/open dialogue box prompting the user to save the file (wherever they desire)
  • once the file is downloaded the user simply returns to the browser where they left off (without using 'Go Back')


FPDF is a brilliant contribution to the web2py armoury and again I am chuffed to have gotten this far with it. The blockage for me is that the documentation is somewhat disconnected in that none of the examples are in the context of web2py environment e.g.whether and how to use  pdf.output vs response.stream


I have been reading as much as I can find but nothing seems to match exactly what I want to do and despite trying dozens of variations nothing gets me closer without breaking something else.

It seems I need to drop the xdg portion, that the header's content type = attachment will force the download dialogue, and,  I think, I need to be using file-like object (but is the FPDF object not this?) with Stringio in the mix?


Any assistance with the specifics needed to accomplish what I am looking for above would be appreciated! 



Peter







Peter

unread,
Oct 16, 2016, 1:42:29 PM10/16/16
to web...@googlegroups.com


So this is better but I still need some direction...

(thanks to Paul Rykiel's post re labels and responders including one Mariano Reingart)...and hope it helps someone else down the road.


pdf = receipt_PDF()
pdf.add_page()

rcpt_filename = "RCPT_%s_%s.pdf" % (session.rcpt_number, session.rcpt_recipient)
rcpt_filename = rcpt_filename.replace( ' ', '_' )

rcpt_filepath = request.folder + '/static/temp/%s'%(rcpt_filename)

if sys.platform.startswith( "linux" ) :
os.system( "xdg-open %s" %(rcpt_filepath) )
else :
os.system( "%s" %(rcpt_filepath) )

pdf.output(rcpt_filepath,dest='S')
response.headers['Content-Type'] = 'application/pdf'
return response.stream(rcpt_filepath, chunk_size=4096, request=request, attachment=True, filename=rcpt_filename)



so this code...
  • no longer opens the browser pdf reader,  user page does not change!
  • opens the open/save dialogue box with appropriate filename and allows user to save wherever desired!
but it still has one issue...
  • as well as opening the open/save dialogue box it automatically opens Adobe Reader as well and displays the file (a little overkill!)

(It also creates temp files on the system that may/may not need to be cleaned up - but that's for another day.)


Can I stop it automatically opening the file in Adobe and if so how?


Peter 




[edit]
There is an added bonus that kicked in somewhere the file size is approximately 30% of what it was before...





 


Message has been deleted

Paolo Valleri

unread,
Oct 17, 2016, 1:45:04 PM10/17/16
to web2py-users
With this:    
...
s_io = pdf.output(dest='S')
response.headers['Content-Type']='application/pdf'
response.headers['Content-Disposition'] = 'attachment; filename="%s"' % file_name
raise HTTP(200, s_io, **response.headers)
You get the same you have now with response.stream but it's cleaner.
Without setting the 'Content-disposition' the pdf will be opened in the browser.

Paolo

Peter

unread,
Oct 17, 2016, 3:24:01 PM10/17/16
to web...@googlegroups.com

Many thanks for response Paolo!

I have tried your suggestion...


# pdf.output(rcpt_filepath,dest='S')
# response.headers['Content-Type'] = 'application/pdf'
# return response.stream(rcpt_filepath, chunk_size=4096, request=request, attachment=True, filename=rcpt_filename)

s_io
= pdf.output(dest='S')
response.headers['Content-Type']='application/pdf'
response.headers['Content-Disposition'] = 'attachment; filename="%s"' % rcpt_filename
raise HTTP(200, s_io, **response.headers



but the behaviour appears identical to the original code (commented out above)...
  • browser page doesn't change (Good!)
  • opens the File Open/Save dialogue for downloading the PDF (Good!)
  • also automatically opens the PDF in Adobe (Not Wanted!)



Without setting the 'Content-disposition' the pdf will be opened in the browser.


Apologies if it wasn't clear but this particular issue had already been fixed.

If I understand it correctly I believe that,

response.stream (....,attachment=True, filename=rcpt_filename)

i.e. the assignment of the headers 'attachment'  and 'filename',  is functionally equivalent to 

response.headers['Content-Disposition'] = 'attachment; filename="%s"' % rcpt_filename


To summarise the issue,
It is sufficient that the user is prompted to download and save the PDF - and this is working!.
The only change that is needed now is how to prevent Adobe automatically opening the PDF as well?



Regards
Peter

[edit]
Just thinking maybe it has nothing to do with web2py and the code above, it may a setting on my machine, off to investigate...


Niphlod

unread,
Oct 17, 2016, 3:27:58 PM10/17/16
to web2py-users
halt. downloading is something the browser can decide. opening the saved file is something your operating system does.
The browser can only send the response which gives the user a choice between saving and opening directly the file.
If the user chooses "open" instead of "save" ... nothing can prevent that.

Peter

unread,
Oct 17, 2016, 6:05:50 PM10/17/16
to web...@googlegroups.com

Hi Niphlod,   (& Paolo read this!)


halt. downloading is something the browser can decide. opening the saved file is something your operating system does.
The browser can only send the response which gives the user a choice between saving and opening directly the file.
If the user chooses "open" instead of "save" ... nothing can prevent that.

At this point the user has only clicked on the request (above code) in the browser and has not responded to the os open/save dialogue which subsequently opens.
i.e. the file has not yet been saved by the user (though a version has been written 'earlier' to static/temp on the local machine for use 'later' when the user clicks/calls response.stream()
this is because I am developing on localhost. In production obviously the static/temp directory will be on a remote server.

The code above appears to kick off two separate events, one being the 'open/save dialogue' and the other being  'open in adobe' event.

Having said that... I went back and looked again and I think I Paolo had it right ...



import
os
import sys


pdf = receipt_PDF()
pdf.add_page()

rcpt_filename = "RCPT_%s_%s.pdf" % (session.rcpt_number, session.rcpt_recipient)
rcpt_filename = rcpt_filename.replace( ' ', '_' )
rcpt_filename = rcpt_filename.replace( '&', 'and' )     # TODO read somewhere there is a 'clean' option in web2py for filepath?

rcpt_filepath = request.folder + 'static/temp/%s'%(rcpt_filename) # Note I have removed leading '/' - long story!


s_io = pdf.output(dest='S')
response.headers['Content-Type']='application/pdf'
response.headers['Content-Disposition'] = 'attachment; filename="%s"' % rcpt_filename
raise HTTP(200, s_io, **response.headers)


This is doing exactly what I want - the difference being I have removed the xdg open request (no static/temp file creation needed if dealing with a stringio object ).

I need to play with it a bit more because it is now opening a 'download dialogue' which is perfect! (as opposed to 'open/save' dialogue)
This is probably because I changed the system download options while investigating, so I need to go back over it to be sure.

It does raise another question - did I not read that downloading a stringio is not recommended or was it just response.stream a stringio objectis not recommended - is there a diff?

When I have finished checking it out I will post to confirm this is the solution and thanks again guys for your assistance! 

Peter


[edit]
Again for the record this is on a Laptop running Ubuntu 12.04 LTS (time to upgrade) with Linux  3.2.0-89-generic
No idea what will happen on a Windows machine but assuming/hoping it will work the same - regardless I will find out shortly.



 

Peter

unread,
Oct 24, 2016, 9:55:21 PM10/24/16
to web...@googlegroups.com

Just to close this off.

I think I am in a bit of a catch 22..

Because the behaviour (with Paolo's code) is exactly what I want   i.e. stream goes straight to download option, I don't get the open/save dialogue so can't change the setting and after reading this (possibly related Firefox bug report)   https://bugzilla.mozilla.org/show_bug.cgi?id=1109420  I'm more than happy to leave well enough alone and not go down the rabbit hole.

Thanks again Niphlod & Paolo for your inputs!

Peter
Reply all
Reply to author
Forward
0 new messages