Odoo 的报表和打印功能是其核心业务能力之一,允许用户将系统中的数据以结构化、美观的格式输出,通常是 PDF 或 HTML。Odoo 18 沿袭并优化了其成熟的报表架构,该架构主要依赖于 ir.actions.report
动作、QWeb 模板引擎以及外部的 wkhtmltopdf
工具(用于生成 PDF)。
在深入工作流程之前,先认识一下构成 Odoo 打印功能的几个关键组件:
ir.actions.report
(报表动作): 这是 Odoo 中定义一个报表行为的数据库记录。它将用户界面上的触发点(如按钮)与特定的报表逻辑、数据源模型、报表模板以及输出格式关联起来。它是整个报表生成流程的入口和协调者。ir.actions.report
指定了数据来源的模型,并且通常会调用该模型上的特定方法(如 _get_report_values
)来准备传递给报表模板的数据。wkhtmltopdf
: 一个独立的、开源的命令行工具,用于将 HTML 网页转换为 PDF 文档。Odoo 在生成 PDF 报表时,会将 QWeb 生成的 HTML 输出传递给 wkhtmltopdf
进行转换。report
模块: Odoo 的官方模块,提供了报表动作的处理逻辑、QWeb 报表渲染的基础结构以及与 wkhtmltopdf
集成的接口。从用户点击打印按钮到最终文件生成的完整流程可以分解为以下步骤:
或
。name
属性指向一个 ir.actions.report
的外部 ID 或数据库 ID。ir.actions.report
记录。ir.actions.report
):
ir.actions.report
记录的所有配置信息,包括:
report_type
(例如 qweb-pdf
, qweb-html
)model
(报表数据来源的主要模型)report_name
/ report_file
(报表的内部名称,常用于文件名或查找模板)template_id
(关联的 QWeb 模板视图 ir.ui.view
)paperformat_id
(关联的纸张格式 report.paperformat
,仅对 PDF 有效)binding_model_id
(动作绑定的模型)binding_type
(绑定类型,如 report
)groups_id
控制访问权限)。odoo.addons.report.models.report
中的逻辑)根据 ir.actions.report
中指定的 model
和用户选择的记录 ID (docids
),调用相应模型上的数据准备方法。_get_report_values(self, docids, data=None)
方法。docids
查询数据库,获取报表所需的所有数据(主记录、关联记录、计算字段等)。docs
,其值是用户选择的记录对象列表。也可以包含其他自定义数据。ir.actions.report
中指定的 QWeb 模板 (template_id
指向的 ir.ui.view
记录)。t-foreach="doc in docs"
),评估条件 (t-if="..."
),输出字段值 (t-field="..."
, t-esc="..."
),调用子模板 (t-call="..."
) 等。wkhtmltopdf
或直接返回 HTML):
report_type
是 qweb-pdf
:
paperformat_id
指定的纸张格式配置(页边距、纸张大小、页眉页脚等),以及其他可能的 wkhtmltopdf
参数,传递给 wkhtmltopdf
命令行工具。wkhtmltopdf
命令,将 HTML 作为输入,指定输出为 PDF 文件。wkhtmltopdf
进程运行,将 HTML 转换为 PDF 字节流。wkhtmltopdf
的输出(PDF 字节流)。report_type
是 qweb-html
:
Content-Type
头会设置为相应的 MIME 类型(例如 application/pdf
或 text/html
)。Content-Disposition
头,建议客户端如何处理文件(例如 attachment; filename="report.pdf"
表示下载)。Content-Type
和 Content-Disposition
头,浏览器决定如何处理文件:通常是直接在浏览器中打开 PDF,或者提示用户下载文件。+-------------------+ +-------------------+ +-----------------------+
| 1. 用户点击打印按钮 | --> | 2. 客户端发送RPC请求 | --> | 3. 服务器查找ir.actions.report |
| (Odoo Web UI) | | (触发动作) | | (根据ID/External ID) |
+-------------------+ +-------------------+ +-----------------------+
|
v
+-----------------------+ +-----------------------+ +-----------------------+
| 4. 加载报表动作配置 | --> | 5. 调用Python方法准备数据 | --> | 6. QWeb引擎渲染HTML模板 |
| (report_type, model, | | (_get_report_values) | | (结合数据与模板) |
| template_id, etc.) | +-----------------------+ +-----------------------+
| |
| v
| +-----------------------+
| | 7a. 如果是qweb-pdf: |
| | 调用wkhtmltopdf |
| | HTML -> PDF |
| +-----------------------+
| |
| +-----------------------+
| | 7b. 如果是qweb-html: |
| | 直接使用HTML |
| +-----------------------+
| |
+---------------------------------+
|
v
+-----------------------+ +-----------------------+
| 8. 服务器发送文件响应 | --> | 9. 客户端处理文件 |
| (PDF或HTML) | | (下载/预览) |
+-----------------------+ +-----------------------+
QWeb 是 Odoo 报表呈现层的核心。它的作用是将 Python 代码提供的数据,按照预定义的 HTML 结构和样式进行填充和排版,生成最终的 HTML 输出。
t-
开头的特殊属性(指令)。QWeb 引擎是一个解析器,它遍历模板树,识别这些指令,并根据指令和当前的数据上下文执行相应的操作。_get_report_values
) 返回的字典中的数据,在 QWeb 模板中可以通过变量名直接访问。例如,如果 Python 返回 {'docs': records, 'company': current_company}
,在 QWeb 中就可以使用 docs
和 company
变量。最常用的变量是 docs
,它包含了用户选择要打印的记录列表。在遍历 docs
时,通常使用 t-foreach="doc in docs"
,此时 doc
变量代表当前正在处理的记录。t-foreach="item in collection"
: 循环遍历集合。t-if="condition"
: 条件判断,如果条件为真则渲染其内容。t-field="record.field_name"
: 输出记录字段的值,并应用 Odoo 的格式化(如货币、日期)。t-esc="variable"
: 输出变量的值,进行 HTML 转义。t-raw="variable"
: 输出变量的值,不进行 HTML 转义(用于输出包含 HTML 标签的字符串)。t-att="attribute_name, value"
: 设置元素的属性。t-call="template_external_id"
: 调用另一个 QWeb 模板(常用于布局、页眉页脚)。t-set="variable_name, value"
: 在模板中设置一个局部变量。web.external_layout
) 和内部布局 (web.internal_layout
) 模板。报表模板通常会使用 t-call="web.external_layout"
来包含标准的页眉、页脚和公司信息,确保报表风格一致。web.html_container
是最外层的容器模板,提供基本的 HTML 结构。wkhtmltopdf
在 Odoo 报表生成中的角色、配置及其重要性wkhtmltopdf
是 Odoo 生成 PDF 报表不可或缺的外部依赖。
wkhtmltopdf
来完成这个复杂的任务。wkhtmltopdf
基于 WebKit 渲染引擎(与 Chrome/Safari 早期版本相似),能够较好地解析现代 HTML 和 CSS,生成视觉效果接近浏览器中显示的 PDF。wkhtmltopdf
支持通过命令行参数配置页眉、页脚、页码以及控制分页行为(尽管分页控制在 HTML/CSS 中实现起来可能比较复杂)。wkhtmltopdf
需要独立安装在运行 Odoo 服务器的机器上,并且 Odoo 用户需要有执行该命令的权限。wkhtmltopdf
可执行文件的路径。通常,如果它在系统的 PATH 环境变量中,Odoo 可以直接找到。否则,可能需要在 Odoo 配置中指定路径(尽管这不常见,更推荐加入 PATH)。report.url
: 这是一个非常重要的配置。Odoo 在调用 wkhtmltopdf
时,通常不是直接传递 HTML 字符串,而是启动一个临时的 HTTP 服务器(或使用主 Odoo 实例的 /report/html/
路由),让 wkhtmltopdf
通过 HTTP 请求获取 HTML 内容。report.url
系统参数(例如 http://localhost:8069)告诉 Odoo 报表模块 wkhtmltopdf
应该访问哪个 URL 来获取 HTML。这对于 wkhtmltopdf
正确加载 CSS、图片等相对路径资源至关重要。report.paperformat
): Odoo 中的 report.paperformat
模型允许用户配置纸张大小、方向、页边距、页眉页脚高度等。这些配置在生成 PDF 时会被 Odoo 报表模块读取,并作为命令行参数传递给 wkhtmltopdf
。wkhtmltopdf
的版本兼容性是一个常见问题,特别是页眉页脚的渲染。Odoo 官方通常推荐使用特定版本或经过 Odoo 补丁的版本,以确保最佳兼容性和稳定性。ir.actions.report
的配置、属性及其关联ir.actions.report
是 Odoo 报表架构的粘合剂,它定义了报表的所有元数据和行为。它是一个数据库模型 (ir.actions.report
) 的记录。
.xml
) 在模块中定义。
Sales Order
sale.order
qweb-pdf
sale.report_saleorder
sale.report_saleorder
report
name
(Char): 报表动作的显示名称。model
(Char): 报表数据来源的主要 Odoo 模型的技术名称(例如 'sale.order'
)。report_type
(Selection): 报表输出类型,最常见的是 'qweb-pdf'
和 'qweb-html'
。report_name
(Char): 报表的内部名称,通常用于生成文件名,也是 Odoo 查找关联 QWeb 模板的默认名称(格式通常是 module_name.template_name
)。report_file
(Char): 类似于 report_name
,有时用于指定生成的文件名或查找模板。在现代 Odoo 版本中,report_name
和 report_file
通常设置为相同的值,并且与 QWeb 模板的外部 ID 相关联。binding_model_id
(Many2one to ir.model
): 指定这个报表动作应该绑定到哪个模型上,以便在模型的视图中作为“打印”按钮出现。binding_type
(Selection): 指定绑定的类型,'report'
表示这是一个报表动作,会出现在“打印”菜单下。paperformat_id
(Many2one to report.paperformat
): 关联一个纸张格式记录,用于配置 PDF 输出的纸张设置。groups_id
(Many2many to res.groups
): 控制哪些用户组可以看到并执行这个报表动作。template_id
(Many2one to ir.ui.view
): 显式关联用于渲染的 QWeb 模板视图。如果未指定,Odoo 会尝试根据 report_name
或 report_file
查找同名的 ir.ui.view
记录。model
属性指定了报表数据的主要来源模型,报表模块会调用该模型上的方法来获取数据。template_id
或通过 report_name
/report_file
隐式关联的 ir.ui.view
记录,指定了用于渲染报表的 QWeb 模板。这个模板定义了报表的结构和外观。binding_model_id
属性将报表动作与特定的业务模型关联,使得用户可以在该模型的记录视图或列表视图中方便地访问打印功能。这是报表动态生成内容的关键环节。
ir.actions.report
中 model
属性指定的 Odoo Python 模型。_get_report_values(self, docids, data=None)
方法。
self
: 当前模型的实例。docids
: 一个列表,包含用户选择要打印的记录的数据库 ID。data
: 一个可选字典,可以包含从客户端或动作定义中传递的额外参数。_get_report_values
方法必须返回一个字典。这个字典的键值对将成为 QWeb 模板渲染时的上下文变量。
'docs'
,其值为根据 docids
查询到的记录对象列表(例如 self.env[self.env.context.get('active_model')].browse(docids)
)。这是因为大多数报表都是针对一个或多个特定记录生成的。{'docs': records, 'total_amount': 1000}
,在 QWeb 中就可以使用 docs
和 total_amount
变量。docs
: 通常是一个记录集。可以使用 t-foreach="doc in docs"
来遍历每一条记录。doc
: 在 t-foreach="doc in docs"
循环内部,doc
变量代表当前正在处理的记录对象。可以像在 Python 中一样访问其字段和关联记录,例如 doc.name
, doc.order_line
, doc.partner_id.name
。doc_model
: 报表动作中指定的模型的技术名称字符串(例如 'sale.order'
)。data
: 如果 _get_report_values
方法接收并处理了 data
参数,并且将其包含在返回字典中,也可以在 QWeb 中访问。
Order:
Customer: ()
Total Amount:
- - x
在这个例子中,docs
是从 Python 传递的记录集,doc
是循环中的当前记录,total_amount
是从 Python 传递的另一个变量。
ir.actions.report
数据库记录的内部唯一标识符 (id
字段)。这是一个整数,在同一个数据库中是唯一的。主要用于数据库内部关联和操作。module_name.object_identifier
)。它是在 XML 数据文件中定义记录时赋予的逻辑名称。外部 ID 的主要作用是在不同数据库实例之间(如开发、测试、生产环境)以及在模块升级时稳定地引用特定的数据库记录。在视图定义中触发报表动作时,通常使用报表动作的外部 ID (
)。ir.actions.report
记录的 report_type
字段。它决定了报表生成的方式和最终输出格式。
qweb-pdf
: 使用 QWeb 渲染为 HTML,然后通过 wkhtmltopdf
转换为 PDF。这是最常见的类型。qweb-html
: 使用 QWeb 直接渲染为 HTML,并在浏览器中显示或作为 HTML 文件下载。aeroo
, rml
),但在 Odoo 18 中,QWeb 是主要的内置报表类型。Odoo 的核心报表架构(ir.actions.report
+ QWeb + wkhtmltopdf
)自 Odoo 8/9 以来一直保持相对稳定。Odoo 18 在这个基础上的改进更多是迭代和优化,而非颠覆性的改变。可能的改进方向包括:
wkhtmltopdf
。wkhtmltopdf
集成稳定性: 改进与不同版本 wkhtmltopdf
的兼容性,更好的错误处理和日志记录,尤其是在 wkhtmltopdf
调用失败时提供更清晰的反馈。wkhtmltopdf
中获得更好的渲染效果。wkhtmltopdf
仍然是主流,但社区或 Odoo 官方可能会持续关注或有限度地支持其他 PDF 生成方案,但这不太可能在 Odoo 18 中成为核心变化。总的来说,Odoo 18 的报表架构是其成熟平台的一部分,开发者可以预期在现有知识基础上进行开发,同时受益于框架层面的性能和稳定性提升。
Odoo 18 的打印功能构建在一个稳定、灵活且可扩展的架构之上。它通过 ir.actions.report
统一管理报表行为,利用强大的 Python 模型层准备数据,依靠灵活的 QWeb 模板引擎定义报表布局和内容,并借助成熟的外部工具 wkhtmltopdf
生成高质量的 PDF 输出。
整个流程从用户界面触发动作开始,经过服务器端的动作识别、数据准备、QWeb 渲染,最终根据报表类型决定是直接返回 HTML 还是调用 wkhtmltopdf
生成 PDF,并将结果返回给客户端。
理解 报表ID、外部ID 和 报表类型 这些概念,掌握 ir.actions.report
的配置,熟悉 QWeb 模板的指令和数据交互方式,以及了解 wkhtmltopdf
的作用和配置,是进行 Odoo 报表开发和定制的关键。
尽管核心架构保持稳定,Odoo 18 在性能、兼容性和用户体验方面的持续优化,使得报表功能更加健壮和高效。作为技术架构师和开发者,深入理解这些底层机制,能够更有效地设计、开发和调试复杂的 Odoo 报表,满足多样的业务需求。
QWeb 是 Odoo 框架中用于渲染 XML 模板的引擎,它在报表生成中扮演着至关重要的角色。报表设计师主要通过编写和修改 QWeb 模板来控制报表的布局、内容和样式。
QWeb 模板本质上是带有特殊 t-
前缀属性的 XML/HTML 结构。这些 t-
属性就是 QWeb 指令,它们告诉 QWeb 引擎如何处理模板的这一部分。
基本结构:
一个典型的 QWeb 报表模板通常包含在一个
标签内,并经常调用 Odoo 提供的标准布局模板。
Report Title
: 这是最外层的容器,提供了基本的 HTML5 文档结构 (
,
,
,
)。
: 调用外部布局模板,它负责添加标准的页眉(公司 Logo、地址等)和页脚(页码、公司信息等)。如果需要自定义页眉页脚或不需要标准布局,可以使用 web.internal_layout
或完全不调用布局模板。: 在布局模板内部,报表的主体内容通常放在一个带有 page
类的 div
中,这有助于控制分页和样式。
核心 QWeb 指令:
以下是一些最常用的 QWeb 指令及其在报表中的应用:
t-if="condition"
: 条件渲染。如果 condition
为真,则渲染包含 t-if
属性的元素及其内容;否则,跳过渲染。
This is a confirmed sale order.
Large Order Discount Applied!
-
- 解释: 根据 Python 模型中传递的数据(例如
doc
对象的 state
或 amount_total
字段值)来决定是否显示某个段落或元素。
t-foreach="collection" t-as="variable_name"
: 循环遍历集合。常用于遍历记录集(如订单行、发票行)。
Product
Quantity
Price
-
- 解释: 遍历
doc
对象(例如一个销售订单记录)的 order_line
关联记录集。在每次循环中,当前订单行记录被赋值给 line
变量,然后在循环体内部可以使用 line
来访问订单行的字段。
t-field="record.field_name"
: 显示记录字段的值,并应用 Odoo 的默认格式化。这是显示模型字段值的首选方式。
Order Date:
Customer:
Total:
-
- 解释: Odoo 会根据字段类型(日期、数字、货币、关联字段等)自动选择合适的格式化方式。例如,日期字段会根据用户偏好格式化,货币字段会显示货币符号和正确的精度。
t-esc="variable"
: 显示变量的值,并进行 HTML 转义。用于显示非字段数据或需要确保安全输出的字符串。
Report Generated By:
Custom Message:
-
- 解释: 如果
user.name
是 "Admin alert('xss') ",t-esc
会将其转义为 "Admin ",防止跨站脚本攻击。
t-raw="variable"
: 显示变量的值,不进行 HTML 转义。用于显示包含 HTML 标签的字符串(例如富文本字段的内容)。
-
- 解释: 如果
doc.description_sale
字段包含 Hello World
,t-raw
会直接输出这些 HTML 标签,浏览器会将其渲染为格式化的文本。
t-set="variable_name, value"
: 在模板中定义一个局部变量。这个变量只在定义它的元素及其子元素范围内有效。
Total Quantity of Products:
-
- 解释: 计算所有订单行的产品数量总和,并将其存储在名为
total_qty
的变量中,然后在模板的其他地方使用它。t-value
属性用于指定变量的值,可以使用 Python 表达式。
t-call="template_external_id"
: 调用并渲染另一个 QWeb 模板,并将其输出插入到当前位置。常用于模块化报表设计,将页眉、页脚、地址块等独立成子模板。
-
- 解释: 渲染外部 ID 为
my_module.report_address_block
的 QWeb 模板,并将其内容放在这里。
t-att="attribute_name, value"
: 动态设置元素的属性。
Draft Order
-
- 解释: 第一个例子动态设置
![]()
标签的 src
属性,用于显示公司 Logo 图片(通常存储为 base64 编码)。第二个例子根据订单状态动态设置 div
的 class
属性。t-att-attribute_name
是 t-att="attribute_name, value"
的简写形式。
t-options='{"option": value, ...}'
: 与 t-field
结合使用,提供额外的格式化选项。
Price:
Date:
-
- 解释: 第一个例子强制使用货币控件格式化
amount_total
,并指定使用 doc.currency_id
作为货币符号。第二个例子强制使用日期控件格式化 date_order
。
2. 通过 Odoo 开发者模式访问和修改现有报表模板
在 Odoo 中,报表模板是以“视图” (ir.ui.view
) 的形式存储在数据库中的。开发者模式允许你直接在 Web 界面中查找和编辑这些视图。
步骤:
- 启用开发者模式:
- 登录 Odoo。
- 点击右上角的用户头像 -> "About Odoo"。
- 在弹出的窗口中,点击 "Activate the developer mode" 或 "Activate the developer mode (with assets)". 后者在调试前端问题时更有用。
- 或者,在当前 URL 中添加
#debug=1
或 #debug=assets
。
- 导航到视图列表:
- 启用开发者模式后,顶部菜单栏会出现 "Technical" (技术) 菜单。
- 点击 "Technical" -> "User Interface" (用户界面) -> "Views" (视图)。
- 查找报表模板:
- 在视图列表中,你可以通过搜索来找到特定的报表模板。
- 报表模板的名称通常与其关联的
ir.actions.report
的 report_name
或 report_file
属性相关。例如,销售订单报表的模板名称可能是 sale.report_saleorder_document
或包含 sale.report_saleorder
。
- 你也可以通过搜索视图的“类型” (
Type
) 为 qweb
来过滤。
- 找到目标模板后,点击进入编辑页面。
- 修改模板:
- 在视图编辑页面,你可以看到模板的 XML 代码。
- 直接在 "Architecture" (架构) 字段中修改 QWeb/HTML 代码。
- 修改完成后,点击 "Save" (保存)。
- 测试修改:
- 回到相应的业务记录(例如一个销售订单)。
- 点击打印按钮,生成报表。
- 查看生成的报表,确认修改是否生效。
注意: 直接在开发者模式下修改视图会立即生效,但这不推荐作为长期或生产环境的修改方式。这些修改会直接写入数据库,并且在模块升级时可能会丢失或与模块自带的视图定义冲突。最佳实践是创建一个自定义模块,使用 QWeb 模板继承来修改现有模板。
3. 常见报表元素在 QWeb 中的实现
- 表格: 使用标准的 HTML
, , , , , 标签,结合 t-foreach
遍历数据行。
Description
Quantity
Unit Price
Amount
- 图片: 使用
![]()
标签。对于存储在 Odoo 字段中的图片(如公司 Logo),通常是 base64 编码的,需要使用 t-att-src
动态设置 src
属性。
![]()
-
to_text()
是一个 QWeb 辅助函数,用于确保 base64 字符串是文本格式。
- 页眉页脚和页码: 如前所述,这主要通过调用
web.external_layout
或 web.internal_layout
实现。这些布局模板内部使用了特定的 HTML 结构和 CSS 类(如 header
, footer
, page-break-after: always;
)以及 wkhtmltopdf
的功能来处理页眉页脚和自动页码。
- 页码通常由
wkhtmltopdf
在生成 PDF 时自动插入,布局模板提供了插入页码的位置。
- 如果你需要完全自定义页眉页脚,可以不调用标准布局,自己编写
和