依赖
关于依赖包的安装请看文档
docxtpl
文档地址: https://docxtpl.readthedocs.io/en/latest/
注:它的github中有很多example实例,建议下载该项目在本地运行学习各种使用场景。plotly
文档地址: https://plotly.com/python/
注: 导出静态图像需要依赖安装kaleido包,直接pip安装即可,具体内容可在plotly文档中查看到。
- Jinja2
它的文档就不放了...
这里我再对依赖的所有库和版本做一个汇总,避免版本之间不兼容的问题:
- docxtpl==0.11.2
- kaleido==0.1.0
- plotly==4.14.1
- jinja2==2.10.3
- python-docx==0.8.10
需求场景分析
最近有个新的接口需求,需要后端提供一个报表导出接口,该接口可以导出两种内容形式的word文档,文档内包含有图表数据:统计表格以及数据分析的折线图、柱状图以及饼图。
由于word的导出内容,其格式及描述信息基本是固定的,只有数据和图表会动态变化,因此调研后决定使用docxtpl
库+Jinja2
的方式使用模板语法对制作好的word模板文档进行内容填充生成最终文件,docxtpl库是依赖python-docx
实现的,可以在制作好的模板文件中通过jinja2渲染字典,插入文字、表格、图片等数据。
虽然我们平时使用word的时候可以根据数据生成图表,但是python-docx
库本身是没有生成图表的API接口的,图表数据只能插入静态图像的方式来实现。经过调研和测试,最终选用了plotly
这个包来进行图表生成和图像文件导出。
网上很多方案是用的pyecharts
+pyecharts-snapshot
的方式,实际是采用无头浏览器渲染截图后得到的静态图像文件,实际测试中导出图片效率相对太低,且依赖node环境phantomjs,使用体验也不佳,不推荐。
准备模板docx文档
先使用jinja2
制作好作为基础模板的word文档,貌似不能上传文件,我这里就简单截图示例吧,下图为示例docx文件中编辑的模板内容,已经包含了我实际项目中用到的填充方式:普通填充、表格循环填充、条件判断内容块,这里都是使用jinja2的语法,对jinja2不熟悉可以大概先找文档看下这个模板语法的使用方式。
plotly图表静态文件生成代码示例
这里只是简单展示一下使用plotly生成本地文件或者将静态文件数据写入ByteIO,其他图表相关的API请参考文档,官方文档非常详细。
import plotly.graph_objects as go
from io import BytesIO
def get_line_image(x, y):
fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=y, text=y, textposition="top center", mode="lines+markers"))
image_io = BytesIO()
fig.write_image(image_io, format="jpeg")
return image_io
由于我后端并不需要图像文件生成到本地,因此只需要将文件对象写入到BytesIO返回即可,
你也可以根据需求直接write_image(file="a.png")
的方式生成图片及返回本地文件路径。
docxtpl模板库的使用示例
最终我们要将组装的填充数据写入到docx文档中,生成需要的word文档。
还是直接上示例代码吧。
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm
# 初始化模板处理对象
tpl = DocxTemplate("./示例模板文件.docx")
# 图表静态文件对象调用上面的示例plotly方法生成
line_charts = get_line_image(x=["一班", "二班", "三班"], y=["75", "80", "82"])
# 待填充的示例字典数据,其中key对应word模板中的填充名,图片需要调用InlineImage类
context = {
"average": 80,
"class": "三年2班",
"table": [{"name": "小红", "gender": "男", "grade": 80},
{"name": "小白", "gender": "男", "grade": 79},
{"name": "小黑", "gender": "女", "grade": 81}],
"line_image": InlineImage(tpl, line_charts, width=Mm(164), height=Mm(82.5)),
}
# 开始渲染context数据到模板文件中
tpl.render(context=context)
# 保存:这里依然可以写入的ByteIO对象,因为后端不需要保存文件,你也依然可以根据需要使用save('a.docx')的方式保存最终文件到本地
# file_io = BytesIO()
tpl.save("示例结果文档.docx")
一下是上述示例的输出结果截图:
最后
只要参照以上跑通了一次,就能根据实际需求重新设计代码结构以及调整模板文件格式、图片大小等,基本能实现模板类的word文档导出需求,若有任何问题和建议,也欢迎大家留言,希望这篇文档能帮到你。