How to create a pdf report Reportlab

0

I was wondering how could I create a pdf report using reportlab. I’m developing a web site and I’m using Django. In my project I charged a lot of icons, and I was using a Javascript function(PrintThisjs), but it doesn’t work well. Seems like Reportlab is a good solution. Thanks :slight_smile:

Reportlab is awesome. It took me about a day or two to figure out how to programmatically produce quite complex PDF-files. I installed Reportlab into my venv and then I created a folder inside my project called pdf_generator. I created a file called factory.py. I have a method in there which takes a user, a month, a year and a project. Users will enter their work time into the system and then the system will produce a printable time sheet in the form of a PDF, save it and return the path to the file. Here’s the code I wrote. This will give you an example of how to create a basic document and put a table into it that is filled with data. I hope this will help you to get started.

import datetime
import os
from ahub import settings as s
from django.utils.timezone import now
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Image
from reportlab.lib.pagesizes import A4, cm
from reportlab.lib import colors
from time_manager.entry.models import Entry


class PDFGenerator(object):
    """ This class contains factory methods for creating PDF files. The methods should create and store the PDF and
        return the file path. """

    @staticmethod
    def get_directory():
        """ Build the path for the directory to store in. Takes the username as argument. """
        directory = os.path.join(s.MEDIA_ROOT, 'time_manager', 'pdf', 'temp')
        # Create directory if it doesn't exist yet.
        if not os.path.exists(directory):
            os.makedirs(directory)

        return directory

    @staticmethod
    def time_sheet(user, year, month, project):
        """ Creates a time sheet with the work time a user entered for a certain project. """
        # Store the PDF in a sub folder named after the username with the following filename format:
        # YYYYMMDD_ZE_NameConsultant_Project_MMYY
        date = datetime.date(year=year, month=month, day=1)
        filename = now().strftime('%Y%m%d') + '_ZE_' + user.last_name + user.first_name + '_' + project.customer + \
            '_' + date.strftime('%m%y') + '.pdf'
        directory = PDFGenerator.get_directory()
        store_path = os.path.join(directory, filename)
        # Initiate doc.
        doc = SimpleDocTemplate(store_path, pagesize=A4, rightMargin=72, leftMargin=72, topMargin=72, bottomMargin=18)
        # Build header.
        header_text = 'Stundenzettel\n' + user.last_name + ', ' + user.first_name + '\n' + project.customer + '\n' + \
                      date.strftime('%B %Y')
        header_logo = Image(os.path.join(s.BASE_DIR, 'pdf_generator', 'files', 'logo_auticon.jpg'))
        header_table = Table([(header_text, header_logo,)], colWidths=[8*cm], rowHeights=[5*cm])
        header_table.setStyle(TableStyle([('VALIGN', (0, 0), (-1, -1), 'TOP'),
                                          ('ALIGN', (0, 0), (0, 0), 'LEFT'),
                                          ('ALIGN', (1, 0), (1, 0), 'RIGHT'),
                                          ]))
        # Build content table.
        content_table = Table(data=Entry.render_entries_for_pdf(user, year, month, project))
        content_table.setStyle(TableStyle([('INNERGRID', (0, 0), (-1, -1), 0.25, colors.gray),
                                           ('ALIGN', (3, 0), (-1, -1), 'RIGHT'),
                                           ]))
        # Build footer.
        footer_top_row = ('', '', '', '', '')
        footer_bot_row = ('Ort/Datum', 'Kunde', '', 'Ort/Datum', 'Consultant', )
        footer_table = Table([footer_top_row, footer_bot_row], colWidths=[3*cm, 3*cm, 4*cm, 3*cm, 3*cm], rowHeights=[2*cm, 0.5*cm])
        footer_table.setStyle(TableStyle([('INNERGRID', (0, 0), (0, 1), 0.25, colors.gray),
                                          ('INNERGRID', (1, 0), (1, 1), 0.25, colors.gray),
                                          ('INNERGRID', (3, 0), (3, 1), 0.25, colors.gray),
                                          ('INNERGRID', (4, 0), (4, 1), 0.25, colors.gray),
                                          ]))
        # Build layout table.
        data = [(header_table, ), (content_table, ), (footer_table, )]
        layout_table = Table(data)
        layout_table.setStyle(TableStyle([('ALIGN', (0, 0), (-1, -1), 'CENTER')]))

        doc.build([layout_table])

        return store_path
2 Likes

I have the same dilemma, I have to create a printable report of a player roster that is in a table format. The table I’ve created on the site is based on a paper copy that we used in previous years prior to having the Django site.
Do you still recommend ReportLab? I can use it for some work projects so I can justify the learning curve. But you mention a couple of days to figure it out. What are your thoughts on the learning curve, is it worth it?
Is ReportLab popular? What are the alternatives?
Can your recommend any tutorials or books? I find the documents well written but lacking examples.

Reportlab is excellent. I don’t think any other library can provide the capabilities that reportlab can. I avoided using document templates, I wanted to have everything customized my own way.

I could provide a table example script if you want, I made one before and never ended up using it. I also made images to explain some things which seemed tedious from the documentation.

It depends upon specifically what you want to produce.

We use ReportLab for creating some reports.

There are some other reports where we use the “PDF Export” features of Datatables to allow for client-side pdf creation. (It works extremely well for us in a wide variety of situations.)

I see where “fpdf” has been forked (fpdf2) and brought up-to-date - you may wish to take a look at it as an alternative. (I used fpdf a long time ago - admittedly I haven’t looked at it in about 10 years.)

