Submit Blog  RSS Feeds
Showing posts with label pisa. Show all posts
Showing posts with label pisa. Show all posts

Thursday, April 26, 2012

Convert HTML/CSS to PDF (preserving layout) using python pisa

A few days ago I published a post regarding PDF file  generation. I mentioned that I may be able to say a few words about positioning elements using pisa at-keywords. The first thing you should know, is that although CSS is suppose to be supported, most positioning properties won't work... until you apply them inside an appropriate block. Take a look at this head section:

  1 <head>
  2     <style type="text/css">
  3        @page {
  4             size: a4;
  5             @frame {
  6                 -pdf-frame-content: "footer";
  7                 top: 25.8cm;
  8                 margin-right: 2.8cm;
  9                 margin-left: 2.8cm;
 10             }   
 11             @frame {
 12                -pdf-frame-content: 'date_box';
 13                top: 2.4cm;
 14                left: 2.8cm;
 15             }  
 16             @frame {
 17                -pdf-frame-content: "content";
 18                top: 16.7cm;
 19                margin-right: 2.8cm;
 20                margin-left: 2.8cm;
 21             }  
 22        }    
 23     </style>
 24     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 25 </head>
The @page block contains some general document/page properties, such as page format (line 4) - in this example we want our document to be 210x297 (vertical and horizontal dimensions respectively described in mm).  @frame keywords define layout positioning information. For example, lines 5-10 could be represented in regular CSS like this:

 1 div#footer {
 2     top: 25.8cm;
 3     margin-right: 2.8cm;
 4     margin-left: 2.8cm;
 5 }      


So it is now easy to imagine what this layout looks like, it contains a date-box, regular content (in the middle of the document) and a footer.

You can place your data in the corresponding box the following way:

<div id="date_box" style="text-align:right; font-size: 20px;">
2012/04/26
</div>

<div id="content" style="text-align:left; font-size: 14px;">
This is a pisa layout test
</div>

<div id="footer" style="text-align:center; font-size: 10px;">
This should be a footer
</div>

In order to generate the PDF from the command line, simply run:

~ pisa my_html_file.html

This will create my_html_file.pdf in the same director.
You may also achieve this the pythonic way - see my previous post about PDF generation.

For more information about layout definition and supported CSS styles check the official pisa documentation.

~KR

P.S. I don't usually do front-end stuff, but I really like this tool.




Monday, April 23, 2012

Django view serving dynamically generated PDF files.

Serving static files is cool. However, static files have a drawback - mainly they tend to be static. I'm not saying that you should avoid serving static content or anything similar - static files have variety of important applications , which are not the topic of this post.

So what can you do when you want to serve a dynamically generated PDF file to the user? First of all you have to provide a package capable of generating such files, so unless you want to spend quite a few days implementing your own tool, you should try using ReportLab. ReportLab is quite powerful, but it takes a lot of effort to create a good layout. If you also want your PDF file to look sexy - you should choose pisa (it requires ReportLab as a dependency). Pisa is a HTML/CSS to PDF converter - which is just what we need (be warned - not all CSS styles are supported, but that's another history).

Let's have a look at this code:


  1 import ho.pisa
  2 import cStringIO
  3
  4 from django.http import HttpResponse
  5 from django.template.loader import get_template
  6 from django.template import Context
  7
  8    
  9 def generate_pdf(template_file, context={}):
 10     #to avoid using a temporary file StringIO has to be used
 11     pdf = cStringIO.StringIO()
 12     template = get_template(template_file)
 13    
 14     html_response  = template.render(Context(context))
 15    
 16     pdf_status = ho.pisa.pisaDocument(cStringIO.StringIO(html_response), pdf)
 17    
 18     if pdf_status.err:
 19         #catch it in the invoking view
 20         raise Exception('Oops! Something went wrong!')
 21     return HttpResponse(pdf.getvalue(), mimetype='application/pdf')
 

 This is a generic function that can render any template with an apropriate context and return a HttpResponse, this function may be used the following way:

 24 def generate_report_view(request):
 25     '''
 26         (...) some code
 27     '''
 28     return generate_pdf('reports/sample_report.html',\
 29         {'owner' : request.user, 'some_param' : 'Hello PDF Generator'})


There are two tricks here, that enable downloading the generated PDF directly from the view. Firstly, the cStringIO (faster version of StringIO) is used instead of a file, so that we do not have to make any HDD IO operations. Secondly we set the response mimetype to application/pdf, which informs a browser that this is not a regular site.

~KR

free counters