构建 PDF 报表

重要

本教程是 服务器框架 101 教程的延伸。请确保您已完成该教程,并使用您之前构建的 estate 模块作为本教程中练习的基础。

我们之前在 QWeb 中有所介绍,当时用于构建看板视图。现在我们将进一步探讨 QWeb 的另一个主要用途:创建 PDF 报告。一个常见的业务需求是能够创建发送给客户的文档以及内部使用的文档。这些报告可以用来以有组织的模板形式总结和展示信息,从而以多种方式支持业务。Odoo 还可以在极少额外努力的情况下,将我们公司的页眉和页脚添加到我们的报告中。

与该主题相关的文档可以在 QWeb 模板QWeb 报表 以及操作参考的 报表操作(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.model.access.csv
├── views
│   └── *.xml
├── __init__.py
└── __manifest__.py

不要忘记将你的模板和动作视图所需的任何文件添加到 __manifest__.py 中。在这种情况下,你需要将这些文件添加到 data 列表中,并记住清单中列出的文件是按顺序加载的!

基础报表

注解

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

简单 PDF 报表

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

报表数据

在我们做任何事情之前,首先需要一些数据来填充我们的报表,否则这个教程将不会很有意思。在创建报表时,你需要一些数据来测试你的报表代码,并检查生成的外观是否符合预期。用能够覆盖你所有或大部分预期使用场景的数据进行测试是个好主意。对于我们简单的报表来说,一个良好的数据集示例是:

  • 至少 3 个属性,其中 1 个为 “已售出”,1 个为 “已收到报价”,1 个为 “新状态”。

  • 至少需要 2-3 份针对我们“已售出”和“已收到报价”的房源的报价单

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

  • 完成 定义模块数据 教程(如果您尚未完成的话),并将额外的案例添加到您的演示数据中(您可能需要创建一个新的数据库以加载演示数据)。

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

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

在继续之前,请在数据库中浏览您的数据,确保数据符合预期。当然,您可以在编写报表代码之后添加数据,但这样在编写代码时将无法逐步测试代码的各个部分。对于复杂的报表来说,这可能会在长期中使检查错误和调试代码变得更加困难。

最小模板

报表模板 文档的“最小可行模板”部分可以查看一个最小可行模板。我们可以修改这个示例,以构建我们的最小房产报价模板文件:

<?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(我们在此情况下使用其表格类)和 Font Awesome(用于添加图标)的类来丰富您的报表模板。

  • 使用 t-sett-valuet-foreacht-as 来遍历所有 offer_ids

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

否则,我们建议您进一步了解它们(维基百科 提供了很好的高层次描述),但总体思路是,QWeb 可以根据 Odoo 数据和简单的命令动态生成网页代码。也就是说,QWeb 可以访问记录集数据(和方法),并处理简单的编程操作,例如设置和访问临时变量。例如,在上面的例子中:

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

  • t-foreacht-as 的用法等同于 Python 中的:

for offer in offers:

报表动作

现在我们有了一个模板,需要通过 ir.actions.report 在我们的应用中使其可访问。ir.actions.report 的一个实际示例可以在 这里 找到,对应于 这个模板。其内容在 文档 中有详细说明。

一个 ir.actions.report 动作主要通过模型视图的“打印”菜单使用。在实际示例中,binding_model_id 指定了报表应显示哪个模型的视图,Odoo 会自动为您添加它。报表动作的另一个常见用例是将其与按钮关联,如我们在 Chapter 9: Ready For Some Action? 中所学。这对于仅在特定条件下才有意义的报表非常有用。例如,如果我们想创建一个“最终销售”报表,那么可以将其与一个“打印销售信息”按钮关联,该按钮仅在表单视图中显示当属性为“已售”时才出现。

打印菜单按钮

你可能已经注意到或曾疑惑,为什么我们的报表模板会遍历一个记录集。当我们的模板传入多个记录时,它可以为所有记录生成一份 PDF 报表。在列表视图中使用“打印”菜单并选择多个记录即可演示此功能。

创建报表

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

Exercise

生成一份报表。

  • 在“房产”视图的“打印”菜单中,从“最小模板”子部分添加“房源报价”报表。

  • 通过添加更多数据来改进报表。参考本节的 目标,了解可以添加哪些额外数据,并可根据需要添加更多内容。

  • 附加工具:通过添加一些逻辑,使报表更加灵活。当房产没有报价时,我们不创建表格,而是写一段说明表示目前还没有报价。提示:你需要使用 t-ift-else

请确保您的 PDF 报表与预期数据一致。

子模板

注解

目标:在本节结束时,我们将拥有一个子模板,用于两个报表中。

使用子模板的报表

使用子模板主要有两个原因。一个是当处理超长或复杂的模板时,使代码更易于阅读。另一个是尽可能地重用代码。我们简单的房产报价报表是有用的,但列出房产报价信息可以用于不止一个报表模板。例如,可以生成一个列出销售员所有房产报价的报表。

请查看有关如何调用子模板的 文档,或查看一个 `示例 <https://github.com/odoo/odoo/blob/0e12fa135882cd5095dbf15fe2f64231c6a84336/addons/portal/static/src/xml/portal_chatter.xml#L147-L160>`__(请注意,无论是在报表还是视图中,QWeb 都使用相同的控制流程。)

Exercise

创建和使用子模板。

  • 将报价单中的表格部分拆分为独立的模板。记得在完成后检查原始报表是否仍能正确打印。

  • res.users 添加一个新的报表,允许打印在其表单视图中可见的所有房地产物业(即在“设置”应用中)。在同一份报表中包含该销售人员所有物业的报价。提示:由于此情况下 binding_model_id 不在房地产模块中,您需要使用 ref="base.model_res_users"

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

请确保您的报表与预期数据一致!

报表继承

注解

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

一个继承的报表

QWeb 中的继承使用与 视图继承 相同的 xpath 元素。不过,QWeb 模板引用其父模板的方式有所不同。只需向 template 元素添加 inherit_id 属性,并将其设置为 模块.父模板 ID 即可更轻松地实现。

我们没有在 estate_account 模块的任何房地产模型中添加新字段,但我们仍然可以向现有的房地产报表中添加信息。例如,我们知道所有“已售出”的房产已经为其创建了发票,因此我们可以将此信息添加到我们的报表中。

Exercise

继承一个报表。

  • 扩展物业报表,以包含有关发票的一些信息。您可以参考本节的 目标 以获得灵感(即,当物业状态为“完成”时打印一行,否则不打印任何内容)。

再次提醒,确保您的报表与预期数据一致!

附加功能

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

翻译

我们都知道,由于自动翻译和手动翻译,Odoo 被用于多种语言。QWeb 报表也不例外!请注意,如果模板中的文本内容存在不必要的空格,有时翻译可能无法正常工作,因此请尽可能避免使用这些空格(尤其是开头的空格)。

报表是网页

你可能已经厌倦了听到 QWeb 会生成 HTML,但我们还是要再说一遍!报表使用 QWeb 编写的其中一个优点是它们可以在网页浏览器中查看。如果你想要嵌入一个指向特定报表的超链接,这会很有用。请注意,通常的安全检查仍然适用,以防止未经授权的用户访问报表。

条形码

Odoo 内置了条形码图片生成器,允许在您的报表中嵌入条形码。查看对应的 代码,以查看所有支持的条形码类型。