构建 PDF 报表

重要

本教程是 服务器框架入门 教程的延伸。请确保你已完成该教程,并以你构建的 estate 模块作为本教程练习的基础。

我们之前 了解了 QWeb,当时用它来构建看板视图。现在我们将展开介绍 QWeb 的另一项主要用途:创建 PDF 报表。常见的业务需求是能够创建文档以发送给客户并在内部使用。这些报表可用于在结构化的模板中汇总和展示信息,以多种方式支持业务。Odoo 还可以用最小的额外投入为报表添加公司页眉和页脚。

与本主题相关的文档可在 QWeb 模板、QWeb 报表 以及 Actions 参考的 报表动作(ir.actions.report) 小节中找到。

文件结构

PDF 报表的主体是其 QWeb 模板。它通常还需要一个对应的 ir.actions.report 来将该报表纳入模块的业务逻辑中。文件名或其位置没有严格的规则,但这两部分通常存储在模块目录顶层 report 文件夹内的两个独立文件中。如果模块有许多或不止一个较长的报表模板,它们通常按逻辑分散在以所含报表命名的不同文件中。报表的所有动作通常存储在同一个以 _reports.xml 结尾的文件中,无论其中包含多少个报表。

因此,你的工作目录树预期看起来像这样:

estate
├── models
│   ├── *.py
│   └── __init__.py
├── report
│   ├── estate_property_templates.xml
│   └── estate_property_reports.xml
├── security
│   └── ir.access.csv
├── views
│   └── *.xml
├── __init__.py
└── __manifest__.py

别忘了将模板和动作视图所在的文件添加到 __manifest__.py 中。在本例中,你需要将这些文件添加到 data 列表中,并记住清单文件中列出的文件是按顺序加载的!

基本报表

注解

目标:在本节结束时,我们将能够打印一个显示某房产所有报价的报表。

简单的 PDF 报表

在我们的房地产示例中,有许多有用的报表可以创建。我们可以创建的一个简单报表就是显示某房产所有报价的报表。

报表数据

在做任何事之前,我们首先需要一些数据来填充报表,否则本教程就不会很有趣。创建报表时,你需要一些数据来测试报表代码并检查最终外观是否符合预期。最好使用能覆盖大部分或全部预期用例的数据进行测试。适合我们简单报表的一组良好的代表性数据是:

  • 至少 3 个房产,其中 1 个为 “sold”,1 个为 “offer received”,1 个为 “new”。

  • 至少为我们的 “sold” 和 “offer received” 房产提供 2-3 个报价

如果你还没有这样一组数据,你可以选择:

  • 完成 定义模块数据 教程(如果你还没完成),并将额外的情况添加到演示数据中(你可能需要创建一个新数据库来加载演示数据)。

  • 在数据库中手动创建数据。

  • 将此 数据文件 复制到 estate 模块中的一个新目录(data)中,并将 这些行 复制到你的 __manifest__.py 文件中(你可能需要创建一个新数据库来加载演示数据)。

在继续之前,请在你的数据库中逐一查看数据,确保数据符合预期。当然,你可以在编写报表代码之后再添加数据,但那样你就无法在编写过程中逐步测试代码的各个部分。对于复杂的报表,从长远来看,这会让检查错误和调试代码变得更加困难。

最小模板

一个最小可行模板可在 报表模板 文档的 “Minimal viable template” 小节中查看。我们可以修改此示例来构建最小的房产报价模板文件:

<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
    <template id="report_property_offers">
        <t t-foreach="docs" t-as="property">
            <t t-call="web.html_container">
                <t t-call="web.external_layout">
                    <div class="page">
                        <h2>
                            <span t-field="property.name"/>
                        </h2>
                        <div>
                            <strong>Expected Price: </strong>
                            <span t-field="property.expected_price"/>
                        </div>
                        <table class="table">
                            <thead>
                                <tr>
                                    <th>Price</th>
                                </tr>
                            </thead>
                            <tbody>
                                <t t-set="offers" t-value="property.mapped('offer_ids')"/>
                                <tr t-foreach="offers" t-as="offer">
                                    <td>
                                        <span t-field="offer.price"/>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </t>
            </t>
        </t>
    </template>
</odoo>

我们文件中大多数 Odoo 特有的(即非 HTML 的)项目都在最小可行模板小节中作了说明。我们模板中的一些额外特性是:

  • 使用 class="table" 属性,让我们的表格具有不错的格式。你的报表模板中可以使用 Twitter Bootstrap(本例中我们使用其 table 类)和 Font Awesome(可用于添加图标)的类。

  • 使用 t-set、t-value、t-foreach 和 t-as,以便遍历所有 offer_ids。

如果你已经熟悉网站模板引擎,那么 QWeb 指令(即 t- 命令)可能无需过多解释,你可以直接查看其 文档 并跳到下一小节。

否则建议你多了解一些它们(Wikipedia 有很好的高层次描述),但总体思路是:QWeb 提供了根据 Odoo 数据和简单命令动态生成 Web 代码的能力。也就是说,QWeb 可以访问记录集数据(及方法),并处理设置和访问临时变量等简单的编程操作。例如,在上面的示例中:

  • t-set 创建一个名为 “offers” 的临时变量,其值由 t-value 设为当前 estate.property 记录集的 offer_ids。

  • t-foreach 和 t-as 的用法等同于以下 Python:

for offer in offers:

报表动作

现在我们有了模板,还需要通过 ir.actions.report 让应用能够访问它。ir.actions.report 的实际示例见 此处,对应 这个模板。其内容在 文档 中均有说明。

ir.actions.report 主要通过模型视图的打印菜单使用。在实际示例中,binding_model_id 指定报表应显示在哪些模型的视图中,Odoo 会自动为你添加。报表动作的另一个常见用法是把它链接到按钮,正如我们在 第 9 章:准备好采取行动了吗? 中学到的。对于只在特定条件下才有意义的报表,这很方便。例如,如果我们要制作一份“最终销售”报表,就可以把它链接到一个“打印销售信息”按钮,该按钮只在房产状态为“已售出”时出现在表单视图中。

打印菜单按钮

你可能已经注意到,或者想知道为什么我们的报表模板要遍历一个记录集。当模板接收到多条记录时,它可以为所有记录生成一份 PDF 报表。在列表视图中选中多条记录后使用打印菜单,就能演示这一点。

制作报表

最后,你现在知道该在哪里创建文件,以及文件的内容应该是什么样子。祝你制作报表愉快!

练习

制作一个报表。

  • 把“最小模板”小节中的房产报价报表添加到房产视图的打印菜单中。

  • 通过添加更多数据来改进报表。请参阅本节的 目标 以了解可以添加哪些额外数据,也欢迎继续添加更多内容。

  • 附加练习:加入一些逻辑让报表更加灵活——当房产没有任何报价时,不创建表格,而是写一段说明“暂无报价”的文字。提示:你需要使用 t-if 和 t-else。

记得检查你的 PDF 报表是否与数据相符。

子模板

注解

目标:本节结束时,我们将拥有一个可以在 2 份报表中使用的子模板。

使用子模板的报表

使用子模板主要有两个原因。一是处理超长或复杂的模板时,可以让代码更易读。二是尽可能复用代码。我们这份简单的房产报价报表很有用,但列出房产报价信息的用途并不限于一份报表模板。例如,有一份报表会列出某位销售员名下所有房产的报价。

试着通过阅读相关 文档 和/或查看一个 示例 来理解如何调用子模板(请记住,无论用于 Odoo 中的报表还是视图,QWeb 使用的控制流都是相同的)。

练习

创建并使用子模板。

  • 把报价中的表格部分拆分成单独的模板。之后记得检查原来的报表是否仍能正确打印。

  • 为 res.users 新增一份报表,用于打印在其表单视图中可见的所有房地产房产(即“设置”应用中的内容)。在同一份报表中包含这些销售员每处房产的报价。提示:由于此处的 binding_model_id 不在 estate 模块内,你需要使用 ref="base.model_res_users"。

    最终结果应与本节 目标 中的图片类似。

记得检查你的报表是否与数据相符!

报表继承

注解

目标:本节结束时,我们将在 estate_account 模块中继承房产报表。

一份继承而来的报表

QWeb 中的继承使用与 视图继承 相同的 xpath 元素。不过,QWeb 模板引用其父模板的方式有所不同。更简单的做法是,只需在 template 元素上添加 inherit_id 属性,并将其设置为 module.parent_template_id。

我们并没有在 estate_account 中为任何 estate 模型添加新字段,但仍可以往现有的房产报表中添加信息。例如,我们知道任何“已售出”的房产都已经为其创建了发票,因此可以把这一信息加入报表。

练习

继承一个报表。

  • 扩展房产报表,加入一些关于发票的信息。你可以参考本节 目标 获取灵感(即:当房产状态为“完成”时打印一行,否则不打印任何内容)。

同样,记得检查你的报表是否与数据相符!

附加功能

以下所有附加功能在 QWeb 报表 文档中都有更详细的说明,包括如何实现每一项。

多语种翻译

我们都知道,得益于自动翻译和人工翻译,Odoo 可以在多种语言中使用。QWeb 报表也不例外!请注意,如果模板文本内容中存在多余的空格,翻译有时会无法正常工作,因此请尽量避免这些空格(尤其是行首空格)。

报表即网页

你可能已经听腻了“QWeb 生成 HTML”这句话,但我们还要再说一次!用 QWeb 编写报表的一个妙处是,它们可以在 Web 浏览器中查看。如果你想嵌入指向某份报表的超链接,这会很有用。请注意,常规的安全检查仍会生效,以防未授权用户访问报表。

条码作业

Odoo 内置了条形码图像生成器,可以把条形码嵌入报表中。查看相应的 代码 即可了解支持的所有条形码类型。