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

如何解决Python Reportlab 划分表格以适应不同的页面

我正在尝试使用 ReportLab 生成的 PDF 文件构建日程计划器。根据一天中的时间,日程安排会有不同的行:从早上 8:00、早上 8:15、早上 8:30 开始,依此类推。

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

期望的结果是当表格有大约 20 个活动时有一个分页符。在下一页,标题应该与第一页和下面的表格完全相同,表格的延续。每次需要时都应重复该过程,直到表结束。

enter image description here

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,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,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 += 1

    table_actividades = Table(data_actividades,colWidths=85,rowHeights=30,repeatRows=1)
    tblStyle = TableStyle([
        ('BACKGROUND',(0,0),(-1,title_background),('TEXTCOLOR',colors.whitesmoke),('ALIGN',(1,-1),'CENTER'),('GRID',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',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',]

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

解决方法

您应该使用模板,如官方文档第 5 章“PLATYPUS - 页面布局和排版使用脚本”中所述。

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

我的建议:

对于您的情况,我使用了两个模板,第一个模板包含带有工作表信息的标题和表格的第一部分,另一个模板对应于其余内容。这些模板的名字是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,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,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,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,height-2*inch,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 += 1

    table_actividades = Table(data_actividades,colWidths=85,rowHeights=30,repeatRows=1)
    tblStyle = TableStyle([
        ('BACKGROUND',(0,0),(-1,title_background),('TEXTCOLOR',colors.whitesmoke),('ALIGN',(1,-1),'CENTER'),('GRID',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',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页面的大小。

enter image description here enter image description here

额外,在每个页面上添加标题

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

canvas = Canvas(file_name,id='normal'
    )

frame_table= Frame(
    0.2*inch,id='col'
    )

laterpages = PageTemplate(id='laterpages',)

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

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],table_background)

    table_actividades.setStyle(tblStyle)

    if ((count >= 20) or (i== size) ):
        count = 0
        table_group.append(table_actividades)
        data_actividades = [
            {'Hora',]
    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)

额外 - 结果:

enter image description here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res