惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
博客园 - 司徒正美
C
Check Point Blog
G
Google Developers Blog
The GitHub Blog
The GitHub Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
有赞技术团队
有赞技术团队
P
Proofpoint News Feed
IT之家
IT之家
B
Blog
博客园_首页
量子位
MongoDB | Blog
MongoDB | Blog
博客园 - Franky
J
Java Code Geeks
H
Help Net Security
A
About on SuperTechFans
Apple Machine Learning Research
Apple Machine Learning Research
Jina AI
Jina AI
D
DataBreaches.Net
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
Google DeepMind News
Google DeepMind News

又见苍岚

COLMAP PatchMatch Stereo 算法详解 事件驱动的状态机框架:从理论到工程实践 Git 在国内网络环境下无法 Push 的排查与修复 —— 配置 Clash 代理 分段五次多项式插值原理详解 路径插值方法深度对比研究 Claude Code 使用指南 OpenClaw 记忆管理与技能创建指南 CBS(Conflict-Based Search)算法详解 A* 算法及其变种详解 OpenClaw 配置多 Agents Windows Powershell 无法加载文件,因为在此系统上禁止运行脚本问题的解决方案 MaxClaw 安装流程 大模型 AI 名词介绍 AList 网盘聚合工具简介 Protobuf 简介与测试 Claude Code 简介以及 GLM 4.7 模型接入 Github 歌词下载工具 163MusicLyrics Python __getattr__ 懒加载 Python TypedDict 机器人仿真平台 Gazebo 安装记录 机器人仿真平台 Gazebo 简介 多机器人路径规划问题(Multi-Agent Path Finding, MAPF)简介 Python exifread 读取修改过的 jpeg 信息错误问题修复 3D 坐标系变换的理解 3D 旋转矩阵基本概念 MongoDB Compass 介绍 Python 环境管理工具 uv Flutter 开发指南 Snipaste 安装下载与黑屏问题解决方案 全局路径规划算法记录
Python 利用 docxtpl 和 Jinja2 生成基于模板的 Word 文档
Yiwei Zhang · 2025-08-13 · via 又见苍岚

我们需要创建一个包含占位符的Word文档模板 (docs 格式)。这些占位符将在后续的文档生成过程中被实际内容替换。使用 Jinja2 的模板语法,我们可以定义占位符和可替换的内容。例如,可以使用 {{ name }} 表示一个占位符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
context = {
'title': '项目报告',
'user': {
'name': '张三',
'email': 'zhangsan@example.com'
},
'tasks': [
{'description': '需求分析', 'deadline': '2023-12-31'},
{'description': '代码开发', 'deadline': '2024-01-15'}
],
'special_note': '请尽快完成初稿评审',
'name': '项目经理',
'date': '2025-08-04',
'employees': [
{'name': '张三', 'department': '研发部', 'hire_date': '2020-01-15', 'salary': 15000},
{'name': '李四', 'department': '市场部', 'hire_date': '2019-05-20', 'salary': 12000},
{'name': '王五', 'department': '财务部', 'hire_date': '2021-03-10', 'salary': 18000},
],
"node_config": {
"ip1": {
"check_hostnamectl": {
"hostname": "node01",
"operating_system": "Tencent tlinux 2.6",
"kernel": "Linux 5.4.119-1-tlinux4-0010",
"architecture": "x86-64"
},
"check_cpu_metrics": {
"cpu_num": "4C",
"model_name": "Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz"
},
"check_physical_cpu": {
"physical_cpu": "4C"
},
"check_physical_mem": {
"physical_mem": "8G"
},
"check_nvme": {
"nvme_size": "1.80TB*4"
}
},
"ip2": {
"check_hostnamectl": {
"hostname": "node02",
"operating_system": "Tencent tlinux 2.6",
"kernel": "Linux 5.4.119-1-tlinux4-0010",
"architecture": "x86-64"
},
"check_cpu_metrics": {
"cpu_num": "4C",
"model_name": "Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz"
},
"check_physical_cpu": {
"physical_cpu": "4C"
},
"check_physical_mem": {
"physical_mem": "8G"
},
"check_nvme": {
"nvme_size": "1.80TB*4"
}
},
"ip3": {
"check_hostnamectl": {
"hostname": "node03",
"operating_system": "Tencent tlinux 2.6",
"kernel": "Linux 5.4.119-1-tlinux4-0010",
"architecture": "x86-64"
},
"check_cpu_metrics": {
"cpu_num": "4C",
"model_name": "Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz"
},
"check_physical_cpu": {
"physical_cpu": "4C"
},
"check_physical_mem": {
"physical_mem": "8G"
},
"check_nvme": {
"nvme_size": "1.80TB*4"
}
}
},
"test_vm": "AAA"
}

首先,我们需要加载模板文件并创建一个DocxTemplate对象。然后,我们将数据传递给模板对象,使用render方法渲染文档。最后,可以选择将文档保存到本地文件或直接进行下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Pt
import os

# 数据准备
context = {
'title': '项目报告',
'user': {
'name': '张三',
'email': 'zhangsan@example.com'
},
'tasks': [
{'description': '需求分析', 'deadline': '2023-12-31'},
{'description': '代码开发', 'deadline': '2024-01-15'}
],
'special_note': '请尽快完成初稿评审',
'name': '项目经理',
'date': '2025-08-04',
'employees': [
{'name': '张三', 'department': '研发部', 'hire_date': '2020-01-15', 'salary': 15000},
{'name': '李四', 'department': '市场部', 'hire_date': '2019-05-20', 'salary': 12000},
{'name': '王五', 'department': '财务部', 'hire_date': '2021-03-10', 'salary': 18000},
],
"node_config": {
"ip1": {
"check_hostnamectl": {
"hostname": "node01",
"operating_system": "Tencent tlinux 2.6",
"kernel": "Linux 5.4.119-1-tlinux4-0010",
"architecture": "x86-64"
},
"check_cpu_metrics": {
"cpu_num": "4C",
"model_name": "Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz"
},
"check_physical_cpu": {
"physical_cpu": "4C"
},
"check_physical_mem": {
"physical_mem": "8G"
},
"check_nvme": {
"nvme_size": "1.80TB*4"
}
},
"ip2": {
"check_hostnamectl": {
"hostname": "node02",
"operating_system": "Tencent tlinux 2.6",
"kernel": "Linux 5.4.119-1-tlinux4-0010",
"architecture": "x86-64"
},
"check_cpu_metrics": {
"cpu_num": "4C",
"model_name": "Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz"
},
"check_physical_cpu": {
"physical_cpu": "4C"
},
"check_physical_mem": {
"physical_mem": "8G"
},
"check_nvme": {
"nvme_size": "1.80TB*4"
}
},
"ip3": {
"check_hostnamectl": {
"hostname": "node03",
"operating_system": "Tencent tlinux 2.6",
"kernel": "Linux 5.4.119-1-tlinux4-0010",
"architecture": "x86-64"
},
"check_cpu_metrics": {
"cpu_num": "4C",
"model_name": "Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz"
},
"check_physical_cpu": {
"physical_cpu": "4C"
},
"check_physical_mem": {
"physical_mem": "8G"
},
"check_nvme": {
"nvme_size": "1.80TB*4"
}
}
},
"test_vm": "AAA"
}

# 加载模板
doc = DocxTemplate("template.docx")

# 创建InlineImage对象并添加到context
context['my_image'] = InlineImage(doc, 'test.png', width=Pt(100)) # Pt(100) 表示一个 100 磅 的长度。
# 在排版和印刷领域,1 磅 (Pt) = 1/72 英寸 ≈ 0.0353 厘米 ≈ 0.3527 毫米 1 英寸 = 2.54 厘米
context['my_image_2'] = InlineImage(doc, 'test.png', width=Pt(300))

# 渲染Word文档
doc.render(context)
output_docx = "output.docx"
doc.save(output_docx)

# 转换为PDF
def convert_to_pdf(docx_path):
pdf_path = docx_path.replace('.docx', '.pdf')
cmd = f"libreoffice --headless --convert-to pdf {docx_path} --outdir ."
os.system(cmd)
return pdf_path

pdf_output = convert_to_pdf(output_docx)
print(f"PDF生成成功: {pdf_output}")

1
2
数字列表的总和是:{{ numbers | sum }}
总支出:{{ expenses | sum(attribute='amount') }}
1
2
3
4
5
6
{% set ns = namespace(total=0) %}
{% for expense in expenses %}
{% set ns.total = ns.total + expense.amount %}
当前累计支出:{{ ns.total }}
{% endfor %}
总支出:{{ ns.total }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

公式运算示例:
a = {{a}}, b = {{b}}

算术运算
◦ +:加法 {{ a + b }}
◦ -:减法 {{ a - b }}
◦ *:乘法 {{ a * b }}
◦ /:除法 {{ a / b }} (结果是浮点数)
◦ //:整除 {{ a // b }} (结果是整数)
◦ %:取模 {{ a % b }}
◦ **:幂运算 {{ a ** 2 }}

==:等于 {{a==b}}
• !=:不等于 {{a!=b}}
• >:大于 {{a>b}}
• >=:大于等于{{a>=b}}
• <:小于 {{a<b}}
• <=:小于等于 {{a<=b}}

逻辑运算 (常用于 if 语句)
• and {{a and b}}
• or {{a or b}}
• not {{not a}}
• ( ):用于改变优先级 {{(a + b) * b}}

字符串连接: {{ s1 + " ----- " + s2 }}
列表/元组索引: {{ my_list[0] }}
• 字典键值访问:
◦ {{ my_dict['key'] }}
◦ 或 {{ my_dict.key }} (如果键是有效的标识符)
• 获取属性: {{ obj.test_attr }}
• 函数: {{ first | get_full_name(last) }}
• 三元表达式: {{ "Yes" if a > 1 else "No" }}

百分比表示:{{ a, b | my_filterA }}

多元素求和

1. 内置过滤器
数字列表的总和是:{{ numbers | sum }}
总支出:{{ expenses | sum(attribute='amount') }}

2. for 循环自定义变量
{% set ns = namespace(total=0) %}
{% for expense in expenses %}
{% set ns.total = ns.total + expense.amount %}
当前累计支出:{{ ns.total }}
{% endfor %}
总支出:{{ ns.total }}

3. 自定义过滤器
所有支出总和:{{ expenses | sum_if('amount') }}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Pt
import os
import jinja2

# 添加全局函数而不是过滤器
def calculate_percentage_vvd(a):
return f"{(a ) * 100:.2f}%"

class Test:
pass

test_obj = Test()
test_obj.test_attr = "test_attr"

# 自定义过滤器:条件求和
def sum_if(items, attribute=None, condition=None):
total = 0
for item in items:
value = item[attribute] if attribute else item
# 如果没有条件或者条件满足,则累加
if condition is None or condition(item):
total += value
return total

# 数据准备
context = {
"test_vm": "AAA",
"a": 1,
"b": 4,
"s1": 'test_str1',
"s2": 'test_str2',
"my_list": [1, 2, 3, 4, 5],
"my_dict": {
"key": "dict_key",
},
"obj": test_obj,
"first": "John",
"last": "Doe",
'numbers': [10, 20, 30, 40, 50],
'expenses': [
{'amount': 100, 'category': 'food'},
{'amount': 200, 'category': 'transport'},
{'amount': 150, 'category': 'entertainment'},
]
}

def get_full_name(first, last):
return f"{first} {last}"

jinja_env = jinja2.Environment()
jinja_env.filters['my_filterA'] = calculate_percentage_vvd
jinja_env.filters['get_full_name'] = get_full_name
jinja_env.filters['sum_if'] = sum_if

# 加载模板
doc = DocxTemplate("template_plus.docx")

# 创建InlineImage对象并添加到context
context['my_image'] = InlineImage(doc, 'test.jpg', width=Pt(100))
context['my_image_2'] = InlineImage(doc, 'test.jpg', width=Pt(300))

# 渲染Word文档
doc.render(context, jinja_env)
output_docx = "output.docx"
doc.save(output_docx)

# 转换为PDF
def convert_to_pdf(docx_path):
pdf_path = docx_path.replace('.docx', '.pdf')
cmd = f"libreoffice --headless --convert-to pdf {docx_path} --outdir ."
os.system(cmd)
return pdf_path

pdf_output = convert_to_pdf(output_docx)
print(f"PDF生成成功: {pdf_output}")