简体   繁体   中英

Python Reportlab divide table to fit into different pages

I am trying to build a schedule planner, in a PDF file generated with ReportLab. The schedule will have a different rows depending on the hour of the day: starting with 8:00 am, 8:15 am, 8:30 am, and so on.

I made a loop in which the hours will be calculated automatically and the schedule will be filled. However, since my table is too long, it doesn't fit completely in the page. (Although the schedule should end on 7:30 pm, it is cutted at 2:00 pm)

The desired result is to have a PageBreak when the table is at around 20 activities. On the next page, the header should be exactly the same as in the first page and below, the continuation of the table. The process should repeat every time it is necessary, until the end of the table.

在此处输入图像描述

The Python code is the following:

from reportlab.pdfgen.canvas import Canvas
from datetime import datetime, timedelta
from reportlab.platypus import Table, TableStyle
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter, landscape


class Vendedor:
    """
    Información del Vendedor: Nombre, sucursal, meta de venta
    """
    def __init__(self, nombre_vendedor, sucursal, dia_reporte):
        self.nombre_vendedor = nombre_vendedor
        self.sucursal = sucursal
        self.dia_reporte = dia_reporte


class Actividades:
    """
    Información de las Actividades realizadas: Hora de actividad y duración, cliente atendido,
    tipo de actividad, resultado, monto venta (mxn) + (usd), monto cotización (mxn) + (usd),
    solicitud de apoyo y comentarios adicionales
    """
    def __init__(self, hora_actividad, duracion_actividad, cliente, tipo_actividad, resultado,
                 monto_venta_mxn, monto_venta_usd, monto_cot_mxn, monto_cot_usd, requiero_apoyo, comentarios_extra):
        self.hora_actividad = hora_actividad
        self.duracion_actividad = duracion_actividad
        self.cliente = cliente
        self.tipo_actividad = tipo_actividad
        self.resultado = resultado
        self.monto_venta_mxn = monto_venta_mxn
        self.monto_venta_usd = monto_venta_usd
        self.monto_cot_mxn = monto_cot_mxn
        self.monto_cot_usd = monto_cot_usd
        self.requiero_apoyo = requiero_apoyo
        self.comentarios_extra = comentarios_extra


class PDFReport:
    """
    Crea el Reporte de Actividades diarias en archivo de formato PDF
    """
    def __init__(self, filename):
        self.filename = filename


vendedor = Vendedor('John Doe', 'Stack Overflow', datetime.now().strftime('%d/%m/%Y'))

file_name = 'cronograma_actividades.pdf'
document_title = 'Cronograma Diario de Actividades'
title = 'Cronograma Diario de Actividades'
nombre_colaborador = vendedor.nombre_vendedor
sucursal_colaborador = vendedor.sucursal
fecha_actual = vendedor.dia_reporte


canvas = Canvas(file_name)
canvas.setPageSize(landscape(letter))
canvas.setTitle(document_title)


canvas.setFont("Helvetica-Bold", 20)
canvas.drawCentredString(385+100, 805-250, title)
canvas.setFont("Helvetica", 16)
canvas.drawCentredString(385+100, 785-250, nombre_colaborador + ' - ' + sucursal_colaborador)
canvas.setFont("Helvetica", 14)
canvas.drawCentredString(385+100, 765-250, fecha_actual)

title_background = colors.fidblue
hour = 8
minute = 0
hour_list = []

data_actividades = [
    {'Hora', 'Cliente', 'Resultado de \nActividad', 'Monto Venta \n(MXN)', 'Monto Venta \n(USD)',
     'Monto Cotización \n(MXN)', 'Monto Cotización \n(USD)', 'Comentarios \nAdicionales'},
]

i = 0
for i in range(47):

    if minute == 0:
        if hour <= 12:
            time = str(hour) + ':' + str(minute) + '0 a.m.'
        else:
            time = str(hour-12) + ':' + str(minute) + '0 p.m.'
    else:
        if hour <= 12:
            time = str(hour) + ':' + str(minute) + ' a.m.'
        else:
            time = str(hour-12) + ':' + str(minute) + ' p.m.'

    if minute != 45:
        minute += 15
    else:
        hour += 1
        minute = 0
    hour_list.append(time)

    # I TRIED THIS SOLUTION BUT THIS DIDN'T WORK
    # if i % 20 == 0:
    #     canvas.showPage()

    data_actividades.append([hour_list[i], i, i, i, i, i, i, i])

    i += 1

    table_actividades = Table(data_actividades, colWidths=85, rowHeights=30, repeatRows=1)
    tblStyle = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), title_background),
        ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
        ('ALIGN', (1, 0), (1, -1), 'CENTER'),
        ('GRID', (0, 0), (-1, -1), 1, colors.black)
    ])

    rowNumb = len(data_actividades)
    for row in range(1, rowNumb):
        if row % 2 == 0:
            table_background = colors.lightblue
        else:
            table_background = colors.aliceblue

        tblStyle.add('BACKGROUND', (0, row), (-1, row), table_background)

    table_actividades.setStyle(tblStyle)

    width = 150
    height = 150
    table_actividades.wrapOn(canvas, width, height)
    table_actividades.drawOn(canvas, 65, (0 - height) - 240)

canvas.save()

I tried by adding:

if i % 20 == 0:
    canvas.showPage()

However this failed to achieve the desired result.

Other quick note: Although I specifically coded the column titles of the table. Once I run the program, the order of the column titles is modified for some reason (see the pasted image). Any idea of why this is happening?

data_actividades = [
    {'Hora', 'Cliente', 'Resultado de \nActividad', 'Monto Venta \n(MXN)', 'Monto Venta \n(USD)',
     'Monto Cotización \n(MXN)', 'Monto Cotización \n(USD)', 'Comentarios \nAdicionales'},
]

Thank you very much in advance, have a great day!

You should use templates, as suggested in the Chapter 5 "PLATYPUS - Page Layout and TypographyUsing Scripts" of the official documentation.

The basic idea is to use frames, and add to a list element all the information you want to add. In my case I call it "contents", with the command " contents.append(FrameBreak()) " you leave the frame and work on the next one, on the other hand if you want to change the type of template you use the command " contents.append(NextPageTemplate('<template_name>')) "

My proposal:

For your case I used two templates, the first one is the one that contains the header with the sheet information and the first part of the table, and the other template corresponds to the rest of the content. The name of these templates is firstpage and laterpage.The code is as follows:

from reportlab.pdfgen.canvas import Canvas
from datetime import datetime, timedelta
from reportlab.platypus import Table, TableStyle
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter, landscape
from reportlab.platypus import BaseDocTemplate, Frame, Paragraph, PageBreak, \
    PageTemplate, Spacer, FrameBreak, NextPageTemplate, Image
from reportlab.lib.pagesizes import letter,A4
from reportlab.lib.units import inch, cm
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER,TA_LEFT,TA_RIGHT
class Vendedor:
    """
    Información del Vendedor: Nombre, sucursal, meta de venta
    """
    def __init__(self, nombre_vendedor, sucursal, dia_reporte):
        self.nombre_vendedor = nombre_vendedor
        self.sucursal = sucursal
        self.dia_reporte = dia_reporte


class Actividades:
    """
    Información de las Actividades realizadas: Hora de actividad y duración, cliente atendido,
    tipo de actividad, resultado, monto venta (mxn) + (usd), monto cotización (mxn) + (usd),
    solicitud de apoyo y comentarios adicionales
    """
    def __init__(self, hora_actividad, duracion_actividad, cliente, tipo_actividad, resultado,
                 monto_venta_mxn, monto_venta_usd, monto_cot_mxn, monto_cot_usd, requiero_apoyo, comentarios_extra):
        self.hora_actividad = hora_actividad
        self.duracion_actividad = duracion_actividad
        self.cliente = cliente
        self.tipo_actividad = tipo_actividad
        self.resultado = resultado
        self.monto_venta_mxn = monto_venta_mxn
        self.monto_venta_usd = monto_venta_usd
        self.monto_cot_mxn = monto_cot_mxn
        self.monto_cot_usd = monto_cot_usd
        self.requiero_apoyo = requiero_apoyo
        self.comentarios_extra = comentarios_extra

class PDFReport:
    """
    Crea el Reporte de Actividades diarias en archivo de formato PDF
    """
    def __init__(self, filename):
        self.filename = filename


vendedor = Vendedor('John Doe', 'Stack Overflow', datetime.now().strftime('%d/%m/%Y'))

file_name = 'cronograma_actividades.pdf'
document_title = 'Cronograma Diario de Actividades'
title = 'Cronograma Diario de Actividades'
nombre_colaborador = vendedor.nombre_vendedor
sucursal_colaborador = vendedor.sucursal
fecha_actual = vendedor.dia_reporte


canvas = Canvas(file_name, pagesize=landscape(letter))

doc = BaseDocTemplate(file_name)
contents =[]
width,height = A4

left_header_frame = Frame(
    0.2*inch, 
    height-1.2*inch, 
    2*inch, 
    1*inch
    )

right_header_frame = Frame(
    2.2*inch, 
    height-1.2*inch, 
    width-2.5*inch, 
    1*inch,id='normal'
    )

frame_later = Frame(
    0.2*inch, 
    0.6*inch, 
    (width-0.6*inch)+0.17*inch, 
    height-1*inch,
    leftPadding = 0, 
    topPadding=0, 
    showBoundary = 1,
    id='col'
    )

frame_table= Frame(
    0.2*inch, 
    0.7*inch, 
    (width-0.6*inch)+0.17*inch, 
    height-2*inch,
    leftPadding = 0, 
    topPadding=0, 
    showBoundary = 1,
    id='col'
    )
laterpages = PageTemplate(id='laterpages',frames=[frame_later])

firstpage = PageTemplate(id='firstpage',frames=[left_header_frame, right_header_frame,frame_table],)

contents.append(NextPageTemplate('firstpage'))
logoleft = Image('logo_power.png')
logoleft._restrictSize(1.5*inch, 1.5*inch)
logoleft.hAlign = 'CENTER'
logoleft.vAlign = 'CENTER'

contents.append(logoleft)
contents.append(FrameBreak())
styleSheet = getSampleStyleSheet()
style_title = styleSheet['Heading1']
style_title.fontSize = 20 
style_title.fontName = 'Helvetica-Bold'
style_title.alignment=TA_CENTER

style_data = styleSheet['Normal']
style_data.fontSize = 16 
style_data.fontName = 'Helvetica'
style_data.alignment=TA_CENTER

style_date = styleSheet['Normal']
style_date.fontSize = 14
style_date.fontName = 'Helvetica'
style_date.alignment=TA_CENTER

canvas.setTitle(document_title)

contents.append(Paragraph(title, style_title))
contents.append(Paragraph(nombre_colaborador + ' - ' + sucursal_colaborador, style_data))
contents.append(Paragraph(fecha_actual, style_date))
contents.append(FrameBreak())

title_background = colors.fidblue
hour = 8
minute = 0
hour_list = []

data_actividades = [
    {'Hora', 'Cliente', 'Resultado de \nActividad', 'Monto Venta \n(MXN)', 'Monto Venta \n(USD)',
     'Monto Cotización \n(MXN)', 'Monto Cotización \n(USD)', 'Comentarios \nAdicionales'},
]

i = 0
for i in range(300):

    if minute == 0:
        if hour <= 12:
            time = str(hour) + ':' + str(minute) + '0 a.m.'
        else:
            time = str(hour-12) + ':' + str(minute) + '0 p.m.'
    else:
        if hour <= 12:
            time = str(hour) + ':' + str(minute) + ' a.m.'
        else:
            time = str(hour-12) + ':' + str(minute) + ' p.m.'

    if minute != 45:
        minute += 15
    else:
        hour += 1
        minute = 0
    hour_list.append(time)

    # I TRIED THIS SOLUTION BUT THIS DIDN'T WORK
    # if i % 20 == 0:
    

    data_actividades.append([hour_list[i], i, i, i, i, i, i, i])

    i += 1

    table_actividades = Table(data_actividades, colWidths=85, rowHeights=30, repeatRows=1)
    tblStyle = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), title_background),
        ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
        ('ALIGN', (1, 0), (1, -1), 'CENTER'),
        ('GRID', (0, 0), (-1, -1), 1, colors.black)
    ])

    rowNumb = len(data_actividades)
    for row in range(1, rowNumb):
        if row % 2 == 0:
            table_background = colors.lightblue
        else:
            table_background = colors.aliceblue

        tblStyle.add('BACKGROUND', (0, row), (-1, row), table_background)

    table_actividades.setStyle(tblStyle)

    width = 150
    height = 150
    
contents.append(NextPageTemplate('laterpages'))
contents.append(table_actividades)


contents.append(PageBreak())


doc.addPageTemplates([firstpage,laterpages])
doc.build(contents)

Results

With this you can add as many records as you want, I tried with 300. The table is not fully visible because for my convenience I made an A4 size pdf. However, the principle is the same for any size so you must play with the size of the frames and the size of the pdf page.

在此处输入图像描述 在此处输入图像描述

EXTRA, add header on each page