1 Like

Well… if you want an easy starting example you’re in the right place. My example does just that. It turns a table into a PDF printable document. It took my two or three days to get there from scratch. But you should be able to use my example code and just change a few things and be ready to go.

The static method time_sheet() is where you want to look. Just follow the inline comments.

1 Like

Take a look at WeasyPrint as well. We are using it to create reports and they look great. It integrates well with Django since the input is an HTML document which you can generate by following standard Django process. From the view, you pass the context to the template which returns the HTML which you then pass to WeasyPrint to generate the PDF.

However, if you have the table already being displayed to the client, Datatables library has integrated functionality exporting to various formats, including PDF. Apart from the feature “pdfHtml5” already linked by @KenWhitesell above, we find it helpful to use “print” which opens up the browser’s print dialog, where the user can preview and adjust different settings before saving as PDF (paper size, orientation etc.).

I created the form in Reportlab, created a view/link/URL and the form pops up as expected.
The form has a table formatted for exactly 24 positions screen capture below.
I’m not sure the correct method for inputting the data into the table. I assume the methods are iterating over a list or accessing the individual elements and placing them inside the cells. I tried the latter but can’t figure out the correct syntax. Which makes me wonder if I should be inserting the values this way in the first place. Assuming inserting values can be done this way, does anyone know the correct syntax to access individual elements?

res = Table([
       
       # Header Row
       ['Pos', 'Color', '#', 'Last', 'First', 'DOB', 'Last Team', 'Level',
       'Zone', 'CC'],
       # Test Row 1
       ['D', 'Black', '20','Smith','John','1970-10-06',
       'Thrashers','AAA','Lord Selkirk','Melville'],
       ['D', '{player_list}', ''],
       ['D', '', ''],
       ['D', '', ''],
       ['D', '', ''],

Is this way possible?
Is iterating over a list the only way?
What are the other ways?
Each form will have a set number of 24 rows with 10 elements. This form will be used for printing one form element containing all 240 cells.

Formatting works with the style attributes. There’s also _argW and _argH lists which allow you to format the width of an entire row, or the height of an entire row.

for example this would affect the first row of table cells:
table._argH[0] = 5mm

this would affect the second row of table cells and so on:
table._argH[1] = 5mm

for the table elements you can also use a paragraph flowable inside a list so instead of {player_list}, you can use [P]

from reportlab.lib.colors import *
from reportlab.lib.units import inch, mm, cm

   P = Paragraph(''' The ReportLab Left Logo Image''',self.styles["BodyText"])
        data= [['A', 'B', 'C', P0, 'D'],
        ['00', '01', '02', [P0], '04'],
        ['10', '11', '12', [P], '14'],
        ['20', '21', '22', '23', '24'],
        ['30', '31', '32', '33', '34']]

        t=Table(data,style=[('GRID',(1,1),(-2,-2),1, green),
        ('BOX',(0,0),(1,-1),2,red),
        ('LINEABOVE',(1,2),(-2,2),1,blue),
        ('LINEBEFORE',(2,1),(2,-2),1,pink),
        ('BACKGROUND', (0, 0), (0, 1), pink),
        ('BACKGROUND', (1, 1), (1, 2), lavender),
        ('BACKGROUND', (2, 2), (2, 3), orange),
        ('BOX',(0,0),(-1,-1),2,black),
        ('GRID',(0,0),(-1,-1),0.5,black),
        ('VALIGN',(3,0),(3,0),'BOTTOM'),
        ('BACKGROUND',(3,0),(3,0),limegreen),
        ('BACKGROUND',(3,1),(3,1),khaki),
        ('ALIGN',(3,1),(3,1),'CENTER'),
        ('BACKGROUND',(3,2),(3,2),beige),
        ('ALIGN',(3,2),(3,2),'LEFT'),
        ])
        t._argW[3]=1.5*inch

In terms of the table attributes, these images explain what these main attributes mean if you wanted to make your own custom style (without resorting to using the ‘BOX’ attribute).

Based on a 4x4 table:

1 Like

This is great information for reference. Thank-you.
To confirm, to put the values from the database into the table, you access them via their element in an array, [P0] for example. Correct?

In what I posted there, “P0” was actually just a name I gave to a paragraph flowabale, the 0 isn’t an index or anything. You can do a lot with Paragraph flowables because they use XML markup, so you could turn one into a sequenced list /bullet point list and more. I can’t remember what page of the documentation is relevant but if you search “<br/>” you’ll probably find the area of reference since that’s what makes a new line in a Paragraph flowable.

here it is…page 72 - 82

To get data from a database into the pdf, you will need to do data retrieval with a primary key and get()

for example:
var = DatabaseModel.objects.get(pk = id)

then you would do var.my_field1, var.my_field2, var.my_field3 and so on based on the different database fields you have. You can pass the primary key through a button in Jinja2 and it makes things easy…I honestly don’t know of many other ways of doing it.

The method Table expects an argument data. This is just a list of lists. The outer list is a list of rows. The inner list is a list of the values for each column in that row. I would suggest you create two methods for the objects that you want to put in the table. The first method while turn an object instance into a list of the values that you need for the table. The second method could be one that excepts arguments for selecting players. Inside this method would be a call to the database, something like:

Player.objects.filter(level=‘AAA’).sort_by(whatever)

Then you return that as a list to the Table method.
For accessing values inside the Table, you should be able to do something like Table.data[9][5] or Table.data[9][5] = new_value for overwriting a value. Remeber though that this gives the value of column 6 in row 10.