Two different pages with reportlab SimpleDocTemplate and Django - python

I'm using django and generating reports following this example, I need to generate a last page but without headers or footers and different content.
I'm trying to do this:
def print_example(self):
buffer = self.buffer
doc = SimpleDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72,
pagesize=self.pagesize)
elements = []
elements.append(Paragraph('Content for all pages'), my_custom_style)
# ...
doc.build(elements, onFirstPage=self._header_footer, onLaterPages=self._header_footer,
canvasmaker=NumberedCanvas)
doc2 = SimpleDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72,
pagesize=self.pagesize)
elements2 = []
elements2.append(Paragraph('Content for the last page only'), my_custom_style)
doc2.build(elements2, canvasmaker=NumberedCanvas)
# Get the value of the BytesIO buffer and write it to the response.
pdf = buffer.getvalue()
buffer.close()
return pdf
Then only the last content appears and previous content dissapears.
How can I generate the last page with different content?

I don't think it's possible using SimpleDocTemplate but you can achieve this by using BaseDocTemplate and defining your own templates.
Basic example
from reportlab.platypus import PageTemplate, BaseDocTemplate, NextPageTemplate, PageBreak
def headerFooterLayout(canvas, doc):
canvas.saveState()
canvas.setPageSize(self.pagesize)
# add header/footer
canvas.restoreState()
def emptyLayout(canvas, doc):
canvas.saveState()
canvas.setPageSize(self.pagesize)
canvas.restoreState()
pHeight, pWidth = self.pagesize
myFrame = Frame(0, 0, pHeight, pWidth, id='myFrame')
headerFooterTemplate = PageTemplate(id='headerFooterTemplate',
frames=[myFrame],
onPage=headerFooterLayout)
emptyTemplate = PageTemplate(id='emptyTemplate',
frames=[myFrame],
onPage=emptyLayout)
elements = []
elements.append(Paragraph('blah', style))
elements.append(NextPageTemplate('emptyTemplate'))
elements.append(PageBreak())
elements.append(Paragraph('last page', style))
doc = BaseDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72)
doc.addPageTemplates([headerFooterTemplate, emptyTemplate])
doc.build(elements)
It's been quite a while since I used this so there may well be some issues but comment if something doesn't work.
This is all in the user guide but can be hard to find what you're looking for.

Related

ReportLab make a multipage table with header and footer

I'm trying to make a simple report that has a header/footer and table in the middle with a ot of rows.
I'm new to ReportLab and the reason I switched from WeasyPrint is because I wanted the engine to handle page breaks nicely.
Below is my code :
def print_users(self):
buffer = self.buffer
doc = BaseDocTemplate(buffer,
rightMargin=20,
leftMargin=20,
topMargin=20,
bottomMargin=20,
pagesize=landscape(self.pagesize))
frame = Frame(
doc.leftMargin,
doc.bottomMargin,
doc.width,
doc.height - inch * 0.5,
id='normal',
showBoundary=1)
template = PageTemplate(id='all_pages', frames=frame, onPage=self._header_footer)
doc.addPageTemplates([template])
styles=getSampleStyleSheet()
# Our container for 'Flowable' objects
elements = []
elements.append(Spacer(1, 3*units.cm))
title = self.event.name
elements.append(Paragraph(self.event.name, styles["Normal"]))
elements.append(Spacer(1, 1*units.cm))
data = []
titles = ['First name', 'Last name', 'Position', 'Institution']
data.append(titles)
for invitation in self.invitations:
line = []
line.append(invitation.person.firstname)
line.append(invitation.person.lastname)
line.append(invitation.person.position)
line.append(invitation.institution.name)
data.append(line)
t=Table(data)
t.setStyle(TableStyle(
[('LINEBELOW',(0,0),(-1,-1),1, colors.gray)]))
table_story = [t]
elements.append(t)
# t_keep = KeepInFrame(0, 0, table_story, hAlign='CENTER', vAlign='MIDDLE', fakeWidth=False)
# write the document to disk
# Draw things on the PDF. Here's where the PDF generation happens.
# See the ReportLab documentation for the full list of functionality.
# elements.append(t_keep)
doc.build(elements)
The header/footer are showing fine and so is the table , but when we go to the second page, the table overrides the header.
I tried to add a frame with KeepInFrame ut the table becomes so small to fit inside the frame in one page.
It seems like a simple task, but i can't find a way to simply take into consideration the header on all pages. (Or maybe i'm taking the wrong approach?)
I face the same issue but in my case was because my header take like 20 mm of space meanwhile my topMargin was only 10mm. Changing the topMargin to more than 20 fix the issue.

Pass id parameter into imported class in Django

In Django I have a function based view responsible of printing the details (actually only the name) of all the registered users on a pdf file.
def test_pdf(request, id):
# Create the HttpResponse object with the appropriate PDF headers.
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="My Users.pdf"'
buffer = io.BytesIO()
report = MyPrint(buffer, 'Letter', id)
pdf = report.print_users()
response.write(pdf)
return response
This function works because I imported in the views.py file a class I built in another file, responsible of drawing the pdf, MyPrint:
from reportlab.lib.pagesizes import letter, A4
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER
from django.contrib.auth.models import User
class MyPrint:
def __init__(self, buffer, pagesize):
self.buffer = buffer
if pagesize == 'A4':
self.pagesize = A4
elif pagesize == 'Letter':
self.pagesize = letter
self.width, self.height = self.pagesize
def print_users(self):
buffer = self.buffer
doc = SimpleDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72,
pagesize=self.pagesize)
# Our container for 'Flowable' objects
elements = []
# A large collection of style sheets pre-made for us
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='centered', alignment=TA_CENTER))
# Draw things on the PDF. Here's where the PDF generation happens.
# See the ReportLab documentation for the full list of functionality.
users = User.objects.all()
elements.append(Paragraph('My User Names', styles['Heading1']))
for i, user in enumerate(users):
elements.append(Paragraph(user.get_full_name(), styles['Normal']))
doc.build(elements)
# Get the value of the BytesIO buffer and write it to the response.
pdf = buffer.getvalue()
buffer.close()
return pdf
Now, How can I make the function and the class specific to a user if I pass in the relative pk into the function? Apart from updating the urlpattern, should I pass the id into the class and / or into the function?
If you want to have the existing function work with one or more users, and continue to work if you don't pass in an id, I think the simplest way of changing it would be as follows:
def print_users(self, id=None):
buffer = self.buffer
doc = SimpleDocTemplate(buffer,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72,
pagesize=self.pagesize)
# Our container for 'Flowable' objects
elements = []
# A large collection of style sheets pre-made for us
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='centered', alignment=TA_CENTER))
# Draw things on the PDF. Here's where the PDF generation happens.
# See the ReportLab documentation for the full list of functionality.
users = User.objects.all()
if id:
users = users.filter(id__in=id)
elements.append(Paragraph('My User Names', styles['Heading1']))
for i, user in enumerate(users):
elements.append(Paragraph(user.get_full_name(), styles['Normal']))
doc.build(elements)
# Get the value of the BytesIO buffer and write it to the response.
pdf = buffer.getvalue()
buffer.close()
return pdf
Then change how you call it to:
report = MyPrint(buffer, 'Letter')
pdf = report.print_users(id)
or, if you want to print all users, just call it as:
report = MyPrint(buffer, 'Letter')
pdf = report.print_users()

How does platypus "guess" bold and italic styles?

Here's my code. My first function is based on the /Lib/site-packages/reportlab/lib/styles.py source code, to create an array of style:
def create_styles(p_tuples):
retour = StyleSheet1()
parent = None
for p_name, font_name, font in p_tuples:
# (!) change path if Linux:
ttf_file = "C:/Windows/Fonts/{}.ttf".format(font)
pdfmetrics.registerFont(TTFont(font_name, ttf_file))
if parent is None:
p = ParagraphStyle(name=p_name,
fontName=font_name,
fontSize=10,
leading=12)
retour.add(p)
parent = p
else:
retour.add(ParagraphStyle(name=p_name,
parent=parent,
fontName=font_name,
fontSize=10,
leading=12))
return retour
Then I build my own array with my installed fonts:
def render_to_response(self, context, **response_kwargs):
# this is a response for Django, but the question is about styles
response = HttpResponse(content_type='application/pdf; charset=utf-8')
# ! Hint : no filename -> the browser extracts it from the URL!
# -> create URLs like http://pdfreportlab.com/extract/myfilename.pdf
# this is the way to go to have 100% working UTF-8 filenames!
response['Content-Disposition'] = 'attachment; filename=""'
my_styles = self.create_styles([
('ms-regular', 'montserrat-regular',
'Montserrat-Regular'),
('ms-black', 'montserrat-black',
'Montserrat-Black'),
('ms-black-italic', 'montserrat-black-italic',
'Montserrat-BlackItalic'),
('ms-bold', 'montserrat-bold',
'Montserrat-Bold'),
('ms-bold-italic', 'montserrat-bold-italic',
'Montserrat-BoldItalic'),
])
doc = SimpleDocTemplate(response)
elements = []
c = canvas.Canvas(response, pagesize=A4, )
for idx in my_styles.byName:
p = Paragraph("Hello World <i>italic</i> <b>bold</b>",
style=my_styles[idx])
width, height = p.wrapOn(c, A4[0], A4[1])
elements.append(p)
doc.build(elements)
return response
Everything is working except the (very annoying) fact that the <i></i> and <b></b> tags are ignored! It only uses the current font in the style.
How could you modify my code so that it takes in account the tags and I finally get styles with the tags in the text itself?
You need to register a font family if you want Platypus to auto-select fonts - you're creating a different style for every font variant so of course the <i> and <b> tags have no effect - it doesn't know what fonts to use for them as the passed style references a single font.
Here's how to build a style using a font family:
from reportlab.lib.styles import ParagraphStyle
from reportlab.pdfbase.pdfmetrics import registerFont, registerFontFamily
from reportlab.pdfbase.ttfonts import TTFont
def create_paragraph_style(name, font_name, **kwargs):
ttf_path = "C:/Windows/Fonts/{}.ttf"
family_args = {} # store arguments for the font family creation
for font_type in ("normal", "bold", "italic", "boldItalic"): # recognized font variants
if font_type in kwargs: # if this type was passed...
font_variant = "{}-{}".format(font_name, font_type) # create font variant name
registerFont(TTFont(font_variant, ttf_path.format(kwargs[font_type])))
family_args[font_type] = font_variant # add it to font family arguments
registerFontFamily(font_name, **family_args) # register a font family
return ParagraphStyle(name=name, fontName=font_name, fontSize=10, leading=12)
Then you can create your paragraph style as:
pstyle = create_paragraph_style("ms", "montserrat",
normal="Montserrat-Regular",
bold="Montserrat-Bold",
boldItalic="Montserrat-BoldItalic")
assuming, of course, that you have TTF files with those names in your fonts directory.
You can then add it to your stylesheet (especially if you want parent inheritance - make sure you add it as an argument to be forwarded to the ParagraphStyle creation) or directly use it as:
p = Paragraph("Hello World <i>italic</i> <b>bold</b> <b><i>boldItalic</i></b>", style=pstyle)
(the standalone italic won't be affected as we didn't define it in the family, tho).

ReportLab: Flowable too large on page 1 in frame 'normal' of template 'First'

I build PDF using ReportLab. My program has a MyDocTemplate(SimpleDocTemplate) class with two methods: beforePage(self) and afterPage(self) which add header and footer (as PNG image) on every page. There is also a MyDocStyle class which describe ParagraphStyle.
Main method looks like this:
TITLE = Paragraph(Title, MyDocStyle.h1)
TO = Paragraph(To, MyDocStyle.h2)
FROM = Paragraph(From, MyDocStyle.h2)
SUBJECT = Paragraph(Subject, MyDocStyle.h2)
LONG_PARAGRAPH = Paragraph(Text, MyDocStyle.h3)
...
Elements = [TITLE, TO, FROM, SUBJECT, LONG_PARAGRAPH, ...]
doc = MyDocTemplete('output.pdf', pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=4*cm, bottomMargin=4*cm)
doc.build(Elements)
Data comes from CSV files and GUI. From time to time (depends on data length) I receive an error:
Flowable <Spacer at 0x2631120 frame=normal>...(1 x 5.66929133858) too large
on page 1 in frame 'normal'(469.88976378 x 603.118110236) of template 'First'
This exception stop my program. For short Paragraphs I set in MyDocStyle class h2.keepWithNext = 1 however it's not perfect solution. ReportLab split correctly long paragraph if end of paragraph does not "coincide" with end of page (text area).
How can I deal with it?
This error occurs when ReportLab try to split a Spacer over two pages. It seems that the only way to workaround this issue is wrap your Spacer into a KeepTogether element:
elements.append(KeepTogether(Spacer(width, height)))
Solved. Don't use Spacer (e.g. Spacer(1, 0.2*cm)) as a separator for Paragraph. Instead, define spaceBefore and spaceAfter in ParagraphStyle, for example:
ParagraphStyle(name = 'Normal',
fontName = "Verdana",
fontSize = 11,
leading = 15,
alignment = TA_JUSTIFY,
allowOrphans = 0,
spaceBefore = 20,
spaceAfter = 20,
wordWrap = 1)

Python Reportlab PDF - Centering Text on page

I am using ReportLab to generate a pdf dynamically with python.
I would like a line of text to be centered on a page. Here is the specific code I currently have, but do not know how to center the text horizontally.
header = p.beginText(190, 740)
header.textOut("Title of Page Here")
# I know i can use TextLine etc in place of textOut
p.drawText(header)
The text displays and I can manually move the left position so the text looks centered, but I need this to be centered programmatically since the text will be dynamic and I don't know how much text there will be.
The reportlab canvas has a drawCentredString method. And yes, they spell it like that.
We’re British, dammit, and proud of
our spelling!
Edit:
As for text objects, I'm afraid you don't. You can do something along those lines, though:
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.rl_config import defaultPageSize
PAGE_WIDTH = defaultPageSize[0]
PAGE_HEIGHT = defaultPageSize[1]
text = "foobar foobar foobar"
text_width = stringWidth(text)
y = 1050 # wherever you want your text to appear
pdf_text_object = canvas.beginText((PAGE_WIDTH - text_width) / 2.0, y)
pdf_text_object.textOut(text) # or: pdf_text_object.textLine(text) etc.
You can use other page sizes, obviously.
I just needed this too, and wrote this:
def createTextObject(canv, x, y, text, style, centered=False):
font = (style.fontName, style.fontSize, style.leading)
lines = text.split("\n")
offsets = []
if centered:
maxwidth = 0
for line in lines:
offsets.append(canv.stringWidth(line, *font[:2]))
maxwidth = max(*offsets)
offsets = [(maxwidth - i)/2 for i in offsets]
else:
offsets = [0] * len(lines)
tx = canv.beginText(x, y)
tx.setFont(*font)
for offset, line in zip(offsets, lines):
tx.setXPos(offset)
tx.textLine(line)
tx.setXPos(-offset)
return tx
You can use Flowable object like Paragraph and assign alignment value to 1:
styles = getSampleStyleSheet()
title_style = styles['Heading1']
title_style.alignment = 1
title = Paragraph("Hello Reportlab", title_style)
story.append(title)
This example will create a pdf document with centered text:
from flask import make_response
import io
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
story=[]
pdf_doc = io.BytesIO()
doc = SimpleDocTemplate(pdf_doc)
styles = getSampleStyleSheet()
title_style = styles['Heading1']
title_style.alignment = 1
title = Paragraph("Hello Reportlab", title_style)
story.append(title)
doc.build(story)
content = pdf_doc.getvalue()
#output to browser
response = make_response(content)
response.mimetype = 'application/pdf'
return response
If you want the text to be floaten to the left, you need to change alignment to 0:
title_style.alignment = 0
If you want the text to be floaten to the right, you need to change alignment to 2:
title_style.alignment = 2
Try:
<para alignment="center">
As Per Reference : http://two.pairlist.net/pipermail/reportlab-users/2006-June/005092.html
In your case:
header.textOut("<"para alignment='center'>"Title of Page Here")
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
doc = SimpleDocTemplate("form_letter.pdf",pagesize=letter,
rightMargin=72,leftMargin=72,
topMargin=72,bottomMargin=18)
Story = []
styles = getSampleStyleSheet()
title_style = styles['Heading1']
title_style.alignment = 1
title = Paragraph("Welcome to india", title_style)
Story.append(title)
doc.build(Story)

Categories

Resources