大家好,
延續上一篇文章,要怎麼讓模組有功能呢? 首先開始為它加入一個簡單的模型。
模型 (Model) 描述商務對象,例如機會 (Opportunity),銷售訂單 (Sales Order) 或 Partner(客戶,供應商等)。
模型具有屬性列表,還可以定義其特定的商務。使用衍生自 Odoo 模板類別的 Python 類別來實作。
它可以直接轉換為資料庫對象,並且在安裝或升級模組時,Odoo 會自動處理該資料庫對象。負責這個的組件是物件關係映射(ORM)。
待辦事宜 (To-Do) 模組將是一個非常簡單的應用程式來維護待辦任務。
我們先規劃模組所需要的功能:
有一個文字區塊可以描述待辦事項,以及用於將它們標記為完成的複選框。
模組會加入一個按鈕,可以從"待辦事項"列表中刪除已完成任務。
建立資料模型
Odoo 開發指南指出模型的 Python 檔案應放置在 models 子目錄中。
因此,我們將在 todo_app 模組的主目錄中建立一個 models/todo_task_model.py
Odoo 官方編碼指南可以在 http://www.odoo.com/documentation/11.0/reference/guidelines.html 找到。另一個相關的編碼標準檔案是 https://github.com/OCA/maintainer-tools/blob/master/CONTRIBUTING.md 上的 Odoo 社區協會編碼準則。
要導入 models 目錄,要去編輯模組的 __init__.py,如下所示:
from . import models
在 models/__init__.py , import model:
from . import todo_task_model
現在可以建立 models/todo_task_model.py :
# -*- coding: utf-8 -*- from odoo import fields, models class TodoTask(models.Model): _name = 'todo.task' _description = 'To-do Task' name = fields.Char('Description', required=True) is_done = fields.Boolean('Done?') active = fields.Boolean('Active?', default=True) user_id = fields.Many2one( 'res.users', string='Responsible', default=lambda self: self.env.user) team_ids = fields.Many2many('res.partner', string='Team')
第一行是一個特殊的標記,告訴 Python 解釋器該檔案具有 UTF-8,因此它可以預期和處理非 ASCII 字符。
第二行是一個 Python 的 import,使 Odoo 核心的模型和欄位物件可以使用。
第三行聲明我們的新模型。這是一個從 models.Model 衍生出來的類別。
下一行設置 _name 屬性,定義將在整個 Odoo 中使用的標識符來引用此模型。
請注意,在這種情況下,實際的 Python 類別名稱 TodoTask 對其他 Odoo 模組來說是沒有意義的。
_name 值是將用作標識符的值。只有模型名稱使用點(.)分隔關鍵字。
其他所有內容都使用下劃線(_):addon 模組,XML 標識符,table 名稱等等。
然後我們有 _description 模型屬性。這不是強制性的,但它為模型記錄提供了一個使用者友好的名稱,它可以用於更好的使用者消息。
其餘行定義模型的欄位。值得注意的是,name 和 active 名字都是特殊欄位。
預設情況下,從其他模型引用時,Odoo 將使用 name 欄位作為記錄的標題。
active 欄位用於啟用記錄,預設情況下,只會顯示有啟用的記錄。我們將使用它清除已完成的任務,而無需從資料庫中實際刪除它們。
user_id 欄位允許選擇任務的使用者所有者。我們加入了一個預設值,以便當前使用者自動設置為負責任務。
team_ids 則可以選擇任務中涉及的合作夥伴列表。這是一個多對多的關係,每個任務可以有許多團隊成員,每個夥伴可以參與許多任務。
為了讓我們的 Python 程式碼更改生效,需要升級模組,觸發資料庫中相關物件的建立。
目前任何選項來存取這個新模型,因為我們還沒有加入它們。不過,我們可以使用技術菜單檢查新建立的模型。
在設置頂部選項中,轉至 Technical | Database Structure | Models,在列表中搜索 todo.task 模型,然後單擊它以查看其定義:
如果一切順利,則確認模型和欄位已建立。如果在這裡看不到它們,請嘗試使用模組升級重新啟動服務器,如前所述。
我們還可以看到一些我們沒有聲明的其他欄位。這些是保留欄位 Odoo 自動加入到每個新模型。它們如下所示:
- id 是模型中每個記錄的唯一數字標識符。
- create_date 和 create_uid 分別指定記錄的建立時間和建立時間。
- write_date 和 write_uid 分別確認記錄的上次修改時間和修改時間。
- __last_update 是實際上不儲存在資料庫中。它用於一些檢查。
擴展原有的模型
新模型透過 Python 類別定義。
在 Odoo 特定的繼承機制(_inherit 類別屬性)的幫助下,擴展模型也可以透過 Python 類別來完成。
此 _inherit 類別屬性指出要擴展的模型。新類別繼承了父 Odoo 模型的所有功能,只需要定義或宣告修改的部分。
在 todo.task 模式中,我們為人員(合作夥伴)組建了一個多對多的欄位,該團隊將與我們合作完成該任務。
現在,我們將向合作夥伴模型加入與該關係相反的內容,以便我們可以為每個合作夥伴看到他們所涉及的所有任務。
編碼風格指南建議為每個模型建立一個 Python 檔案。因此,我們將加入一個 models/res_partner_model.py 檔案,以在合作夥伴上實施我們的擴展。
編輯 models/__init__.py 檔案以導入這個新的程式碼檔案。內容應該如下所示:
from . import todo_task_model from . import res_partner_model
現在使用以下程式碼建立models / res_partner_model.py:
from odoo import fields, models class ResPartner(models.Model): _inherit = 'res.partner' todo_ids = fields.Many2many( 'todo.task', string="To-do Teams")
使用 _inherit 類別屬性來聲明要擴展的模型。
請注意,我們沒有使用任何其他類別屬性,甚至沒有使用 _name。這是不需要的,除非我們想對其中的任何一個進行更改。
_name 是模型標識符; 如果我們試圖改變它會發生什麼?事實上,它會建立一個新的模型,它是繼承的模型的副本。這稱為原型繼承。
您可以想像在中央註冊表中的模型定義的參考,並對其進行"就地"更改。這些可以是加入欄位,修改現有欄位,修改模型類屬性,甚至是具有商務邏輯的方法。
在 To-Do 模組中,我們加入了一個新欄位 todo_ids,其中包含任務和合作夥伴之間的關係的對立,在待辦任務模型中定義。
這裡有一些細節正在被 ORM 自動處理,但它們現在與我們無關。在未來會對它們進行更詳細的介紹。
擴展現有欄位遵循類似的邏輯:我們只需要使用要修改的屬性。所有被省略的屬性將保持原始值。
要將新的 Model 欄位加入到資料庫表中,我們現在應該執行模組升級。
如果一切按預期進行,則可在 Technical | Database Structure | Models 看到 res.partner model 所新增的欄位。
視圖層
視圖層描述使用者界面。視圖是使用 XML 定義,Web client 框架使用 XML 來產生 data-aware HTML 視圖。
我們有菜單項可以啟動渲染視圖的動作。例如,Users 菜單項處理也稱為 Users 的操作,該操作進而呈現一系列視圖。
有幾種可用的視圖類型,例如列表視圖和表單視圖,並且在右上方的搜索框中提供的 filter 選項也是特定類型的視圖稱為搜索視圖。
Odoo 開發指南規定,定義使用者界面的 XML 檔案應放置在 views/ 子目錄中。
讓我們開始為我們的待辦事宜應用程式建立使用者界面。
在接下來的部分中,我們將逐步改進並頻繁進行模組升級,如果升級由於 XML 錯誤而失敗,請不要驚慌!
仔細閱讀服務器日誌中的錯誤消息;它應該指出你的問題在哪裡。
如果有問題,只需將最後編輯的 XML 部分註釋掉或從 __manifest__.py 中刪除 XML 檔案並重複升級即可。服務器應該會正確啟動。
新增選單項目
有了儲存資料的地方,接著希望在使用者界面上可以看到它們。首先要做的是加入相應的菜單選項。
建立 views/todo_menu.xml 檔案來定義一個菜單項及其執行的操作:
<?xml version="1.0"?> <odoo> <!-- Action to open To-do Task list --> <act_window id="action_todo_task" name="To-do Task" res_model="todo.task" view_mode="tree,form" /> <!-- Menu item to open To-do Task list --> <menuitem id="menu_todo_task" name="Todos" action="action_todo_task" /> </odoo>
使用者界面(包括菜單選項和操作)儲存在資料庫的資料表中。
XML 檔案是一個資料檔案,用於在安裝或升級插件模組時將這些定義載入到資料庫中。
上面的程式碼是一個 Odoo 資料檔案,描述了要加入到 Odoo 的兩條記錄:
- <act_window> 元素定義了一個客戶端窗口操作,該操作將打開 todo.task 模型,並啟用樹和表單視圖
- <menuitem> 定義了一個呼叫 action_todo_task 動作的頂級菜單項,該動作在之前定義
兩者都包含一個 id 屬性。這個 id 屬性稱為 XML ID,非常重要;
它是唯一標識模組內的每個資料元素,並且是其他元素可以用來引用它的方法。在這種情況下,元素需要引用要處理的動作,因此需要使用 <act_window> XML ID。
To-Do 模組還不知道這個新的 XML 資料檔案。因此需要使用 data 屬性在 __manifest__.py 檔案中宣告。它是安裝或升級時要由模組載入的資料檔案的列表。
現在將此屬性加入到清單的字典中:
'data': ['views/todo_menu.xml'],
我們需要再次升級模組才能使這些更改生效。轉到 Todos 頂部菜單,你應該看到我們的新菜單選項可用。
點擊它會顯示一個基本的列表視圖,這是一個自動產生的表單視圖:
即使我們沒有定義我們的使用者界面視圖,Odoo 會自動產生的列表和表單視圖,並且允許我們開始編輯資料。
建立表單視圖
所有視圖都儲存在資料庫中的 ir.ui.view 模型中。幫 To-Do 模組加入一個視圖,首先要在一個 XML 檔案中聲明一個描述視圖的元素,這個 XML 檔案將在模組安裝時載入到資料庫中。
加入這個新的 views/todo_view.xml 檔案來定義我們的表單視圖:
<?xml version="1.0"?> <odoo> <record id="view_form_todo_task" model="ir.ui.view"> <field name="name">To-do Task Form</field> <field name="model">todo.task</field> <field name="arch" type="xml"> <form string="To-do Task"> <group> <field name="name"/> <field name="is_done"/> <field name="active" readonly="1"/> </group> </form> </field> </record> </odoo>
ir.ui.view 記錄具有三個欄位:<name>,<model> 和 <arch>。另一個重要元素是 <record id>,它定義了一個 XML ID,可讓其他記錄來引用它。
視圖是以 todo.task 模型命名的任務表單。name 欄位僅供參考;它不必是唯一的,這個名字可以完全省略。忽略的話 Odoo 會從 模型名稱 和 視圖類型 自動產生 name。
最重要的欄位是 <arch>,因為它包含視圖定義。<form> 標籤則定義視圖類型,一般情況下包含三個欄位。
我們將 readonly 屬性加到 active 欄位,以便在使用者界面中使其成為 read-only。
記得將這個新檔案加入到清單檔案中的 data 中; 否則,我們的模組將不知道它,它不會被載入:
'data': [ 'views/todo_menu.xml', 'views/todo_view.xml', ],
請記住,修改後要重新載入到 Odoo 的資料庫,需要升級模組。要查看 Web 客戶端中的更改,表單需要重新載入。
再次點擊打開它的菜單選項或重新載入瀏覽器頁面(大多數瀏覽器中的F5)。
商務檔案格式的表單視圖
前面的部分提供了基本的表單視圖,但我們可以對其進行一些改進。Odoo 具有模仿紙頁的風格。該表單包含兩個元素:
- <header> 包含操作按鈕
- <sheet> 包含資料欄位
我們現在可以用這個 <sheet> 代替前面部分定義的 <form>:
<form> <header> <!-- Buttons go here--> </header> <sheet> <!-- Content goes here: --> <group> <field name="name"/> <field name="is_done"/> <field name="active" readonly="1"/> </group> </sheet> </form>
加入執行按鈕
表單可以有按鈕來執行操作。這些按鈕可以執行 window actions,例如打開另一個 window 或執行模型中定義的 Python 函數。
它們可以放在表單的任何位置,但對於 document-style 的表單,推薦的位置是 <header> 部分。
對於我們的應用程式,我們將加入一個按鈕來清除完成的任務。該按鈕將執行 do_clear_done 對象方法:
<header> <button name="do_clear_done" type="object" string="Clear Done" /> </header>
一個按鈕的基本屬性由以下幾部分組成:
- string 包含要在按鈕上顯示的文字
- type 執行的操作類型
- name 該操作的標識符
- class 是 HTML 中的 CSS 樣式
使用 <group> 標籤去規劃表單
<group> 標籤允許您組織表單內容。在 <group> 元素內放置 <group> 元素會在外部的 group 內建立一個兩行佈局 (two-column layout)。
建議 group 元素具有名稱屬性,以便其他模組更容易擴展它們。我們將用它來更好地組織我們的內容。讓我們更改表單的內容:
<sheet> <group name="group_top"> <group name="group_left"> <field name="name" /> <field name="user_id" /> <field name="is_done" /> </group> <group name="group_right"> <field name="date_deadline" /> <field name="team_ids" widget="many2many_tags" /> <field name="active" readonly="1" /> </group> </group> </sheet>
完整的表單視圖
最後 todo.task 的表單視圖應該看起來如下所示:
<form> <header> <button name="do_clear_done" type="object" string="Clear Done" /> <!--還未實作 business logic--> </header> <sheet> <group name="group_top"> <group name="group_left"> <field name="name" /> <field name="user_id" /> <field name="is_done" /> </group> <group name="group_right"> <field name="date_deadline" /> <field name="team_ids" widget="many2many_tags" /> <field name="active" readonly="1" /> </group> </group> </sheet> </form>
執行按鈕的動作還未完成,因此接下來還需要增加商務邏輯。