|
Web2Py
IntroductionIf you use web2py, you can make complex reports that can be viewed in a browser, or downloaded as PDF (taking advantage of web2py HTML helper objects to easily diagram a report). See WriteHTML for more information, supported tags and attributes, etc. Also, using web2py DAL, you can easily set up a templating engine for PDF documents. See Templates for more information. The following examples are packaged in a ready to run application: web2py.app.fpdf.w2p PyFPDF is included in web2py since release 1.85.2 Also, you can download the latest versión pyfpdf and uncompress it in the web2py, gluon, contrib folder. Note about images: this sample images are small so they may look like low quality ones. For better results, use bigger images: more DPI (screen is often 72/96DPI, printers are often 300/600DPI). As a rule of thumb, use at least half of the image size when rendering to PDF, ie. if image is 500x200px, use 250x100px as width/height attributes of IMG tag. Sample ReportYou could make a "professional looking" bussiness report just using web2py HTML helpers, mixin headers, logos, charts, text and tables. Te main advantage of this method is that the same report can be rendered in a HTML view, or can be downloaded as PDF, with a minimal effort: Sample: report.pdf Updated Live Demo (HTML and PDF version): def report():
response.title = "web2py sample report"
# include a google chart (download it dynamically!)
url = "http://chart.apis.google.com/chart?cht=p3&chd=t:60,40&chs=500x200&chl=Hello|World&.png"
chart = IMG(_src=url, _width="250",_height="100")
# create a small table with some data:
rows = [THEAD(TR(TH("Key",_width="70%"), TH("Value",_width="30%"))),
TBODY(TR(TD("Hello"),TD("60")),
TR(TD("World"),TD("40")))]
table = TABLE(*rows, _border="0", _align="center", _width="50%")
if request.extension=="pdf":
from gluon.contrib.pyfpdf import FPDF, HTMLMixin
# create a custom class with the required functionalities
class MyFPDF(FPDF, HTMLMixin):
def header(self):
"hook to draw custom page header (logo and title)"
logo=os.path.join(request.env.web2py_path,"gluon","contrib","pyfpdf","tutorial","logo_pb.png")
self.image(logo,10,8,33)
self.set_font('Arial','B',15)
self.cell(65) # padding
self.cell(60,10,response.title,1,0,'C')
self.ln(20)
def footer(self):
"hook to draw custom page footer (printing page numbers)"
self.set_y(-15)
self.set_font('Arial','I',8)
txt = 'Page %s of %s' % (self.page_no(), self.alias_nb_pages())
self.cell(0,10,txt,0,0,'C')
pdf=MyFPDF()
# create a page and serialize/render HTML objects
pdf.add_page()
pdf.write_html(str(XML(table, sanitize=False)))
pdf.write_html(str(XML(CENTER(chart), sanitize=False)))
# prepare PDF to download:
response.headers['Content-Type']='application/pdf'
return pdf.output(dest='S')
else:
# normal html view:
return dict(chart=chart, table=table)
Sample Table ListingAlso, you can make nice tables that automatically spreads over several pages, with header/footers, column/row highlight, etc., in a very pythonic way: Sample: listing.pdf Updated Live Demo (HTML and PDF version): def listing():
response.title = "web2py sample listing"
# define header and footers:
head = THEAD(TR(TH("Header 1",_width="50%"),
TH("Header 2",_width="30%"),
TH("Header 3",_width="20%"),
_bgcolor="#A0A0A0"))
foot = TFOOT(TR(TH("Footer 1",_width="50%"),
TH("Footer 2",_width="30%"),
TH("Footer 3",_width="20%"),
_bgcolor="#E0E0E0"))
# create several rows:
rows = []
for i in range(1000):
col = i % 2 and "#F0F0F0" or "#FFFFFF"
rows.append(TR(TD("Row %s" %i),
TD("something", _align="center"),
TD("%s" % i, _align="right"),
_bgcolor=col))
# make the table object
body = TBODY(*rows)
table = TABLE(*[head,foot, body],
_border="1", _align="center", _width="100%")
if request.extension=="pdf":
from gluon.contrib.pyfpdf import FPDF, HTMLMixin
# define our FPDF class (move to modules if it is reused frequently)
class MyFPDF(FPDF, HTMLMixin):
def header(self):
self.set_font('Arial','B',15)
self.cell(0,10, response.title ,1,0,'C')
self.ln(20)
def footer(self):
self.set_y(-15)
self.set_font('Arial','I',8)
txt = 'Page %s of %s' % (self.page_no(), self.alias_nb_pages())
self.cell(0,10,txt,0,0,'C')
pdf=MyFPDF()
# first page:
pdf.add_page()
pdf.write_html(str(XML(table, sanitize=False)))
response.headers['Content-Type']='application/pdf'
return pdf.output(dest='S')
else:
# normal html view:
return dict(table=table)}}}Sample Templating EnginePyFPDF and web2py can be used to make PDF documents using templates like invoices, badges, certificates, etc.: Sample: invoice.pdf Updated Live Demo: http://www.web2py.com.ar/fpdf/default/invoice.pdf To handle multiples templates, we can define two tables in web2py:
In db.py write: db.define_table("pdf_template",
Field("pdf_template_id","id"),
Field("title"),
Field("format", requires=IS_IN_SET(["A4","legal","letter"])),
)
db.define_table("pdf_element",
Field("pdf_template_id", db.pdf_template, requires=IS_IN_DB(db,'pdf_template.pdf_template_id', 'pdf_template.title')),
Field("name", requires=IS_NOT_EMPTY()),
Field("type", length=2, requires=IS_IN_SET(['T', 'L', 'I', 'B', 'BC'])),
Field("x1", "double", requires=IS_NOT_EMPTY()),
Field("y1", "double", requires=IS_NOT_EMPTY()),
Field("x2", "double", requires=IS_NOT_EMPTY()),
Field("y2", "double", requires=IS_NOT_EMPTY()),
Field("font", default="Arial", requires=IS_IN_SET(['Courier','Arial','Times','Symbol','Zapfdingbats'])),
Field("size", "double", default="10", requires=IS_NOT_EMPTY()),
Field("bold", "boolean"),
Field("italic", "boolean"),
Field("underline", "boolean"),
Field("foreground", "integer", default=0x000000, comment="Color text"),
Field("background", "integer", default=0xFFFFFF, comment="Fill color"),
Field("align", "string", length=1, default="L", requires=IS_IN_SET(['L', 'R', 'C', 'J'])),
Field("text", "text", comment="Default text"),
Field("priority", "integer", default=0, comment="Z-Order"),
)At this point you could go to web2py AppAdmin and start to define your document templates, or use import/export functions to reuse your already defined formats! Then, you can use PyFPDF Templates directly reading rows elements from the web2py database: For example, for an invoice, in a controller you could write: def invoice():
from gluon.contrib.pyfpdf import Template
import os.path
# generate sample invoice (according Argentina's regulations)
import random
from decimal import Decimal
# read elements from db
elements = db(db.pdf_element.pdf_template_id==1).select(orderby=db.pdf_element.priority)
f = Template(format="A4",
elements = elements,
title="Sample Invoice", author="Sample Company",
subject="Sample Customer", keywords="Electronic TAX Invoice")
# create some random invoice line items and detail data
detail = "Lorem ipsum dolor sit amet, consectetur. " * 5
items = []
for i in range(1, 30):
ds = "Sample product %s" % i
qty = random.randint(1,10)
price = round(random.random()*100,3)
code = "%s%s%02d" % (chr(random.randint(65,90)), chr(random.randint(65,90)),i)
items.append(dict(code=code, unit='u',
qty=qty, price=price,
amount=qty*price,
ds="%s: %s" % (i,ds)))
# divide and count lines
lines = 0
li_items = []
for it in items:
qty = it['qty']
code = it['code']
unit = it['unit']
for ds in f.split_multicell(it['ds'], 'item_description01'):
# add item description line (without price nor amount)
li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None))
# clean qty and code (show only at first)
unit = qty = code = None
# set last item line price and amount
li_items[-1].update(amount = it['amount'],
price = it['price'])
# split detail into each line description
obs="\n<U>Detail:</U>\n\n" + detail
for ds in f.split_multicell(obs, 'item_description01'):
li_items.append(dict(code=code, ds=ds, qty=qty, unit=unit, price=None, amount=None))
# calculate pages:
lines = len(li_items)
max_lines_per_page = 24
pages = lines / (max_lines_per_page - 1)
if lines % (max_lines_per_page - 1): pages = pages + 1
# fill placeholders for each page
for page in range(1, pages+1):
f.add_page()
f['page'] = 'Page %s of %s' % (page, pages)
if pages>1 and page<pages:
s = 'Continues on page %s' % (page+1)
else:
s = ''
f['item_description%02d' % (max_lines_per_page+1)] = s
f["company_name"] = "Sample Company"
f["company_logo"] = os.path.join(request.env.web2py_path,"gluon","contrib","pyfpdf","tutorial","logo.png")
f["company_header1"] = "Some Address - somewhere -"
f["company_header2"] = "http://www.example.com"
f["company_footer1"] = "Tax Code ..."
f["company_footer2"] = "Tax/VAT ID ..."
f['number'] = '0001-00001234'
f['issue_date'] = '2010-09-10'
f['due_date'] = '2099-09-10'
f['customer_name'] = "Sample Client"
f['customer_address'] = "Siempreviva 1234"
# print line item...
li = 0
k = 0
total = Decimal("0.00")
for it in li_items:
k = k + 1
if k > page * (max_lines_per_page - 1):
break
if it['amount']:
total += Decimal("%.6f" % it['amount'])
if k > (page - 1) * (max_lines_per_page - 1):
li += 1
if it['qty'] is not None:
f['item_quantity%02d' % li] = it['qty']
if it['code'] is not None:
f['item_code%02d' % li] = it['code']
if it['unit'] is not None:
f['item_unit%02d' % li] = it['unit']
f['item_description%02d' % li] = it['ds']
if it['price'] is not None:
f['item_price%02d' % li] = "%0.3f" % it['price']
if it['amount'] is not None:
f['item_amount%02d' % li] = "%0.2f" % it['amount']
# last page? print totals:
if pages == page:
f['net'] = "%0.2f" % (total/Decimal("1.21"))
f['vat'] = "%0.2f" % (total*(1-1/Decimal("1.21")))
f['total_label'] = 'Total:'
else:
f['total_label'] = 'SubTotal:'
f['total'] = "%0.2f" % total
response.headers['Content-Type']='application/pdf'
return f.render('invoice.pdf', dest='S')Of course, this is a hardcoded example, you can use the database to store invoices or any other data, there is no rigid class hierachy to follow, just fill your template like a dict! |
well, I am not able to understand, how to call the report for my application. If I understand what is explained is that we implement the dual tables, and pdf_template pdf_element. But within my application how do I call the report?
I've added "live demos" showing how to call web2py controllers (html and pdf views, when available). In downloads there is an sample application.
PS: can you fill an issue, as googlecode project updates seems broken... (and there we can attach code, examples and so on)
Thank you for answering the previous post. But if I need to do a report by filtering by date, as do the data entry???
I´ve put some usable thing in a slice: http://web2pyslices.com/main/slices/take_slice/99
It does a simple report by letting the user pick a date range, filtering a table and providing a PDF button to get the PDF version.
I think it can get better, so please test and comment!
I'm looking for php.... how can i implement that please tell me... i want such class to be more implemented than existing on on fpdf for writeHTML.. that doesnot accept table tag.
Hello, nice work and share.
I experienced odd output, the header() seems not handled the linefeed at the next page properly as the example
class MyFPDF(...): def header(self): ... self.ln(20)I would like to suggest
class MyFPDF(...): def header(self): ln = 20 first_page = 1 ... if self.page_no > first_page: self.ln(ln*2) else: self.ln(ln)Hope it helps