Product Catalogue Tutorial

XML product listing converted to a fully customisable PDF in seconds.

Welcome to the new ReportLab tutorial. Here you can get a basic introduction to a common application architecture and start experimenting with some fundamental ReportLab technologies. If you have any problems or questions, please email enquiries@reportlab.com.

We'll show you how to generate a well laid out and styled document from an XML feed. Let's get started!

Register and install an evaluation copy of ReportLab PLUS

This tutorial is aimed at showing the use of Report Markup Language (RML), which is a component of our commercial tool-kit. All you need to do to download a full evaluation copy is sign in or register on our site; then, follow the installation instructions to get yourself set up. Once these are completed, you're ready to go.

Download and install tutorial pack

First thing's first: download our tutorial pack. This includes a number of images which you will need later.

Take a look at the directory structure:

data/ 
output/
rml/
product_catalog.py
  • data/ will hold our XML
  • rml/ will hold the fonts, images, and templates for constructing the documents 
  • output/ will hold the generated PDFs
  • product_catalog.py will be the script which ties it all together

Making your first document in Report Markup Language

Execute python product_catalog.py, you should see the following output:

 

#################### 
about to parse file:  ../data/products.xml 
file parsed OK 

Trying to regenerate the check-list 
Check-list failed! Error: 
'Product' object has no attribute 'imgURL' 

Trying to regenerate the flyer 
success! created output/harwood_flyer.pdf 
#################### 

 

Notice that your 'checklist' is broken; that's ok for now, we'll come back to that later. Let's take a look at the flyer PDF. It's in /output/

You should see a PDF that looks like this:

The data is coming from the XML file, products.xml located in /data/. A standard technique is to build up Python objects with the variable data of interest and pass it to our templating system, preppy. Preppy generates an RML (Report Markup Language) file, which is our own mark-up language designed to lay out documents. A single line of code then converts an RML file into a PDF. We'll look at this process later, but for now, let's dive right in and get the document looking right.

Everything related to the layout and content of the document is described within the 'Prep file', flyer_template.prep which is within the /rml/ directory. Let's take a quick tour before making changes.

Inside a Preppy template

The file looks a lot like XML, with various bits of Python included within braces {{}}. This allows you to import functions and call on data objects which have been passed in.

The whole document is wrapped in a tag, with some key sections within it:

Making your first change: adding some new data

First, let's turn our attention to the story where we loop over the product objects (starts line 86 in flyer_template.prep):

We're looping over all our products and printing a paragraph for each product's name. Let's add product summary and product price as well; your story should now look like this:

Now execute python product_catalog.py and look at the results. You should see output like shown below, with new facts about each product displayed: 

Adding a page template

We have more content, but the appearance is still not ideal. Let's see how we can use a page template to arrange our content. Go back to templates and take note of each part:

By default, the PDF rendering engine uses the first template until it is told otherwise, so in this case, 'blank' has been used on all pages.

Let's include a set next template at the start of the story to use this 'products' template:

Adding some static PDF pages

Notice that a nice PDF background has been used on which the products list is printed. That can also be used to include full static pages. Let's use pre-made PDFs to replace the blank first page and insert a standard end page. Your story should look like this:

And the generated PDF should now have pages like the following included:

Learning some basic page flow controls

Now let's make the designer proud. First, note that page 3 beings with the text "meat...":

Ideally, we would be able to keep the descriptions from breaking across pages, so that all the information about a product stayed together. RML has many ways of controlling page flow and layouts, and in this case, we can use the keepWithNext attribute on these paragraph styles (prod_summary and prod_name). Your stylesheet should now look like this:

Notice that now page 3 has the entire block of content carried over together:

Getting fonts and colors correct

Finally, let's get our colors and fonts correct. We can register a new font and a new color in the docinit section; edit yours to look like this:

Now make a few small changes:

  • change 'red' to 'GREEN-ISH' in line 34
  • change fontName to 'Angelina' in line 50 and 64
  • change fontSize to '8' in line 73
  • change spaceBefore to '4' in lines 75 and 84
  • change textColor to 'GREEN-ISH' in lines 76 and 85

And regenerate the document one last time:

 

And there you have it, a professionally finished document created on the fly from an XML file. There is plenty more to learn to control the flow for more complex documents - see the RML users guide.

Looking at the data source

Now let's turn our attention to how we passed the data into the template in the first place. Open product_catalog.py. For now, lets focus on lines 44-64, where we see the main loop over the XML to build product objects:

Remember how the checklist has been failing to generate? This is because the PDF engine has been complaining that the Prep file is trying to access an attribute imgURL of the products which does not exist. If we check the XML, we'll see that there is a tag <ImageUrl>. Let's try giving our product objects an attribute based on this:

Now try building the document again. You will need to download a pack of images and extract the /img directory within your /rml directory for this to work:

Let's tidy up this issue when there is no set price and the 'request a quote' text does not fit into the small box. Let's put a conditional statement in our template which uses a different size box when the quote gets big. Your story should now look like this: (in /rml/checklist_template.prep)

Rebuild the document:

Finally, if all this meat is getting to you, you can change the vegetarian setting in line 11 of checklist_template.prep to True: