第九章:計算字段和Onchanges?

模型之間的關系 是任何Odoo模塊的關鍵組成部分。它們對于任何業務場景的建模都是必要的。然而,我們可能希望在給定模型內部的字段之間建立鏈接。有時,一個字段的值是由其他字段的值決定的,而有時我們希望幫助用戶進行數據輸入。

這些情況是由計算字段和onchange的概念支持的。雖然本章不是技術上復雜的,但是這兩個概念的語義非常重要。這也是我們第一次編寫Python邏輯。到目前為止,我們除了類定義和字段聲明之外還沒有編寫任何東西。

計算字段?

參考 : 有關此主題的文檔可以在 計算字段 中找到。

注解

目標 :本節結束時:

  • 在物業模型中,應該計算總面積和最佳報價:

計算字段
  • 在物業報價模型中,應該計算有效日期并進行更新:

使用反函數計算字段

在我們的房地產模塊中,我們定義了居住面積和花園面積。因此,將總面積定義為兩個字段的和是很自然的。我們將使用計算字段的概念來實現這一點,即給定字段的值將從其他字段的值計算得出。

到目前為止,字段直接存儲在數據庫中并直接從數據庫中檢索。字段也可以進行 計算 。在這種情況下,字段的值不是從數據庫中檢索出來的,而是通過調用模型的方法即時計算出來的。

要創建一個計算字段,需要創建一個字段并將其屬性 compute 設置為一個方法的名稱。計算方法應該為 self 中的每個記錄設置計算字段的值。

按照慣例, compute 方法是私有的,意味著它們不能從展示層調用,只能從業務層調用(參見: 第一章:架構概述 )。私有方法的名稱以下劃線 _ 開頭。

依賴項?

計算字段的值通常取決于計算記錄中其他字段的值。ORM希望開發人員使用裝飾器 depends() 在計算方法中指定這些依賴關系。ORM使用給定的依賴關系來觸發字段的重新計算,每當它的某些依賴關系被修改時:

from odoo import api, fields, models

class TestComputed(models.Model):
    _name = "test.computed"

    total = fields.Float(compute="_compute_total")
    amount = fields.Float()

    @api.depends("amount")
    def _compute_total(self):
        for record in self:
            record.total = 2.0 * record.amount

注解

self 是一個集合。

對象 self 是一個 記錄集 ,即一個有序的記錄集合。它支持集合的標準Python操作,例如 len(self)iter(self) ,以及額外的集合操作,如 recs1 | recs2 。

在迭代 self 時,每次返回一個記錄,每個記錄本身是一個大小為1的集合。您可以使用點符號(例如 record.name )訪問/分配單個記錄上的字段。

Odoo 中有很多計算字段的示例。 這里 是一個簡單的示例。

Exercise

計算總面積。

  • total_area 字段添加到 estate.property 中。它被定義為 living_areagarden_area 的總和。

  • 在表單視圖中添加字段,如本節 **目標 ** 部分的第一張圖片所示。

對于關系字段,可以使用字段路徑作為依賴項::

description = fields.Char(compute="_compute_description")
partner_id = fields.Many2one("res.partner")

@api.depends("partner_id.name")
def _compute_description(self):
    for record in self:
        record.description = "Test for partner %s" % record.partner_id.name

這個例子是使用 Many2one 來說明的,但是同樣適用于 Many2many 或者 One2many 。一個 例子可以在 這里 找到。

讓我們在我們的模塊中嘗試以下練習!

Exercise

計算最佳報價。

  • best_price 字段添加到 estate.property 。它被定義為報價中最高(即最大)的 price 。

  • 按照本節 目標 部分的第一張圖片所示,將該字段添加到表單視圖中。

提示:你可能想嘗試使用 mapped() 方法。查看 這里 獲取一個簡單的示例。

反函數?

您可能已經注意到,計算字段默認情況下是只讀的。這是預期的,因為用戶不應該設置值。

在某些情況下,直接設置值仍然很有用。在我們的房地產示例中,我們可以為報價定義有效期限并設置有效日期。我們希望能夠設置持續時間或日期,而不會影響另一個。

為了支持這一點,Odoo 提供了使用 inverse 函數的能力::

from odoo import api, fields, models

class TestComputed(models.Model):
    _name = "test.computed"

    total = fields.Float(compute="_compute_total", inverse="_inverse_total")
    amount = fields.Float()

    @api.depends("amount")
    def _compute_total(self):
        for record in self:
            record.total = 2.0 * record.amount

    def _inverse_total(self):
        for record in self:
            record.amount = record.total / 2.0

一個例子可以在這里找到: 這里。

計算方法設置字段,而反向方法設置字段的依賴關系。

請注意,當保存記錄時,會調用 inverse 方法,而在其依賴項發生更改時,會調用 compute 方法。

Exercise

計算優惠的有效日期。

  • estate.property.offer 模型中添加以下字段:

字段

類型

默認

有效性

整數

7

截止日期

日期

其中 date_deadline 是一個計算字段,它被定義為從報價中的兩個字段相加而來: create_datevalidity 。定義一個適當的反函數,以便用戶可以設置日期或有效期。

提示: create_date 只有在記錄創建時才會填充,因此您需要一個備用方案來防止在創建時崩潰。

  • 在表單視圖和列表視圖中添加如本節 目標 第二張圖片所示的字段。

附加信息?

計算字段默認情況下 不會被存儲 在數據庫中。因此,除非定義了 search 方法,否則 無法 在計算字段上進行搜索。這個主題超出了本培訓的范圍,因此我們不會涉及它??梢栽?`這里 <https://github.com/odoo/odoo/blob/f011c9aacf3a3010c436d4e4f408cd9ae265de1b/addons/event/models/event_event.py#L188>`__找到一個示例。

另一種解決方案是使用 store=True 屬性存儲字段。雖然這通常很方便,但要注意可能會增加模型的計算負載。讓我們重用我們的示例:

description = fields.Char(compute="_compute_description", store=True)
partner_id = fields.Many2one("res.partner")

@api.depends("partner_id.name")
def _compute_description(self):
    for record in self:
        record.description = "Test for partner %s" % record.partner_id.name

每當合作伙伴的“名稱”更改時,所有引用它的記錄的“描述”都會自動重新計算!當需要重新計算數百萬條記錄時,這可能會很快變得難以承受。

值得注意的是,計算字段可以依賴于另一個計算字段。ORM 足夠智能,可以正確地按正確順序重新計算所有依賴項… 但有時會以性能降低為代價。

在定義計算字段時,通常需要考慮性能問題。計算字段越復雜(例如具有許多依賴關系或計算字段依賴于其他計算字段),計算所需的時間就越長。在定義計算字段之前,始終要花些時間評估其成本。大多數情況下,只有在您的代碼到達生產服務器時,您才會意識到它會減慢整個過程的速度。這很不好 :-(

觸發器?

參考 : 有關此主題的文檔可以在 onchange() 中找到:

注解

目標 :本節結束時,啟用花園將設置默認面積為10,方向為北。

觸發事件

在我們的房地產模塊中,我們還想幫助用戶進行數據輸入。當設置了“花園”字段時,我們希望為花園面積和朝向提供默認值。此外,當取消設置“花園”字段時,我們希望花園面積重置為零,并刪除朝向。在這種情況下,給定字段的值會修改其他字段的值。

“onchange”機制提供了一種方式,使得客戶端界面在用戶填寫字段值時,無需將任何內容保存到數據庫即可更新表單。為了實現這一點,我們定義一個方法,其中“self”表示表單視圖中的記錄,并使用“ onchange() ”進行裝飾,以指定觸發它的字段。您對“self”所做的任何更改都將反映在表單上:

from odoo import api, fields, models

class TestOnchange(models.Model):
    _name = "test.onchange"

    name = fields.Char(string="Name")
    description = fields.Char(string="Description")
    partner_id = fields.Many2one("res.partner", string="Partner")

    @api.onchange("partner_id")
    def _onchange_partner_id(self):
        self.name = "Document for %s" % (self.partner_id.name)
        self.description = "Default description for %s" % (self.partner_id.name)

在這個例子中,改變合作伙伴也會改變名稱和描述的值。用戶可以選擇是否在之后更改名稱和描述的值。同時請注意,我們沒有在 self 上循環,因為該方法僅在表單視圖中觸發,其中 self 始終是單個記錄。

Exercise

設置花園面積和朝向的值。

estate.property 模型中創建一個 onchange ,以便在將花園設置為 True 時為花園區域(10)和方向(North)設置值。當取消設置時,清除這些字段。

附加信息?

Onchanges 方法也可以返回一個非阻塞的警告信息( 示例)。

如何使用它們??

在使用計算字段和onchange時沒有嚴格的規則。

在許多情況下,計算字段和onchange都可以用于實現相同的結果。始終優先選擇計算字段,因為它們也在表單視圖之外觸發。永遠不要使用onchange向您的模型添加業務邏輯。這是一個非常糟糕的想法,因為onchange不會在以編程方式創建記錄時自動觸發;它們只在表單視圖中觸發。

計算字段和onchange的常見陷阱是試圖通過添加過多的邏輯來變得“過于聰明”。這可能會產生與預期相反的結果:最終用戶會因所有自動化而感到困惑。

計算字段往往更容易調試:這樣的字段由給定的方法設置,因此很容易跟蹤何時設置值。另一方面,onchange 可能會令人困惑:很難知道 onchange 的范圍。由于多個 onchange 方法可能設置相同的字段,因此很容易跟蹤值來自何處。

使用存儲的計算字段時,請注意依賴關系。當計算字段依賴于其他計算字段時,更改一個值可能會觸發大量的重新計算。這會導致性能下降。

下一章 中,我們將看到如何在按鈕被點擊時觸發一些業務邏輯。