since only one template will be needed now, the "first_page" template should be removed since it will be the same for all pages. In the same way that you proposed in the beginning I cut the table every 21 records (to include the header of the table) and it is grouped in a list that then iterates adding the header with the logo in each cycle. Also it is included in the logical cutting sentence, the case when the number of records does not reach 21 but the number of records is going to end. The code is as follows:

canvas = Canvas(file_name, pagesize=landscape(letter))

doc = BaseDocTemplate(file_name)
contents =[]
width,height = A4

left_header_frame = Frame(
    0.2*inch, 
    height-1.2*inch, 
    2*inch, 
    1*inch
    )

right_header_frame = Frame(
    2.2*inch, 
    height-1.2*inch, 
    width-2.5*inch, 
    1*inch,id='normal'
    )

frame_table= Frame(
    0.2*inch, 
    0.7*inch, 
    (width-0.6*inch)+0.17*inch, 
    height-2*inch,
    leftPadding = 0, 
    topPadding=0, 
    showBoundary = 1,
    id='col'
    )

laterpages = PageTemplate(id='laterpages',frames=[left_header_frame, right_header_frame,frame_table],)

logoleft = Image('logo_power.png')
logoleft._restrictSize(1.5*inch, 1.5*inch)
logoleft.hAlign = 'CENTER'
logoleft.vAlign = 'CENTER'


styleSheet = getSampleStyleSheet()
style_title = styleSheet['Heading1']
style_title.fontSize = 20 
style_title.fontName = 'Helvetica-Bold'
style_title.alignment=TA_CENTER

style_data = styleSheet['Normal']
style_data.fontSize = 16 
style_data.fontName = 'Helvetica'
style_data.alignment=TA_CENTER

style_date = styleSheet['Normal']
style_date.fontSize = 14
style_date.fontName = 'Helvetica'
style_date.alignment=TA_CENTER

canvas.setTitle(document_title)


title_background = colors.fidblue
hour = 8
minute = 0
hour_list = []

data_actividades = [
    {'Hora', 'Cliente', 'Resultado de \nActividad', 'Monto Venta \n(MXN)', 'Monto Venta \n(USD)',
     'Monto Cotización \n(MXN)', 'Monto Cotización \n(USD)', 'Comentarios \nAdicionales'},
]

i = 0
table_group= []
size = 304

count = 0
for i in range(size):

    if minute == 0:
        if hour <= 12:
            time = str(hour) + ':' + str(minute) + '0 a.m.'
        else:
            time = str(hour-12) + ':' + str(minute) + '0 p.m.'
    else:
        if hour <= 12:
            time = str(hour) + ':' + str(minute) + ' a.m.'
        else:
            time = str(hour-12) + ':' + str(minute) + ' p.m.'

    if minute != 45:
        minute += 15
    else:
        hour += 1
        minute = 0
    hour_list.append(time)    

    data_actividades.append([hour_list[i], i, i, i, i, i, i, i])

    i += 1

    table_actividades = Table(data_actividades, colWidths=85, rowHeights=30, repeatRows=1)
    tblStyle = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), title_background),
        ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
        ('ALIGN', (1, 0), (1, -1), 'CENTER'),
        ('GRID', (0, 0), (-1, -1), 1, colors.black)
    ])

    rowNumb = len(data_actividades)
    for row in range(1, rowNumb):
        if row % 2 == 0:
            table_background = colors.lightblue
        else:
            table_background = colors.aliceblue

        tblStyle.add('BACKGROUND', (0, row), (-1, row), table_background)

    table_actividades.setStyle(tblStyle)

    if ((count >= 20) or (i== size) ):
        count = 0
        table_group.append(table_actividades)
        data_actividades = [
            {'Hora', 'Cliente', 'Resultado de \nActividad', 'Monto Venta \n(MXN)', 'Monto Venta \n(USD)',
            'Monto Cotización \n(MXN)', 'Monto Cotización \n(USD)', 'Comentarios \nAdicionales'},]
    width = 150
    height = 150
    count += 1
    if i > size:

        break

contents.append(NextPageTemplate('laterpages'))

for table in table_group:

    contents.append(logoleft)
    contents.append(FrameBreak())
    contents.append(Paragraph(title, style_title))
    contents.append(Paragraph(nombre_colaborador + ' - ' + sucursal_colaborador, style_data))
    contents.append(Paragraph(fecha_actual, style_date))
    contents.append(FrameBreak()) 
    contents.append(table)
    contents.append(FrameBreak())

doc.addPageTemplates([laterpages,])
doc.build(contents)

Extra - result:

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM