ReportLab II
I wanted to generate a simple booking confirmation PDF. Below is the result. ReportLab is certainly powerful enough to create these kind of docs. It is very flexible, but this flexibiliy comes at the price of some verbosity. I created a simple wrapper module to meet my requirements and perhaps yours. If you are a diver, book your next scuba holiday on web2py at www.unlogged.co.uk. :)
The module I wrote consists of the following files:
<app>/modules/pdf
|-- common.py # common constants such as H1, H2, ... for headers
|-- __init__.py # the main PDF class
|-- theme.py # DefaultTheme class you can extend to match your needs
|-- util.py # currently one helper to calculate table column widths
Here's a quick example of how I used it to create the PDF above. For the module source please see further below.
# this is a helper in my controller # it takes the divecentre (dc) record and the shopping cart def confirmation_pdf(dc, cart): # Let's import the wrapper import pdf from pdf.theme import colors, DefaultTheme # and define a constant TABLE_WIDTH = 540 # this you cannot do in rLab which is why I wrote the helper initially # then let's extend the Default theme. I need more space so I redefine the margins # also I don't want tables, etc to break across pages (allowSplitting = False) # see http://www.reportlab.com/docs/reportlab-userguide.pdf class MyTheme(DefaultTheme): doc = { 'leftMargin': 25, 'rightMargin': 25, 'topMargin': 20, 'bottomMargin': 25, 'allowSplitting': False } # let's create the doc and specify title and author doc = pdf.Pdf('Booking Confirmation', 'unlogged.co.uk') # now we apply our theme doc.set_theme(MyTheme) # time to add the logo at the top right logo_path = request.folder + 'static/unlogged/images/logo-2.png' doc.add_image(logo_path, 180, 67, pdf.RIGHT) # give me some space doc.add_spacer() # this header defaults to H1 doc.add_header('Booking confirmation') # here's how to add a paragraph doc.add_paragraph("We are pleased to confirm your reservation with <b>%s</b>...") doc.add_spacer() # a subheader - H2 doc.add_header("Divecenter details", pdf.H2) # as in pre-css days we wrap the address and the Google Map Image in a table # my wrapper module just reexposes the reportlab Paragraph and Table classes. # See __init__.py in the source section below address = pdf.Paragraph(""" <b>%(name)s</b><br/> %(hotelname)s<br/> %(region)s<br/> %(name)s<br/> %(country)s<br/><br/> GPS: %(latitude)s, %(longitude)s<br/><br/> Tel: %(tel)s<br/> %(web)s<br/> """ % dc, MyTheme.paragraph) # when we use the Paragraph class directly we have to s
pecify a# style. Here we a using one from the underlying DefaultTheme. # let's get the map image gps = "%(latitude)s,%(longitude)s" % dc gmap = pdf.Image("http://maps.google.com/maps/api/staticmap?center=" + gps + "&zoom=13&markers=color:orange|" + gps + "&size=250x150&sensor=false", 250,150) # and add both the address and the map wrapped in a table to our doc doc.add(pdf.Table([[address,gmap]], style=[('VALIGN', (0,0), (-1,-1), 'TOP')])) # UGLY inline stuff doc.add_spacer() doc.add_header('Diver details', pdf.H2)
# let's move on to the divers table
diver_table = [['Name', 'Qualification', 'Last dive']] # this is the header row
for diver in cart.get_divers():
diver_table.append([diver.name, diver.qualification, diver.last_dive]) # these are the other rows
doc.add_table(diver_table, TABLE_WIDTH)
# all the rest I omitted here but you get the picture.
# read the reportLab docs and the source below to figure out how to tewak things.
# again, see
http://www.reportlab.com/docs/reportlab-userguide.pdf
# ...
# ...
return doc.render()
Look at my last slice (PDF with ReportLab) to see how to set the response headers in order to show the pdf in the browser or to init a download.
Module source
""" common.py Author: H.C. v. Stockhausen < hc at vst.io > Date: 2012-10-14 """ # Header levels H1, H2, H3, H4, H5, H6 = 1, 2, 3, 4, 5, 6 # List styles UL, OL = 0, 1 # Alignment CENTER, LEFT, RIGHT = 'CENTER', 'LEFT', 'RIGHT'
""" __init__.py " Author: H.C. v. Stockhausen < hc at vst.io > Date: 2012-10-14 """ import cStringIO import urllib from reportlab.platypus.doctemplate import SimpleDocTemplate from reportlab.platypus.flowables import Image from reportlab.platypus import Paragraph, Spacer, KeepTogether from reportlab.lib import colors from reportlab.platypus.tables import Table, TableStyle from theme import DefaultTheme from util import calc_table_col_widths from common import * class Pdf(object): story = [] theme = DefaultTheme def __init__(self, title, author): self.title = title self.author = author def set_theme(self, theme): self.theme = theme def add(self, flowable): self.story.append(flowable) def add_header(self, text, level=H1): p = Paragraph(text, self.theme.header_for_level(level)) self.add(p) def add_spacer(self, height_inch=None): height_inch = height_inch or self.theme.spacer_height self.add(Spacer(1, height_inch)) # magic 1? no, first param not yet implemented by rLab guys def add_paragraph(self, text, style=None): style = style or self.theme.paragraph p = Paragraph(text, style) self.add(p) def add_list(self, items, list_style=UL): raise NotImplementedError def add_table(self, rows, width=None, col_widths=None, align=CENTER, extra_style=[]): style = self.theme.table_style + extra_style if width and col_widths is None: # one cannot spec table width in rLab only col widths col_widths = calc_table_col_widths(rows, width) # this helper calcs it for us table = Table(rows, col_widths, style=style, hAlign=align) self.add(table) def add_image(self, src, width, height, align=CENTER): img = Image(src, width, height) img.hAlign = align self.add(img) def add_qrcode(self, data, size=150, align=CENTER): "FIXME: ReportLib also supports QR-Codes. Check it out." src = "http://chart.googleapis.com/chart?" src += "chs=%sx%s&" % (size, size) src += "cht=qr&" src += "chl=" + urllib.quote(data) self.add_image(src, size, size, align) def render(self): buffer = cStringIO.StringIO() doc_template_args = self.theme.doc_template_args() doc = SimpleDocTemplate(buffer, title=self.title, author=self.author, **doc_template_args) doc.build(self.story) pdf = buffer.getvalue() buffer.close() return pdf
""" theme.py Author: H.C. v. Stockhausen < hc at vst.io > Date: 2012-10-14 """ from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch from reportlab.lib import colors from common import * class DefaultTheme(object): _s = getSampleStyleSheet() doc = { 'leftMargin': None, 'rightMargin': None, 'topMargin': None, 'bottomMargin': None } headers = { H1: _s['Heading1'], H2: _s['Heading2'], H3: _s['Heading3'], H4: _s['Heading4'], H5: _s['Heading5'], H6: _s['Heading6'], } paragraph = _s['Normal'] spacer_height = 0.25 * inch table_style = [ ('ALIGN', (0,0), (-1,-1), 'LEFT'), ('VALIGN', (0,0), (-1,-1), 'TOP'), ('FONT', (0,0), (-1,0), 'Helvetica-Bold'), ('LINEBELOW', (0,0), (-1,0), 1, colors.black), ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#C0C0C0')), ('ROWBACKGROUNDS', (0,1), (-1, -1), [colors.white, colors.HexColor('#E0E0E0')]) ] @classmethod def doc_template_args(cls): return dict([(k, v) for k, v in cls.doc.items() if v is not None]) @classmethod def header_for_level(cls, level): return cls.headers[level] def __new__(cls, *args, **kwargs): raise TypeError("Theme classes may not be instantiated.")
""" util.py Author: H.C. v. Stockhausen < hc at vst.io > Date: 2012-10-14 """ def calc_table_col_widths(rows, table_width): max_chars_per_col = [0] * len(rows[0]) for row in rows: for idx, col in enumerate(row): for line in str(col).split('\n'): max_chars_per_col[idx] = max(len(line), max_chars_per_col[idx]) sum_chars = sum(max_chars_per_col) return [(x * table_width / sum_chars) for x in max_chars_per_col]
Comments (1)
0
kkdoc-10636 12 years ago
Hi Hans Christian,
which version of reportlab do you prefer?
I have problems on debian 6.0.5 with build-essential installed:
easy_install reportlab
[...]
Best match: reportlab 2.6
[...]
/tmp/easy_install-TdH3sF/reportlab-2.6/src/rl_addons/rl_accel/_rl_accel.c:1280: error: ‘ttfonts_add32L’ undeclared here (not in a function)
/tmp/easy_install-TdH3sF/reportlab-2.6/src/rl_addons/rl_accel/_rl_accel.c:1281: error: ‘hex32’ undeclared here (not in a function)
/tmp/easy_install-TdH3sF/reportlab-2.6/src/rl_addons/rl_accel/_rl_accel.c:1282: error: expected ‘}’ before ‘unicode2T1’
error: Setup script exited with error: command 'gcc' failed with exit status 1
replies (1)