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

Django 1.2 E-commerce

May 2010


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

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

ReportLab is an open source project available at 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" %, style)
s = Spacer(1, 0.25*inch)

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.

(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'])
style = styles['Normal']
headings = ('Product Name', 'Product Description')
allproducts = [(, p.description) for p
in Product.objects.all()]
t = Table([headings] + allproducts)
[('GRID', (0,0), (1,-1), 2,,
('LINEBELOW', (0,0), (-1,0), 2,,
('BACKGROUND', (0, 0), (-1, 0),]))

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 website. It shows sales of cranberry sauce through the months of October, November, and December. The code is as follows:

from import HorizontalLineChart
from 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() = sales
cht.categoryAxis.categoryNames = months

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() = sales[0]
cht.labels = months
cht.slices[0].popout = 10

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'])
d = Drawing(100, 125)
cht = Pie() = sales[0]
cht.labels = months
cht.slices[0].popout = 10
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.


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:

Books to Consider

comments powered by Disqus