Python Reportlab 分表以适应不同的页面

Posted

技术标签:

【中文标题】Python Reportlab 分表以适应不同的页面【英文标题】:Python Reportlab divide table to fit into different pages 【发布时间】:2021-05-27 06:39:58 【问题描述】:

我正在尝试在 ReportLab 生成的 PDF 文件中构建日程安排器。日程表将根据一天中的时间有不同的行:从上午 8:00、上午 8:15、上午 8:30 等开始。

我创建了一个循环,其中将自动计算小时数并填写时间表。但是,由于我的表格太长,它不完全适合页面。 (虽然时间表应该在下午 7:30 结束,但在下午 2:00 被截断)

期望的结果是在表大约有 20 个活动时有一个 PageBreak。在下一页上,页眉应该与第一页和下面的表的续页完全相同。该过程应在每次必要时重复,直到表格结束。

Python 代码如下:

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()

我尝试添加:

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

然而这并没有达到预期的效果。

其他快速说明:虽然我专门对表格的列标题进行了编码。运行程序后,由于某种原因,列标题的顺序会被修改(参见粘贴的图像)。知道为什么会这样吗?

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',
]

非常感谢您,祝您有美好的一天!

【问题讨论】:

查看这个答案:***.com/a/9287835/42346 关于列的顺序,正在发生重新排列,因为列表中有一个set。 Python 中的集合是无序的。您可以在 list 中使用 list 您好@mechanical_meat 感谢您的回复。虽然我添加了repeatRows=1。正如您建议的答案所建议的那样,我仍然无法获得 pdf 添加新页面。我编辑了我的代码以显示这一点。另一方面,我不太清楚您所说的“您在列表中有一个 ser”是什么意思。你能给我进一步的解释吗?提前非常感谢! 【参考方案1】:

您应该按照官方文档第 5 章“PLATYPUS - Page Layout and TypographyUsing Scripts”中的建议使用模板。

基本思想是使用框架,并将您想要添加的所有信息添加到列表元素中。在我的情况下,我称之为“内容”,使用命令“contents.append(FrameBreak())”,您可以离开框架并处理下一个框架,另一方面,如果您想更改模板类型,请使用命令“ contents.append(NextPageTemplate('&lt;template_name&gt;'))"

我的建议:

对于您的情况,我使用了两个模板,第一个模板包含带有工作表信息的标题和表格的第一部分,另一个模板对应于其余内容。这些模板的名字分别是firstpage和laterpage。代码如下:

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)

结果

有了它,您可以添加任意数量的记录,我尝试使用 300 条记录。该表格不完全可见,因为为方便起见,我制作了 A4 大小的 pdf。但是,任何尺寸的原理都是一样的,所以你必须玩转框架的大小和 pdf 页面的大小。

EXTRA,在每个页面上添加标题

由于现在只需要一个模板,因此应该删除“first_page”模板,因为它对于所有页面都是相同的。与您在开始时提出的相同方式,我每 21 条记录切割一次表(以包括表的标题)并将其分组在一个列表中,然后在每个循环中迭代添加带有徽标的标题。也包含在逻辑切割句中,即记录数未达到 21 但记录数即将结束的情况。代码如下:

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)

额外结果:

【讨论】:

感谢您的回复@Andrewgmz 我有最后一个问题要问您。如何在每个页面上添加页面标题(徽标、标题、nombre_colaborador、sucural_colaborador 等?(因此它在 pfd 的每个页面顶部重复。)提前非常感谢! @DiegoGc 我已经用你问我的问题编辑了答案。 非常感谢,您的回答正是我所需要的。然而,表格中的列标题是无序的。知道如何解决这个问题吗?再次非常感谢,给您带来的不便,我深表歉意。 您必须以您想要的方式订购变量“data_activities”,并考虑如何添加记录 data_actividades = [ ['Monto Cotización \n(USD)', 'Cliente','评论 \nAdicionales', 'Monto Cotización \n(MXN)', 'Monto Venta \n(USD)', 'Hora', 'Monto Venta \n(MXN)', 'Resultado de \nActividad'], ]跨度>

以上是关于Python Reportlab 分表以适应不同的页面的主要内容,如果未能解决你的问题,请参考以下文章

Python Reportlab 生成PDF文档

Python Reportlab 分页符

python reportlab简单的样板

python reportlab简单的样板

在 python django 服务 ubuntu 中安装 reportLab

Python:如何使 Reportlab 在 PDF 输出中移至下一页