ODOO13之12:Odoo 13開發之報表和服務端 QWeb


報表是業務應用非常有價值的功能,內置的 QWeb 引擎是報表的默認引擎。使用 QWeb 模板設計的報表可生成 HTML 文件並被轉化成 PDF。也就是說我們可以很便捷地利用已學習的 QWeb 知識,應用到業務報表中。本文中我們將為圖書館應用添加一個報表,復習 QWeb生成報表的關鍵技巧。包括像匯總一類計算、翻譯和紙張樣式打印。

本文主要內容有:

  • 安裝wkhtmltopdf
  • 創建業務報表
  • QWeb 報表模板
  • 在報表中展示數據
  • 渲染圖片
  • 報表匯總
  • 定義紙張格式
  • 在報表中啟用語言翻譯
  • 使用自定義 SQL 建立報表

開發准備

我們將繼續使用library_app插件模塊進行學習,該模塊在第三章 Odoo 13 開發之創建第一個 Odoo 應用中初次創建,然后在第五章 Odoo 13開發之導入、導出以及模塊數據和第六章 Odoo 13開發之模型 – 結構化應用數據中進行了改進。相關代碼請參見 GitHub 倉庫。本文完成后代碼也請參見GitHub 倉庫。

安裝wkhtmltopdf

要正確地生成報表,應安裝wkhtmltopdf工具的推薦版本,該工具的名稱表示Webkit HTML to PDF。Odoo使用它來將渲染的 HTML 頁面轉化為 PDF 文檔。有些版本的wkhtmltopdf庫已知存在問題,比如不打印頁面頭部和底部,所以需挑選使用的版本。從Odoo 10開始,官方支持了0.12.5版本,這也是官方推薦的版本。

小貼士:官方Odoo項目有一個 wiki 頁面,保持了對於wkthtmltopdf使用的信息和推薦。可通過 GitHub 進行查看。

不幸的是你的主機系統,不論是Debian/Ubuntu或其它系統,所提供的安裝包版本都不太一致。所以我們應下載和安裝對於當前操作系統和 CPU 類型的推薦版本包。下載鏈接請見 GitHub。

首先應確保系統中所安裝的不是錯誤的版本:

wkhtmltopdf --version 

如果上述命令打印的結果不是我們需要的版本,應對其進行卸載。在Debian/Ubuntu系統中,使用的命令如下:

sudo apt-get remove --purge wkhtmltopdf 

下一步我們需要下載適合我們系統的安裝包並進行安裝。通過GitHub下載鏈接進行查看。對於0.12.5,最新 Ubuntu 安裝版本是針對Ubuntu 14.04 LTS穩定版,但對其后的Ubuntu系統應該同樣生效。我們在最近發布的Ubuntu 64系統中進行安裝,下載命令如下:

wget "https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb" -O /tmp/wkhtml.deb 

下一步應進行安裝。安裝本地deb文件並不會自動安裝依賴,因此需要執行第二步來完成安裝:

sudo dpkg -i /tmp/wkhtml.deb 

這時可能會顯示缺少依賴的錯誤,以下命令可解決這一問題:

sudo apt-get -f install 

現在,我們可以檢查wkhtmltopdf庫是否正確安裝並確認是否為所需版本:

$ wkhtmltopdf --version wkhtmltopdf 0.12.5 (with patched qt) 

此時Odoo服務的啟動日志就不會再提示You need Wkhtmltopdf to print a pdf version of the report的信息了。

創建業務報表

我們會繼續使用前面文章所使用的library_app模塊,進添加實現報表的文件。我們將創建的報表會長成這樣

報表文件應放在模塊子文件夾/reports中。首先我們來添加一個reports/library_book_report.xml數據文件,不要忘記在__manifest__.py文件的 data 下導入該文件。先在reports/library_book_report.xml文件中聲明一個新報表:

<?xml version="1.0"?> <odoo> <report id="action_library_book_report" string="Library Books" model="library.book" report_type="qweb-pdf" name="library_app.report_library_book_template" /> </odoo> 

<report>標簽是對向ir.actions.report寫入數據的簡寫形式,這個模型是客戶操作的特殊類型。它的數據可通過Settings > Technical > Actions >Reports菜單進行查看。

注意:Odoo13開始,report的模型從 ir.actions.report.xml修改為ir.actions.report

小貼士:在設計報表時,我們可能更傾向保留為report_type=”qweb-html”然后在完成時再修改為qweb-pdf文件。這樣在QWeb模板中可更快速的生成報表並且更易於檢查 HTML 結果。

執行完模塊升級(~/odoo-dev/odoo/-bin -d dev13 -u library_app)后,圖書表單視圖中會在頂部顯示一個 Print 按鈕(列表視圖中也有),它在Actions按鈕的左側,其中包含添加的運行報表的選項(Library Books)。

現在還無法生成報表,因為我們還沒有進行定義。這是一個QWeb報表,因此需要用到QWeb模板。name 屬性標識所使用的模板。與其它標識符引用不同,name 屬性中需要添加模塊前綴,我們必須使用完整的引用名稱<module_name>.<identifier_name>

QWeb 報表模板

在下面的代碼可以看出,這個報表遵循一個基本框架。僅需在reports/library_book_report.xml文件元素后添加如下代碼:

    <template id="report_library_book_template"> <t t-call="web.html_container"> <t t-call="web.external_layout"> <div class="page"> <!-- Report header content --> <t t-foreach="docs" t-as="o"> <!-- Report row content --> </t> <!-- Report footer content --> </div> </t> </t> </template> 

這里最重要的元素是使用標准報表結構的t-call指令。web.html_container模板進行支持 HTML 文檔的基本設置。web.external_layout模板使用相應公司的相關設置處理報表頭部和底部。可將其替換為web.internal_layout模板,它將只使用一個基本的頭部。

ℹ️ Odoo 11中的修改 對報表的支持布局從report 模塊移到了 web 模塊中。也就是說此前版本中使用report.external_layout或report.internal_layout的引用 ,在11.0中引用應修改為web.<…>。

external_layout模板可由用戶自定義,Odoo 11引入了這一選項,在Settings > General Settings菜單中,然后相關內容在Business Documents > Document Template版塊:

這里我們可以點擊Change Document Template來從幾個可用的模板中選取,甚至是點擊Edit Layout來自定義所選模板的 XML。這一個報表框架適用於列表式報表,即報表中每條記錄顯示為一行。報表頭部通常顯示標題,底部區域則顯示匯總。

另一種格式是文檔報表,每條記錄是單獨一頁,比如郵件。這種情況報表結構如下:

    <template id="report_todo_task_template"> <t t-call="web.html_container"> <t t-call="web.external_layout"> <t t-foreach="docs" t-as="o"> <div class="page"> <!-- Report content --> </div> </t> </t> </t> </template> 

我們會創建一個列表式報表,所以還會使用此前的框架。現在我們已經有了基本框架。既然報表是QWeb模板,那么它也可以像其它視圖那樣進行繼承。報表中使用的QWeb模板可使用常規視圖繼承使用的 XPath 表達式來進行繼承。

補充:此時點擊打印會輸出一個空白的 PDF 文件。

在報表中展示數據

與看板視圖不同,報表中的QWeb模板在服務端進行渲染,因此使用Python QWeb來實現。我們可以將其看作相同規格的兩種實現,需要注意其中的一些區別。

首先這里的QWeb表達式由 Python 語法運行,而非JavaScript。對於最簡的表達式幾乎沒有區別,但更為復雜的運算則可能存在差別。表達式運行上下文也不同,對於報表可使用如下變量:

  • docs是要打印記錄的可迭代集合
  • doc_ids是一個要打印記錄的 ID 列表
  • doc_model指定記錄的模型,如library.book
  • time是對Python時間庫的引用
  • user是運行報表的用戶記錄
  • res_company是當前用戶的公司記錄

可使用t-field來引用字段值,並可使用t-options來進行補充指定渲染字段內容的具體組件。

ℹ️Odoo 11中的修改 在此前的 Odoo 版本中,使用的是t-field-options屬性,但在 Odoo 11中淘汰了該屬性,改用t-options屬性。

例如,假設doc表示一條具體記錄,代碼如下:

<t t-field="doc.date_published" t-options="{'widget': 'date'}" /> 

現在我們可以開始設計報表的頁面內容了。

小貼士:不幸的是官方文檔中並沒有涉及QWeb支持的組件及其選項。所以當前對其做進一步的了解只能是通過閱讀相應源碼。可訪問ir_qweb_fields.py,查找繼承ir.qweb.field的類,get_available_options() 方法可有助了解支持的選項。

報表內容由HTML書寫,並且使用了Twitter Bootstrap 4來幫助設計報表布局。在網頁開發中大量使用了Bootstrap,有關Bootstrap的完整指南請見官方網站。

以下為渲染報表頭部的 XML 代碼,應放在<div class=”page”>中並替換掉現有的<t t-foreach=…>元素:

<!-- Report header content --> <div class="container"> <div class="row bg-primary"> <div class="col-3">Title</div> <div class="col-2">Publisher</div> <div class="col-2">Date</div> <div class="col-3">Publisher Address</div> <div class="col-2">Authors</div> </div> <t t-foreach="docs" t-as="o"> <div class="row"> <!-- Report row content --> </div> </t> <!-- Report footer content --> </div> 

內容的布局使用了Twitter Bootstrap的HTML網格系統。總的來說Bootstrap使用13列的網格布局,此處網格在<div class=”container”>元素中。

ℹ️Odoo 13中的修改 現在Odoo使用Bootstrap 4,它對此前 Odoo 版本中使用的Bootstrap 3並沒有保持向后兼容。對於從Bootstrap 3改為Bootstrap 4的小技巧,可參照 Odoo 中關於這一話題的 Wiki 頁面。

可使用<div class=”row”>來添加行。每行中還有多個單元格,分別占用不同列數,總計應為13列。每個單元格可通過<div class=”col-N”> 來進行定義,其中 N 表示占用列的數量。

小貼士:Bootstrap 4 在其大部分構件中使用了 CSS 彈性盒子布局,已知wkhtmltopdf 對彈性盒子的功能並不都能很好的支持。因此如果有些地方效果不對,請嘗試使用其它元素或方法,如 HTML 表格。

此處我們為頭部行添加了標題,然后t-foreach循環遍歷每條記錄並在各行中進行渲染。因為渲染由服務端完成,記錄都是對象,我們可使用點號標記來從關聯數據記錄中訪問字段。這也讓關聯字段的數據訪問變得更為容易。注意這在客戶端渲染的QWeb視圖中是無法使用的,比如網頁客戶端的看板視圖。

以下是在<div class=”row”>元素中的記錄行內容XML:

<!-- Report row content --> <div class="col-3"> <h4><span t-field="o.name" /></h4> </div> <div class="col-2"> <span t-field="o.publisher_id" /> </div> <div class="col-2"> <span t-field="o.date_published" t-options="{'widget': 'date'}"/> </div> <div class="col-3"> <span t-field="o.publisher_id" t-options='{ "widget": "contact", "fields": ["address", "email", "phone", "website"], "no_marker": true}'/> </div> <div class="col-2"> <!-- Render authors --> </div> 

可以看到字段可通過t-options屬性添加額外的選項,內容為包含帶有widget鍵的 JSON 字典。更為復雜的示例是contact組件,用於格式化地址。我們使用它來渲染出版商地址o.publisher_id。默認contact 組件顯示地址時帶有圖像,類似電話圖標。no_marker=”true”選項禁用了這一顯示。

補充:no_marker=”true”禁用的地址圖標如上所示

渲染圖片

我們報表最后一列為一組帶有頭像的作者。我們將通過遍歷來渲染出每個作者,並使用Bootstrap媒體對象:

<!-- Render authors --> <ul class="list-unstyled"> <t t-foreach="o.author_ids" t-as="author"> <li class="media"> <span t-field="author.image_256" t-options="{'widget': 'image'}" /> <div class="media-body"> <p class="mt-0"> <span t-field="author.name" /> </p> </div> </li> </t> </ul> 

此處我們遍歷了author_ids,使用字段圖像組件<t t-field=”…” t-options=”{‘widget’: ‘image’}”>對每個作者的頭像進行了渲染,然后還有姓名。

報表匯總 報表中經常需要提供匯總。這可借由 Python 表達式來計算總額。在閉合標簽之后,我們添加最后一行用於匯總:

<!-- Report footer content --> <div class="row"> <div class="col-3"> Count: <t t-esc="len(docs)" /> </div> <div class="col-2" /> <div class="col-2" /> <div class="col-3" /> <div class="col-2" /> </div> 

len() Python函數用於計算集合元素的數量。類似地,匯總也可以使用sum()來對一組值進行求和運算。例如,可使用如下列表推導式來進行總額運算:

<t t-esc="sum([x.price for x in docs])" /> 

可以把這個列表推導式看作一個內嵌的循環。有時我們需要貫穿報表執行一些計算,如流動匯總(running total),匯總至當前記錄。這可通過t-set 來定義累加變量在每一行進行更新來實現。為描述這一功能,我們來計算作者數的累加。首先在docs 記錄集 t-foreach 循環前初始化變量:

<!-- Running total: initialize variable --> <t t-set="author_count" t-value="0" /> 

然后在循環內,將記錄的作者數添加到變量中。我們這里顯示在書名之后,並在每行打印出當前總數:

<!-- Running total: increment and present --> <t t-set="author_count" t-value="author_count + len(o.author_ids)" /> (Accum. authors: <t t-esc="author_count" />) 

定義紙張樣式

到這里我們的報表的 HTML 顯示沒有問題了,但在打印的 PDF 頁面中還不夠美觀。使用橫向頁面顯示結果會更好,因此下面就來添加紙張樣式。在報表 XML 文件的最上方添加如下代碼:

    <record id="paperformat_euro_landscape" model="report.paperformat"> <field name="name">European A4 Landscape</field> <field name="default" eval="True" /> <field name="format">A4</field> <field name="page_height">0</field> <field name="page_width">0</field> <field name="orientation">Landscape</field> <field name="margin_top">40</field> <field name="margin_bottom">23</field> <field name="margin_left">7</field> <field name="margin_right">7</field> <field name="header_line" eval="False" /> <field name="header_spacing">35</field> <field name="dpi">90</field> </record> 

這是對European A4格式的一個拷貝,這在data/report_paperformat_data.xml文件中定義的base 模塊中,但這里將排列方向由縱向改為了橫向。定義的紙張樣式可通過后台Settings > Technical > Reporting > Paper Format菜單進行查看。

現在就可在報表中使用它了。默認的紙張樣式在公司設置中定義,但我們也可以為特定報表指定紙張樣式。這通過在報表操作中的paperfomat屬性來實現。下面來編輯打開報表使用的操作,添加這一屬性:

    <report id="action_library_book_report" ... paperformat="paperformat_euro_landscape" /> 

在報表中啟用語言翻譯

要在報表中啟用翻譯,需要使用帶有t-lang屬性的元素在模板中調用翻譯方法。t-lang需傳入一個語言代碼來運行,如es或en_US。它需要可以找到所需使用語言的字段名。一種方式是使用當前用戶的語言,為此,我們定義一個外層翻譯報表來調用要翻譯的報表,使用t-lang屬性來設置語言來源:

    <report id="action_library_book_report" ...  name="library_app.report_library_book_translated" paperformat="paperformat_euro_landscape" /> <template id="report_library_book_template"> <t t-call="library_app.report_library_book_translated" t-lang="user.lang" /> </template> 

本例中,每本書都使用了用戶的語言user_id.lang來進行渲染。

有些情況下,我們可能需要每條記錄以指定語言進行渲染。比如在銷售訂單中,我們可能要各條記錄按照對應合作方/客戶的首選語言進行打印。假設我們需要每本書按照對應出版商的語言進行渲染,QWeb模板可以這么寫:

    <template id="report_library_book_translated"> <t t-foreach="docs" t-as="o"> <t t-call="library_app.report_library_book_template" t-lang="o.publisher_id.lang"> <t t-set="docs" t-value="o" /> </t> </t> </template> 

以上我們對記錄進行了迭代,然后每條記錄根據記錄上的數據使用相應的語言進行報表模板的調用,本例為出版商的語言publisher_id.lang。

補充:以上代碼運行時每條記錄都會帶有一個頭部,如需按列表顯示還需將頭部抽象到循環之外

使用自定義 SQL 創建報表

前面我們所創建的報表都是基於常規記錄集,但在有些情況下我們需要執行一些在QWeb模板中不易於處理的數據轉換或累加。一種解決方法是寫原生 SQL 查詢來創建我們所需的數據集,將結果通過特殊的模型進行暴露,然后基於這一數據集來生成報表。

我們創建reports/library_book_report.py文件來講解這一情況,代碼如下:

from odoo import models, fields class BookReport(models.Model): _name = 'library.book.report' _description = 'Book Report' _auto = False name = fields.Char('Title') publisher_id = fields.Many2one('res.partner') date_published = fields.Date() def init(self): self.env.cr.execute(""" CREATE OR REPLACE VIEW library_book_report AS (SELECT * FROM library_book WHERE active=True) """) 

要加載以上文件,需要在模塊的頂級__init__.py文件中添加from . import reports,並在reports/__init__.py文件中添加from . import library_book_report。

_auto屬性用於阻止數據表的自動創建。我們在模型的init()方法中添加了替代的 SQL。它會創建數據庫視圖,並提供報表所需數據。以上 SQL 查詢非常簡單,旨在說明我們為視圖可以使用任意有效的 SQL查詢,如對額外數據執行累加或計算。

我們還需要聲明模型字段來讓 Odoo 知道如何正確處理每一條記錄中的數據。同時不要忘記為新模型添加安全訪問規則,否則將無法使用該模型。下面在security/ir.model.access.csv文件中添加下行:

access_library_book_report,access_library_book_report,model_library_book_report,
library_group_user,1,0,0,0 

還應注意這是一個全新的不同模型,與圖書模型的訪問規則並不相同。下一步基於該模型我們可以使用reports/library_book_sql_report.xml新增一個報表:

<?xml version="1.0"?> <odoo> <report id="action_library_book_sql_report" string="Library Book SQL Report" model="library.book.report" report_type="qweb-html" name="library_app.report_library_book_sql" /> <template id="report_library_book_sql"> <t t-call="web.html_container"> <t t-call="web.external_layout"> <div class="page"> <!-- Report page content --> <table class="table table-striped"> <tr> <th>Title</th> <th>Published</th> <th>Date</th> </tr> <t t-foreach="docs" t-as="o"> <tr> <td class="col-xs-6"> <span t-field="o.name" /> </td> <td class="col-xs-3"> <span t-field="o.publisher_id" /> </td> <td class="col-xs-3"> <span t-field="o.date_published" t-options="{'widget': 'date'}" /> </td> </tr> </t> </table> </div> </t> </t> </template> </odoo> 

對於更為復雜的情況,我們還會需要用戶輸入參數,這時可以使用不同的方案:向導。為此我們應創建一個臨時模型來存儲用戶的報表參數。因為這是由代碼生成的,所以我們可以使用所需的任意邏輯。

強烈推薦學習已有的相似報表來獲取靈感。一個不錯的例子是Leaves菜單選項下的Leaves by Department,相應的臨時模型定義可以參見addons/hr_holidays/wizard/hr_holidays_summary_employees.py。

首先在views/library_menu.xml文件中添加如下內容:

    <act_window id="action_library_book_report_window" name="Book Report" res_model="library.book.report" view_mode="tree,form" /> <menuitem id="menu_library_book_report" name="Book Report" parent="menu_library" action="action_library_book_report_window" /> 

__manifest__.py 文件中引入前述的 XML 文件后更新模塊

總結

前面一篇文章中,我們學習了QWeb以及如何使用它來設計看板視圖。本文中我們學習了QWeb報表引擎,以及使用QWeb模板語言創建報表最為重要的一些技術。

下一篇文章中,我們將繼續使用QWeb,這次是創建網頁。我們將學習書寫網頁控制器,為我們的網頁提供更豐富的功能。

☞☞☞第十三章 Odoo 13開發之創建網站前端功能


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM