Django 1.2 E-commerce: Generating PDF Reports from Python using ReportLab

Exclusive offer: get 50% off this eBook here
Django 1.2 E-commerce

Django 1.2 E-commerce — Save 50%

Build powerful e-commerce applications using Django, a leading Python web framework

$23.99    $12.00
by Jesse Legg | May 2010 | e-Commerce Open Source Web Development

The Python community offers dozens of libraries designed to generate graphics, reports, PDF files, images, and charts. It can be somewhat overwhelming choosing which tool is appropriate for the job. In this article by Jesse Legg, author of Django 1.2 e-commerce, we will experiment with the ReportLab toolkit, which is a Python module that allows us to create PDF files. ReportLab can be integrated with Django to generate dynamic PDFs on-the-fly for the data stored in our Django models.

(Read more interesting articles on Django 1.2 e-commerce here.)

ReportLab is an open source project available at http://www.reportlab.org. It is a very mature tool and includes binaries for several platforms as well as source code. It also contains extension code written in C, so it's relatively fast. It is possible for ReportLab to insert PNG and GIF image files into PDF output, but in order to do so we must have the Python Imaging Library (PIL) installed. We will not require this functionality in this article, but if you need it for a future project, see the PIL documentation for setup instructions.

The starting point in the ReportLab API is drawing to canvas objects. Canvas objects are exactly what they sound like: blank slates that are accessible using various drawing commands that paint graphics, images, and words. This is sometimes referred to as a low-level drawing interface because generating output is often tedious. If we were creating anything beyond basic reporting output, we would likely want to build our own framework or set of routines on top of these low-level drawing functions.

Drawing to a canvas object is a lot like working with the old LOGO programming language. It has a cursor that we can move around and draw points from one position to another. Mostly, these drawing functions work with two-dimensional (x, y) coordinates to specify starting and ending positions.

This two-dimensional coordinate system in ReportLab is different from the typical system used in many graphics applications. It is the standard Cartesian plane, whose origin (x and y coordinates both equal to 0) begins in the lower-left hand corner instead of the typical upper-right hand corner. This coordinate system is used in most mathematics courses, but computer graphics tools, including HTML and CSS layouts, typically use a different coordinate system, where the origin is in the upper-left.

ReportLab's low-level interface also includes functions to render text to a canvas. This includes support for different fonts and colors. The text routines we will see, however, may surprise you with their relative crudeness. For example, word-wrapping and other typesetting operations are not automatically implemented. ReportLab includes a more advanced set of routines called PLATYPUS, which can handle page layout and typography. Most low-level drawing tools do not include this functionality by default (hence the name "low-level").

This low-level drawing interface is called pdfgen and is located in the reportlab.pdfgen module. The ReportLab User's Guide includes extensive information about its use and a separate API reference is also available.

The ReportLab canvas object is designed to work directly on files. We can create a new canvas from an existing open file object or by simply passing in a file name. The canvas constructor takes as its first argument the filename or an open file object. For example:

from reportlab.pdfgen import canvas

c = canvas.Canvas("myreport.pdf")

Once we obtained a canvas object, we can access the drawing routines as methods on the instance. To draw some text, we can call the drawString method:

c.drawString(250, 250, "Ecommerce in Django")

This command moves the cursor to coordinates (250, 250) and draws the string "Ecommerce in Django". In addition to drawing strings, the canvas object includes methods to create rectangles, lines, circles, and other shapes.

Because PDF was originally designed for printed output, consideration needs to be made for page size. Page size refers to the size of the PDF document if it were to be output to paper. By default, ReportLab uses the A4 standard, but it supports most popular page sizes, including letter, the typical size used in the US. Various page sizes are defined in reportlab.lib.pagesizes. To change this setting for our canvas object, we pass in the pagesize keyword argument to the canvas constructor.

from reportlab.lib.pagesizes import letter

c = canvas.Canvas('myreport.pdf', pagesize=letter)

Because the units passed to our drawing functions, like rect, will vary according to what page size we're using, we can use ReportLab's units module to precisely control the output of our drawing methods. Units are stored in reportlab.lib.units. We can use the inch unit to draw shapes of a specific size:

from reportlab.lib.units import inch

c.rect(1*inch, 1*inch, 0.5*inch, 1*inch)

The above code fragment draws a rectangle, starting one inch from the bottom and one inch from the left of the page, with sides that are length 0.5 inches and one inch, as shown in the following screenshot:

Not particularly impressive is it? As you can see, using the low-level library routines require a lot of work to generate very little results. Using these routines directly is tedious. They are certainly useful and required for some tasks. They can also act as building blocks for your own, more sophisticated routines.

Building our own library of routines would still be a lot of work. Fortunately ReportLab includes a built-in high-level interface for creating sophisticated report documents quickly. These routines are called PLATYPUS; we mentioned them earlier when talking about typesetting text, but they can do much more.

PLATYPUS is an acronym for "Page Layout and Typography Using Scripts". The PLATYPUS code is located in the reportlab.platypus module. It allows us to create some very sophisticated documents suitable for reporting systems in an e-commerce application.

Using PLATYPUS means we don't have to worry about things such as page margins, font sizes, and word-wrapping. The bulk of this heavy lifting is taken care of by the high-level routines. We can, if we wish, access the low-level canvas routines. Instead we can build a document from a template object, defining and adding the elements (such as paragraphs, tables, spacers, and images) to the document container.

The following example generates a PDF report listing all the products in our Product inventory:

from reportlab.platypus.doctemplate import SimpleDocTemplate
from reportlab.platypus import Paragraph, Spacer
from reportlab.lib import sytles

doc = SimpleDocTemplate("products.pdf")
Catalog = []
header = Paragraph("Product Inventory",
styles['Heading1']) Catalog.append(header)
style = styles['Normal']
for product in Product.objects.all():
for product in Product.objects.all():
p = Paragraph("%s" % product.name, style)
Catalog.append(p)
s = Spacer(1, 0.25*inch)
Catalog.append(s)
doc.build(Catalog)

The previous code generates a PDF file called products.pdf that contains a header and a list of our product inventory. The output is displayed in the accompanying screenshot.

Django 1.2 E-commerce Build powerful e-commerce applications using Django, a leading Python web framework
Published: May 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

(Read more interesting articles on Django 1.2 e-commerce here.)

The document template used above, SimpleDocTemplate, is a class derived from ReportLab's BaseDocTemplate class. It provides for management of the page, the PDF metadata such as document title and author, and can even be used for PDF encryption. By inheriting from BaseDocTemplate, we could create our own document templates that correspond to specific reports or types of report we might generate on a regular basis (a product deals flyer, for example).

In addition to the paragraph and spacer elements we saw in the previous example, documents also support very sophisticated table definitions. We can render the same product inventory in a table format using ReportLab's Table class. Tables can be styled in myriad ways, including setting background and line colors around specific cells, rows or columns, drawing boxes around subsets of cells, and adding images to specific cells. A very simple demonstration is as follows:

doc = SimpleDocTemplate("products.pdf")
Catalog = []
header = Paragraph("Product Inventory", styles['Heading1'])
Catalog.append(header)
style = styles['Normal']
headings = ('Product Name', 'Product Description')
allproducts = [(p.name, p.description) for p
in Product.objects.all()]
t = Table([headings] + allproducts)
t.setStyle(TableStyle(
[('GRID', (0,0), (1,-1), 2, colors.black),
('LINEBELOW', (0,0), (-1,0), 2, colors.red),
('BACKGROUND', (0, 0), (-1, 0), colors.pink)]))
Catalog.append(t) doc.build(Catalog)

This code defines a two-column table with headings Product Name and Product Description, and then renders the product inventory into each row in the table. We set some style rules for the Table element, including a black grid throughout the table, a red line beneath the headers, and a light pink background on the header row. The PDF output looks like this:

Notice how the padding and centering of the table is handled automatically by the ReportLab package. Now we're getting more sophisticated, but there is a lot more to the ReportLab library. We will end our tour by looking at ReportLab's chart functions.

Creating charts in ReportLab is straightforward, but very powerful. One aspect of the charting module worth mentioning is that it does not have to be output to PDF files, but is general purpose enough to render to other formats. PostScript and bitmap output is available, as well as support for the SVG XML format is now supported by most browsers.

To create charts we need to use ReportLab's Drawing object. This is what ReportLab calls a "Flowable". We've already seen several examples of flowable objects: Paragraph, Spacer, and Table. Flowables can be added to a document template as we've already seen. In addition to the Drawing object, ReportLab provides several classes that represent different kinds of charts: line charts, bar charts, pie charts, and so on. We instantiate an object from one of these classes and manipulate it by adding our data, our axis labels, and so on. Once our chart object is set up, we can add it to our Drawing flowable and then add the Drawing to our document template.

The following example renders a simple line chart with fictitious sales data for our CranStore.com website. It shows sales of cranberry sauce through the months of October, November, and December. The code is as follows:

from reportlab.graphics.charts.linecharts import HorizontalLineChart
from reportlab.graphics.shapes import Drawing
sales = [ (23, 74, 93) ]
months = ['Oct', 'Nov', 'Dec']

doc = SimpleDocTemplate("products.pdf")
Catalog = []
style = styles['Normal']
p = Paragraph("Cranberry Sauce Sales",
styles['Heading1']) Catalog.append(p)
d = Drawing(100, 100)
cht = HorizontalLineChart()
cht.data = sales
cht.categoryAxis.categoryNames = months
d.add(cht)
Catalog.append(d)
doc.build(Catalog)

The output in PDF format appears as follows:

Notice that our sales data is a list of tuples. This list could contain more than one element. If it did, then our horizontal line chart would contain two lines: one for each data series defined in the sales list. Most of ReportLab's line chart classes allow for this type of data definition.

Generating a pie chart is equally as simple. We can even add a fancy pop-out on a specific pie slice:

doc = SimpleDocTemplate("products.pdf")
Catalog = []
style = styles['Normal']
p = Paragraph("Cranberry Sauce Sales",
styles['Heading1']) Catalog.append(p)
d = Drawing(100, 125)
cht = Pie()
cht.data = sales[0]
cht.labels = months
cht.slices[0].popout = 10
d.add(cht)
Catalog.append(d)
doc.build(Catalog)

When rendered the pie chart appears as follows:

As you can see, ReportLab allows us to quickly generate interesting report output in PDF and other formats. As it is a Python library, we can easily integrate it with our existing Django code. But what if we want to publish our report information to the Web? The next section will explain how to write Django views that generate custom, dynamic PDF output.

Creating PDF views

When working with ReportLab, we briefly mentioned that we can create canvas or document templates using either a file name or an open Python file, or file-like, object. In our previous examples, we created a new file using a file name passed as a string to our ReportLab constructors. If we wanted, we could have opened a file manually and used it as a constructor argument:

f = open('products.pdf')
doc = SimpleDocTemplate(f)

This is important because when working with Django views, we want to be able to return this PDF file after generating it from our database information. One idea is to generate the file on disk and redirect the user's browser to the newly create PDF. But this is a lot of extra effort and will require maintenance of the disk to prevent old report requests from using up our server's storage space.

A much better alternative is to use ReportLab's support for file-like objects with our Django response object. Django views always return an instance of the HttpResponse class. It turns out that this response object is itself a file-like object; it supports a write method and can therefore be used with our ReportLab routines. This works like so: our view code constructs a response object and passes it to our report generation code. When our report is created, control returns to our view, which returns our response object that now contains the report.

Here is an example Django view that generates a pie chart using the routine listed in the previous section. The pie chart code has been moved to a make_pie_chart function, which takes and returns a file object:

def piechart_view(request):
response = HttpResponse(mimetype='application/pdf')
doc = SimpleDocTemplate(response)
Catalog = []
style = styles['Normal']
p = Paragraph("Cranberry Sauce Sales", styles['Heading1'])
Catalog.append(p)
d = Drawing(100, 125)
cht = Pie()
cht.data = sales[0]
cht.labels = months
cht.slices[0].popout = 10
d.add(cht)
Catalog.append(d)
doc.build(Catalog)
return response

The key to these specialized views is setting the appropriate mimetype on the HttpResponse object. Browsers will interpret the mimetype and launch the appropriate in-browser handler for the file returned by the view. You can specify other mimetypes in this way, as well. If we were using ReportLab to generate a PNG file instead of PDF, we would use this:

response = HttpResponse(mimetype='image/png')

This is an extremely powerful feature of Django and allows us to write views that generate reports based on parameters defined in URL patterns or using GET and POST values. You could even manipulate the report's display by processing GET parameters and adjusting the resulting report (for example, breaking out the smallest element of the pie chart if a certain GET flag is included).

If we wanted to use a toolkit besides ReportLab, the implementation pattern would be exactly the same. Anything that can work with Python file-like objects can be used in a view, as we've done previously. We can generate any type of file, even spreadsheet data in a comma-separated value format, as long as we provide the appropriate mimetype.

Summary

In this article we've covered visual display output to PDF using ReportLab for Django 1.2 e-commerce. Specifically, we saw generating charts and graph-based reports.


If you have read this article you may be interested to view:


Django 1.2 E-commerce Build powerful e-commerce applications using Django, a leading Python web framework
Published: May 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Jesse Legg

Jesse Legg is a web engineer and writer based in Somerville, Massachusetts. He has built Python and Django tools in environments ranging from web startups to higher education and has contributed several applications to the Django open source community. He has been working in the technology industry for almost a decade and has developed web applications in a range of technologies, including Python, Django, PHP, and Javascript. He writes an occasional blog at jesselegg.com.

Books From Packt


Plone 3 Products Development Cookbook
Plone 3 Products Development Cookbook

Drupal E-commerce with Ubercart 2.x
Drupal E-commerce with Ubercart 2.x

Joomla! E-Commerce with VirtueMart
Joomla! E-Commerce with VirtueMart

Magento 1.3 Sales Tactics Cookbook
Magento 1.3 Sales Tactics Cookbook

PHP 5 E-commerce Development
PHP 5 E-commerce Development

TYPO3 4.2 E-Commerce
TYPO3 4.2 E-Commerce

PrestaShop 1.3 Beginner's Guide
PrestaShop 1.3 Beginner's Guide

Python Testing: Beginner's Guide
Python Testing: Beginner's Guide


Your rating: None Average: 5 (1 vote)
Error by
Also the getSampleStyleSheet method is missing it should be something like this: reportlab.lib.styles import getSampleStyleSheet styles = getSampleStyleSheet() style = styles['Heading1'] ...
Error by
I'm getting the error: 'module' object is unsubscriptable At header = Paragraph("Outages",styles['Heading1']) I think the problem is on the styles['Headin1'] sentence. Has anybody the same problem? Thanks
Error by
In first example that generates a PDF report listing there is typing error: from reportlab.lib import sytles should be from reportlab.lib import styles Thanx for great tutorial !

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
9
u
t
r
y
2
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software