Odoo 開發(fā)手冊連載四 Odoo 12 開發(fā)之模塊繼承

本文為最好用的免費ERP系統(tǒng)Odoo 12開發(fā)手冊系列文章第四篇。

英文原著為Odoo 12 Development Essentials - Fourth Edition By Daniel Reis

?第一章 使用開發(fā)者模式快速入門 Odoo 12
?第二章 Odoo 12開發(fā)之開發(fā)環(huán)境準備
?第三章 Odoo 12 開發(fā)之創(chuàng)建第一個 Odoo 應用
?第四章 Odoo 12 開發(fā)之模塊繼承
?第五章 Odoo 12開發(fā)之導入、導出以及模塊數(shù)據(jù)
?第六章 Odoo 12開發(fā)之模型 - 結(jié)構(gòu)化應用數(shù)據(jù)
?第七章 Odoo 12開發(fā)之記錄集 - 使用模型數(shù)據(jù)
?第八章 Odoo 12開發(fā)之業(yè)務邏輯 - 業(yè)務流程的支持
?第九章 Odoo 12開發(fā)之外部 API - 集成第三方系統(tǒng)
?第十章 Odoo 12開發(fā)之后臺視圖 - 設計用戶界面
?第十一章 Odoo 12開發(fā)之看板視圖和用戶端 QWeb
?第十二章 Odoo 12開發(fā)之報表和服務端 QWeb
?第十三章 Odoo 12開發(fā)之創(chuàng)建網(wǎng)站前端功能
?第十四章 Odoo 12開發(fā)之部署和維護生產(chǎn)實例

Odoo 的一個強大功能是無需直接修改底層對象就可以添加功能。這是通過其繼承機制來實現(xiàn)的,采取在已有對象之上修改層來完成。這種修改可以在不同層上進行-模型層、視圖層和業(yè)務邏輯層。我們創(chuàng)建新的模塊來做出所需修改而無需在原有模塊中直接修改。

上一篇文章中我們從零開始創(chuàng)建了一個新應用,本文中我們學習如何通過繼承已有的核心應用或第三方模塊來創(chuàng)建新的模塊。實現(xiàn)以上本文將主要涵蓋:

  • 原模型擴展,為已有模型添加功能
  • 修改數(shù)據(jù)記錄來繼承視圖,添加功能或修改數(shù)據(jù)來修改其它模塊創(chuàng)建的數(shù)據(jù)記錄
  • 其它模型繼承機制,如代理繼承和 mixin 類
  • 繼承 Python 方法來為應用業(yè)務邏輯添加功能
  • 繼承 Web 控制器和模板來為網(wǎng)頁添加功能

開發(fā)準備

本文要求可通過命令行來啟動 Odoo 服務。代碼將在第三章 Odoo 12 開發(fā)之創(chuàng)建第一個 Odoo 應用的基礎上進行修改。通過該文的學習現(xiàn)在我們已經(jīng)有了library_app模塊。本系列文章代碼請參見 GitHub 倉庫。

學習項目-繼承圖書館應用

在第三章 Odoo 12 開發(fā)之創(chuàng)建第一個 Odoo 應用中我們創(chuàng)建了一個圖書應用的初始模塊,可供查看圖書目錄。現(xiàn)在我們要創(chuàng)建一個library_member模塊,來對圖書應用進行擴展以讓圖書會員可以借書。它繼承 Book 模型,并添加一個圖書是否可借的標記。該信息將在圖書表單和圖書目錄頁顯示。

應添加圖書會員主數(shù)據(jù)模型Member,類似 Partner 來存儲個人數(shù)據(jù),如姓名、地址和 email,還有一些特殊字段,如圖書會員卡號。最有效的方案是代理繼承,自動創(chuàng)建圖書會員記錄并包含關聯(lián) Partner 記錄。該方案使得所有的Partner 字段在 Member 中可用,沒有任何數(shù)據(jù)結(jié)構(gòu)上的重復。

我們還要在借書表單中為會員提供消息和社交功能,包括計劃活動組件來實現(xiàn)更好地協(xié)作。我們還要添加會員從圖書館中借書的功能,但暫不涉及。以下是當前所要修改內(nèi)容的總結(jié):

  • 圖書
    • 添加一個Is Available? 字段。現(xiàn)在通過手動管理,以后會自動化
    • 擴展 ISBN 驗證邏輯來同時支持10位數(shù)的ISBN
    • 擴展圖書目錄頁來分辨不可借閱圖書并允許用戶過濾出可借圖書
  • 會員
    • 添加一個新模型來存儲姓名、卡號和 Email、地址一類的聯(lián)系信息
    • 添加社交討論和計劃活動功能

首先在library_app同級目錄創(chuàng)建一個library_member目錄來作為擴展模塊,并在其中添加兩個文件,一個init.py空文件和一個包含如下內(nèi)容的manifest.py文件:

{
    'name': 'Library Members',
    'description': 'Manage people who will be able to borrow books.',
    'author': 'Alan Hou',
    'depends': ['library_app'],
    'application': False,
}

原模型繼承

第一步我們來為Book模型添加is_available布爾型字段。這里使用經(jīng)典的 in-place 模型繼承。該字段值可通過圖書借出和歸還記錄自動計算,但現(xiàn)在我們先使用普通字段。要繼承已有模型,需要在 Python 類中添加一個_inherit 屬性來標明所繼承的模型。新類繼承父 Odoo 模型的所有功能,僅需在其中聲明要做的修改。在任何地方使用該模型修改都可用,可以認為這類繼承是對已有模型的引用并在原處做了一些修改。

為模型添加字段

通過 Python 類來新建模型,繼承模型同樣是通過 Python 以及 Odoo 自有的繼承機制,即_inherit 類屬性。該屬性標明所繼承的模型。新的類繼承父 Odoo 模型的所有功能,僅需聲明要做修改的部分。編碼指南推薦為每個模型創(chuàng)建一個 Python 文件,因此我們添加library_member/models/library_book.py文件來繼承原模型,首先創(chuàng)建init.py文件來導入該文件:

1、添加library_member/init.py文件來導入 models 子文件夾

from . import models

2、添加library_member/models/init.py文件子文件夾中的代碼文件:

from . import library_book

3、創(chuàng)建library_member/models/library_book.py文件來繼承l(wèi)ibrary.book模型:

from odoo import fields, models

class Book(models.Model):
    _inherit = 'library.book'
    is_available = fields.Boolean('Is Available?')

使用_inherit類屬性來聲明所繼承模型。注意我們并沒有使用到其它類屬性,甚至是_name 也沒使用。除非想要做出修改,否則不需要使用這些屬性。

??_name是模型標識符,如果修改會發(fā)生什么呢?其實你可以修改,這時它會創(chuàng)建所繼承模型的拷貝,成為一個新模型。這叫作原型繼承,本文后面會討論。

可以把這個想成是對模型定義的一個引用,在原處做了一個修改??梢蕴砑幼侄?、修改已有字段、修改模型類屬性甚至是包含業(yè)務邏輯的方法。要在數(shù)據(jù)表中添加新的模型字段需要安裝該模塊。如果一切順利,通過Settings > Technical > Database Structure > Models菜單查看library.book模型即可看到該字段。

~/odoo-dev/odoo/odoo-bin -d dev12 -i library_member
Odoo 12圖書項目is_available字段添加

修改已有字段

通過上面部分可以看到向已有模型添加新字段非常簡單。有時還要對已有字段進行修改,也非常簡單。在繼承模型時,可對已有字段疊加修改,也就是說僅需定義要增加或修改的字段屬性。

我們將對原來創(chuàng)建的library_app模塊的 Book模型做兩處簡單修改:

  • 為isbn字段添加一條提示,說明同時支持10位數(shù)的 ISBN(稍后會實現(xiàn)該功能)
  • 為publisher_id字段添加數(shù)據(jù)庫索引,以提升搜索效率

編輯library_member/models/library_book.py文件,并在library.book 模型中添加如下代碼:

class Book(models.Model):
...
    isbn = fields.Char(help="Use a valid ISBN-13 or ISBN-10.")
    publisher_id = fields.Many2one(index=True)

這會對字段進行指定屬性修改,未涉及的屬性不會被修改。升級模塊,進入圖書表單,將鼠標懸停在 ISBN 字段上,就可以看到所添加的提示信息了。index=True這一修改不太容易發(fā)現(xiàn),通過Settings > Technical > Database Structure > Models菜單下的字段定義中可進行查看。

Odoo 12圖書項目 ISBN 提示

修改視圖和數(shù)據(jù)

模塊中視圖和其它數(shù)據(jù)構(gòu)件也可通過繼承來修改。就視圖而言,通常需要添加功能。視圖的展示結(jié)構(gòu)在 arch 字段中使用 XML定義。這一 XML 數(shù)據(jù)可通過定位到所需修改的地方來進行繼承,然后聲明需執(zhí)行的操作,如在該處添加 XML 元素。對于剩余的數(shù)據(jù)元素,它們代表寫入數(shù)據(jù)庫中的記錄,繼承模型可通過寫操作來修改它們的值。

繼承視圖

表單、列表和搜索視圖通過arch XML結(jié)構(gòu)定義。要繼承視圖,就要一種修改 XML 的方式,也即定位 XML 元素然后對該處進行修改。視圖繼承的 XML 記錄和普通視圖中相似,多一個 inherit_id屬性來引用所要繼承的視圖。下面我們來繼承圖書視圖并添加is_available字段。

首先要查找待繼承的視圖的XML ID,通過Settings > Technical > User Interface > Views菜單來查看。圖書表單的XML ID是library_app.view_form_book。然后還要找到要插入修改的XML元素,我們在 ISBN 字段之后添加Is Available?通常通過name 屬性定位元素,此處為<field name="isbn" />。

我們添加views/book_view.xml文件來繼承 Partner 視圖,加入如下內(nèi)容:

<?xml version="1.0"?>
<odoo>
    <record id="view_form_book_extend" model="ir.ui.view">
        <field name="name">Book: add Is Available? field</field>
        <field name="model">library.book</field>
        <field name="inherit_id" ref="libary_app.view_form_book" />
        <field name="arch" type="xml">
            <field name="isbn" position="after">
                <field name="is_available" />
            </field>
        </field>
    </record>
</odoo>

以上代碼中,我們高亮顯示了繼承相關的元素。inherit_id記錄字段通過 ref 屬性指向繼承視圖的外部標識符,我們將在第五章 Odoo 12開發(fā)之導入、導出以及模塊數(shù)據(jù)討論外部標識符詳情。視圖使用 XML 定義并存儲在結(jié)構(gòu)字段 arch 中。要繼承一個視圖,先定位要擴展的節(jié)點,然后執(zhí)行要做的操作,如添加 XML 元素。

定位節(jié)點的最簡單方法是使用唯一標識屬性,通常是 name。然后添加定位屬性,聲明要做的修改。本例中繼承節(jié)點是name="isbn"元素,修改是在選定元素后加一段 XML:

<field name="isbn" position="after">
    <!-- 此處添加修改內(nèi)容 -->
</field>

除string 屬性外的任意 XML 元素和屬性可作為繼承節(jié)點,字符串屬性會被翻譯成用戶所使用的語言,因此不能作為節(jié)點選擇器。

??在9.0以前,string 屬性(顯示標簽文本)也可作為繼承定位符。在9.0之后則不再允許。這一限制主要源自這些字符串的語言翻譯機制。

一旦 XML 節(jié)點被選為繼承點,需要指明要執(zhí)行的繼承操作。這通過 position 屬性實現(xiàn):

  • inside(默認值):在所選節(jié)點內(nèi)添加內(nèi)容,這一節(jié)點應是<group>或<page>一類的容器
  • after:在選定節(jié)點之后向父節(jié)點添加內(nèi)容
  • before:在選定節(jié)點之前向父節(jié)點添加內(nèi)容
  • replace:替換所選節(jié)點。若使用空元素則會刪除該元素。Odoo 之后還允許使用其它標記來包裹元素,通過在內(nèi)容中使用$0來表示被替換的元素。
  • attributes:修改匹配元素屬性值。內(nèi)容中應包含帶有一個或多個<attribute name="attr-name">value</attribute>元素。如<attribute name="invisible">True</attribute>,若不帶內(nèi)容,如<attribute name="invisible" />則 attribute 會從所選元素中刪除。

小貼士:通過position="replace"可刪除 XML 元素,但應避免這么做。這么做會破壞其它依賴所刪除節(jié)點、將其作為占位符添加元素的模塊。一個替代方案是,讓該元素不可見。

除了attributes定位,上述定位符可與帶position="move"的子元素合并。效果是將子定位符目標節(jié)點移到父定位符目錄位置。

??Odoo 12中的修改
position="move"子定位符是 Odoo 12中新增的,之前的版本中沒有

例如:

<field name="target_field" position="after">
    <field name="my_field" position="move"/>
</field>

其它視圖類型,如列表和搜索視圖,也有 arch 字段,可以表單視圖同樣的方式被繼承。

在聲明文件data 中加入該視圖文件并更新模塊即可:

Odoo 12圖書項目添加 is_available

使用 XPath 選取繼承點

有時可能沒有帶唯一值的屬性來用作 XML 節(jié)點選擇器。在所選元素沒有 name 屬性時可能出現(xiàn)這一情況,如<group>、<notebook>或<page>視圖元素。另外就是有多個帶有相同 name 屬性的元素,比如在看板 QWeb 視圖中相同字段可能在同一 XML 模板中被多次包含。

在這些情況下我們就需要更高級的方式來定位待擴展 XML 元素。定位 XML 中元素的一種自然方式是 XPath 表達式。以上一篇文章中定義的 Book 表單視圖為例,定位<field name="isbn">元素的 XPath 表達式是//field[@name]='isbn'。該表達式查找 name 屬性等于 isbn 的<field>元素。

前一部分對圖書表單視圖繼承的 XPath 寫法是:

<xpath expr="http://field[@name='isbn']" position="after">
    <field name="is_available" />
</xpath>

XPath 語法的更多知識請見 Python 官方文檔。

如果 XPath 表達式匹配到了多個元素,僅會選取第一個作為擴展目錄。所以表達式應越精確越好,使用唯一屬性。name 屬性最易于確保找到精確元素作為擴展點,因此在創(chuàng)建視圖 XML 元素時添加唯一標識符就非常重要。

修改數(shù)據(jù)

普通數(shù)據(jù)記錄不同于視圖,它沒有 XML arch 結(jié)構(gòu),也不能使用 XPath 來進行擴展。但還是可以通過替換字段值來進行修改。

<record id="x" model="y">數(shù)據(jù)加載元素實際是對 y 模型進行插入或更新操作。若不存在記錄 x,則被創(chuàng)建,否則被更新/覆蓋。其它模塊中的記錄可通過<module>.<identifier>全局標識符訪問,因此可以在我們的模塊中重寫其它模塊中已寫入的數(shù)據(jù)。

??點號是保留符號,用于分隔模塊名和對象標識符,所以在標識符名中不要使用點號,而應使用下劃線字符。

舉個例子,我們將 User 安全組的名稱修改為 Librarian,對應修改library_app.library_group_user記錄。添加library_member/security/library_security.xml并加入如下代碼:

<odoo>
    <!-- Modify Group name -->
    <record id="library_app.library_group_user" model="res.groups">
        <field name="name">Librarian</field>
    </record>
</odoo>

這里我們使用了一個<record>元素,僅寫了 name 字段??梢哉J為這是對所選字段的一次寫操作。

小貼士:使用<record>元素時,可以選擇要執(zhí)行寫操作的字段,但對 shortcut 元素則并非如此,如<menuitem>和<act_window>。它們需要提供所有的屬性,漏寫任何一個都會將對應字段置為空值。但可使用<record>為原本通過 shortcut 元素創(chuàng)建的字段設置值。

在聲明文件data 中加入security/library_security.xml并更新模塊即可看到效果。

Odoo 12圖書項目 Librarian

其它模型繼承機制

前面我們介紹了模型的基本繼承,在官方文檔中稱為經(jīng)典繼承。這是最常用的繼承方式,最容易想到的就是in-place繼承。獲取模型并對其繼承。添加的新功能會自動添加到已有模型中,而不會創(chuàng)建新模型。

可以為_inherit 屬性傳入多個值來繼承多個父模型。大多數(shù)情況下這通過 mixin 類完成,mixin類是實現(xiàn)可復用的通用功能。也可以像普通模型那樣獨立使用,像是一個功能容器,可隨時加到其它模型中。

如在使用_inherit 屬性的同時還使用了與父模型不同的_name屬性,此時會復用所繼承并創(chuàng)建一個新的模型,并帶有自己的數(shù)據(jù)表和數(shù)據(jù)。官方文檔稱其為原型(prototype)繼承。下面我們會拿一個模型,并為其創(chuàng)建一個拷貝。在添加新功能時,只會被加到新模型中,而不會變更原模型。

此外還有代理(delegation)繼承,通過_inherits 屬性來使用(注意最后有一個 s)。這允許我們創(chuàng)建一個包含和繼承已有模型的新模型。新模型創(chuàng)建新記錄時,在原模型中也會被創(chuàng)建并使用many-to-one 字段關聯(lián)。查看新模型的人可以看到所有原模型和新模型中的字段,但在后臺兩個模型分別處理各自的數(shù)據(jù)。

下面我們一起來了解詳情。

使用原型繼承拷貝功能

前文我們繼承模型時使用了_inherit 屬性,創(chuàng)建一個類繼承l(wèi)ibrary.book 并添加了一些功能。類中沒有使用_name屬性,不指明即使用library.book。如果設置了不個不同值的_name 屬性,會通過從所繼承的模型拷貝功能創(chuàng)建新模型。

在實際開發(fā)中,這類繼承一般通過抽象 mixin 類,很少這樣直接繼承普通模型,因為這樣會創(chuàng)建冗余的數(shù)據(jù)結(jié)構(gòu)。Odoo 還有一種代理繼承機制可避免這類數(shù)據(jù)結(jié)構(gòu)冗余,所以普通模型通常會使用這種方法來做繼承。

使用代理繼承內(nèi)嵌模型

使用代理繼承無需復制數(shù)據(jù)即可在數(shù)據(jù)庫中復用數(shù)據(jù)結(jié)構(gòu),這通過將一個模型嵌入另一個來實現(xiàn)。UML 中這種稱作組合(composition)關系:父類無需子類即可存在,而子類必須要有父類才能存在。

比如,對于內(nèi)核 User模型,每條記錄包含一條 Partner 記錄,因此包含 Partner 中的所有字段以及User自身的一些字段。

在圖書項目中,我們要添加一個圖書會員模型。會員有會員卡并通過會員卡借閱讀書。我們要記錄卡號,還要存儲email 和地址這類個人信息。Partner 模型已包含聯(lián)系和地址信息,所以最好是進行復用,而不去創(chuàng)建重復的數(shù)據(jù)結(jié)構(gòu)。

為會員模型創(chuàng)建library_member/models/library_member.py文件并加入如下代碼:

from odoo import fields, models

class Member(models.Model):
    _name = 'library.member'
    _description = 'Library Member'
    card_number = fields.Char()
    partner_id = fields.Many2one(
        'res.partner',
        delegate=True,
        ondelete='cascade',
        required=True)

使用代理繼承,library.member 中嵌入了繼承模型res.partner,因此在創(chuàng)建會員記錄時,一個關聯(lián)的 Partner 會自動被創(chuàng)建并通過partner_id字段引用。

??Odoo 8中的修改
在新的 API 中引入了delegate=True字段屬性。在那之前,代理繼承通過模型屬性來定義,類似_inherits = {'res.partner': 'partner_id'}。現(xiàn)在仍支持這一寫法,官網(wǎng)中還有相應介紹,但delegate=True 字段屬性可起到相同效果且使用更簡單。

透過代理機制,嵌套模型的所有字段就像父模型字段一樣自動可用。本例中,會員卡模型可使用 Partner 中的所有字段,如 name, address和 email,以及會員自身的獨有字段,如card_number。在后臺中,Partner 字段存儲在關聯(lián)的 Partner 記錄,沒有重復的數(shù)據(jù)結(jié)構(gòu)。

??對于模型方法則并非如此,Partner 模型中的方法在 Member 模型中不可使用。

與原型繼承相比,代理繼承的好處在于無需跨表重復像地址這樣的數(shù)據(jù)。任何需包含地址的新模型通過代理嵌入了 Partner 模型。如果在 Partner 中修改 address字段,在所有嵌入的模型中可以馬上使用。

小貼士:代理繼承可通過如下組合來進行替代:

  • 父模型中的一個 many-to-one 字段
  • 重載 create()方法自動創(chuàng)建并設置父級記錄
  • 父字段中希望暴露的特定字段的關聯(lián)字段

有時這比完整的代理繼承更為合適。例如res.company并沒有繼承res.partner,但使用到了其中好幾個字段。

不要忘記在library_member/model/init.py文件中加入:

from . import library_book
from . import library_member

要使用我們創(chuàng)建的 Member 模型,還要完成以下步驟:

  • 添加安全權限控制列表(ACL)
  • 添加菜單項
  • 添加表單和列表視圖
  • 更新manifest文件來聲明這些新增數(shù)據(jù)文件

讀者可以先嘗試自己添加,再來看下面的詳細步驟:

要創(chuàng)建安全ACL,創(chuàng)建library_member/security/ir.model.access.csv文件并加入如下代碼:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_member_user,Member User Access,model_library_member,library_app.library_group_user,1,1,1,0
access_member_manager,Member Manager Access,model_library_member,library_app.library_group_manager,1,1,1,1

要添加菜單項,創(chuàng)建library_member/views/library_menu.xml文件并加入如下代碼:

<odoo>
    <act_window id="action_library_member"
        name="Library Members"
        res_model="library.member"
        view_mode="tree,form" />
    <menuitem id="menu_library_member"
        action="action_library_member"
        parent="library_app.menu_library" />
</odoo>

要添加視圖,創(chuàng)建library_member/views/member_view.xml文件并加入如下代碼:

<?xml version="1.0" ?>
<odoo>
    <record id="view_form_member" model="ir.ui.view">
        <field name="name">Library Member Form View</field>
        <field name="model">library.member</field>
        <field name="arch" type="xml">
            <form>
                <group>
                    <field name="name" />
                    <field name="email" />
                    <field name="card_number" />
                </group>
            </form>
        </field>
    </record>
    <record id="view_tree_member" model="ir.ui.view">
        <field name="name">Library Member List View</field>
        <field name="model">library.member</field>
        <field name="arch" type="xml">
            <form>
                <group>
                    <field name="name" />
                    <field name="card_number" />
                </group>
            </form>
        </field>
    </record>
    <record id="view_tree_member" model="ir.ui.view">
        <field name="name">Library Member List View</field>
        <field name="model">library.member</field>
        <field name="arch" type="xml">
            <form>
                <group>
                    <field name="name" />
                    <field name="card_number" />
                </group>
            </form>
        </field>
    </record>
</odoo>

最后,編輯manifest文件來聲明這三個新文件:

    'data':[
...
        'security/ir.model.access.csv',
        'views/library_menu.xml',
        'views/member_view.xml',
    ]

如果編寫正確,在進行模型更新后即可使用新的圖書會員模型了。

~/odoo-dev/odoo/odoo-bin -d dev12 -u library_member
Odoo 12圖書項目圖書會員

使用 mixin類繼承模型

原型繼承主要用于支持 mixin 類。mixin 是基于 models.Abstract 的抽象的模型(而不是models.Model),它在數(shù)據(jù)庫中沒有實際的體現(xiàn),而是提供功能供其它模型復用(混合 mixed in)。Odoo 插件提供多種 mixin,最常的兩種由 Discuss 應用(mail 模塊)提供:

  • mail.thread提供在許多文檔表單下方或右側(cè)的消息面板功能,以及消息和通知相關邏輯。這在我們自己的模型中將經(jīng)常會添加,下面就來一起學習下。
  • mail.activity.mixin模型提供待辦任務計劃。

??Odoo 11中的修改
mail 模塊現(xiàn)在通過mail.activity.mixin抽象模型提供Activities任務管理功能。該功能在 Odoo 11中才添加,此前的版本中沒有。

我們一起來為 Member 模型添加上述兩種 mixin。社交消息功能由 mail 模塊的mail.thread模型提供,要將其加入自定義模型,應進行如下操作:

  1. 通過 mixin 模型 mail 為插件模塊添加依賴
  2. 讓類繼承mail.thread和mail.activity.mixin兩個 mixin 類
  3. 將message_follower_ids, message_ids和activity_id這些 mixin 的數(shù)據(jù)字段添加到表單視圖

對于第一步擴展模型需要在manifest.py文件中添加對 mail 的依賴。

'depends': ['library_app', 'mail'],

第二步中對 mixin 類的繼承通過_inherit屬性完成,應編輯library_member/models/library_member.py并添加如下代碼:

class Member(models.Model):
    _name = 'library.member'
    _description = 'Library Member'
    _inherit = ['mail.thread', 'mail.activity.mixin']
...

通過添加額外的這行代碼,我們的模型就會包含這些 mixin 的所有字段和方法。

第三步中向表單視圖添加相關字段,編輯library_member/views/member_view.xml文件并在表單最后添加如下代碼:

<odoo>
...
            <form>
...
                <!-- mail mixin fields -->
                <div class="oe_chatter">
                    <field name="message_follower_ids" widget="mail_followers" />
                    <field name="activity_ids" widget="mail_activity" />
                    <field name="message_ids" widget="mail_thread" />
                </div>
            </form>

mail 模塊還為這些字段提供了一些特定的網(wǎng)頁組件,以上代碼中已使用到。在升級模塊后會員表單將變成這樣:

Odoo 12圖書項目使用 Mixin 類繼承

有時普通用戶僅能訪問正在 follow 的記錄。在這些情況下我們應添加訪問記錄規(guī)則來讓用戶可以看到 follow 的記錄。本例中用不到這一功能,但可通過[('message_partner_ids', 'in', [user.partner_id.id])]或來進行添加。

繼承 Python 方法

Python 方法中編寫的業(yè)務邏輯也可以被繼承。Odoo 借用了 Python 已有的父類行為的對象繼承機制。

作為一個實際的例子,我們將繼承圖書 ISBN 驗證邏輯。在圖書應用中僅能驗證13位的 ISBN,但老一些的圖書可能只有10位數(shù)的 ISBN。我們將繼承_check_isbn()方法來完成這種情況的驗證。在library_member/models/library_book.py文件中添加如下方法:

from odoo import api, fields, models

class Book(models.Model):
...

    @api.multi
    def _check_isbn(self):
        self.ensure_one()
        isbn = self.isbn.replace('-', '')
        digits = [int(x) for x in isbn if x.isdigit()]
        if len(digits) == 10:
            ponderators = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            total = sum(a * b for a, b in zip(digits[:9], ponderators))
            check = total % 11
            return digits[-1] == check
        else:
            return super()._check_isbn()

要繼承方法,我們要重新定義該方法,可以使用 super()來調(diào)用已實現(xiàn)的部分。在這個方法中我們驗證是否為10位數(shù) ISBN,然后插入遺失的驗證邏輯。若不是10位,則進入原有的13位驗證邏輯。

如果想要進行測試甚至是書寫測試用例,可使用0-571-05686-5作為例子,該書是威廉·戈爾丁的《蠅王》。

??Odoo 11中的修改
從 Odoo 11開始,支持的主Python版本為 Python 3(Odoo 12中為 Python 3.5)。而此前的 Odoo 版本使用 Python 2,其中 super()需傳入類名和 self 兩個參數(shù),那么,上例中的代碼應修改為super(Book, self)._check_isbn()。

Odoo 12圖書項目10位 ISBN 驗證

繼承 Web 控制器和模板

Odoo 中的所有功能都帶有擴展性,web 功能也不例外,所以已有控制器和模塊都能被繼承。

作為示例,我們將繼承圖書目錄網(wǎng)頁,加入前面添加的圖書可用性信息:

  • 在控制器端添加對查詢參數(shù)的支持,訪問/library/books?available=1過濾出可借閱圖書
  • 在模板端,添加一個圖書不可用的表示

繼承網(wǎng)頁控制器

網(wǎng)頁控制器不應包含實際業(yè)務邏輯,僅集中于展示邏輯。我們可能會需要添加對額外 URL 參數(shù)甚至是路由的支持,來改變網(wǎng)頁的展示。我們將擴展/library/books來支持available=1參數(shù),以過濾出可借閱圖書。

要繼承已有控制器,需導入對應對象,然后用方法新增邏輯來進行實現(xiàn)。下面新增ibrary_member/controllers/main.py文件并加入如下代碼:

from odoo import http
from odoo.addons.library_app.controllers.main import Books

class BookExtended(Books):
    @http.route()
    def list(self, **kwargs):
        response = super().list(**kwargs)
        if kwargs.get('available'):
            Book = http.request.env['library.book']
            books = Book.search([('is_available', '=', True)])
            response.qcontext['books'] = books
        return response

我們要繼承的Books控制器在library_app/controllers/main.py中定義。因此需要通過odoo.addons.library_app.controllers.main導入。這和模型不同,模型可以通過 env 對象中的central registry 來引用任意模型類,而無需了解實現(xiàn)它的文件??刂破鳑]有這個,我們需要知道實現(xiàn)需繼承控制器的模塊和文件。

然后基于Books聲明了一個BooksExtended類,類名不具關聯(lián)性,僅用于繼承和擴展原類中定義的方法。

再后我們(重)定義了一個控制器方法 list()。它至少需要一個簡單的@http.route()裝飾器來保持路徑活躍。如果不帶參數(shù),將會保留父類中定義的路由。但也可以為@http.route() 裝飾器添加參數(shù),來重新定義或替換類路由。

在繼承的 list()方法中,一開始使用了 super()來運行已有代碼。處理結(jié)果返回一個 Response 對象,Response 帶有模塊要渲染的屬性 template,以及渲染使用的上下文qcontext。但還需要生成 HTML,僅會在控制器結(jié)束運行時生成。這也讓我們可以在最終渲染完成之前可以修改 Response 屬性。

list()方法帶有**kwargs參數(shù),捕獲所有kwargs字典中的參數(shù)。這些是 URL 中的參數(shù),如?available=1。方法檢測kwargs中available鍵的值,檢測到后改變qcontext來獲取僅為可借閱圖書的圖書記錄集。

還要記得讓模塊知道這個新 Python 文件,需通過將 controllers 子文件夾中添加到library_member/init.py中:

from . import models
from . import controllers

在library_member/controllers/init.py文件中添加一行代碼:

from . import main

然后更新模板并訪問http://<your-server>:8069/library/books?available=1 將僅顯示勾選了Is Available? 的圖書

Odoo 12圖書項目可借閱圖書

繼承 QWeb 模板

要修改網(wǎng)頁的實際展示,就需要繼承所使用的 QWeb 模板。我們將繼承l(wèi)ibrary_app.book_list_template來展示更多有關不可借閱圖書的信息。添加library_member/views/book_list_template.xml文件并加入如下代碼:

<odoo>
    <template id="book_list_extended"
        name="Extended Book List"
        inherit_id="library_app.book_list_template">
        <xpath expr="http://span[@t-field='book.publisher_id']" position="after">
            <t t-if="not book.is_available">
                <b>(Not Available)</b>
            </t>
        </xpath>
    </template>
</odoo>

網(wǎng)頁模板像其它 Odoo 視圖類型一樣是 XML 文件,同樣也可以使用 XPath 來定位元素并對它們進行操作。所繼承模型通過在元素中的inherit_id來指明。

小貼士:在前例中使用了靈活性很強的 XPath 標記,但這里也可以使用等價的簡化標記:<span t-field="book.publisher_id" position="after">

然后在 library_member/manifest.py文件中加入該文件的聲明:

    'data':[
...
        'views/book_list_template.xml',
    ]

然后訪問http://<your-server>:8069/library/books即可對不可借閱圖書展示額外的(Not Available)信息。

Odoo 12圖書項目不可借閱圖書

總結(jié)

擴展性是 Odoo 框架的一個重要功能。我們可以創(chuàng)建插件來為需要實現(xiàn)功能的多個層的已有插件修改或添加功能。

模型層中,我們使用_inherit模型屬性來引用已有模型,然后在原處執(zhí)行修改。模型內(nèi)的字段對象還支持疊加定義,這樣可對已有字段重新聲明,僅修改屬性。

其它的模型繼承機制允許我們利用數(shù)據(jù)結(jié)構(gòu)和業(yè)務邏輯。代理繼承通過多對一關聯(lián)字段上的delegate=True屬性(或老式的 inherits 模型屬性),來讓所有關聯(lián)模塊的所有字段可用,并復用它們的數(shù)據(jù)結(jié)構(gòu)。原型繼承使用_inherit屬性,來復制其它模型的功能(數(shù)據(jù)結(jié)構(gòu)定義和方法),并啟用抽象 mixin 類,提供一系列像文檔討論消息和 follower 的可復用功能。

視圖層中,視圖結(jié)構(gòu)通過 XML 定義,(使用 XPath 或 Odoo 簡化語法)定位 XML 元素來進行繼承及添加 XML 片斷。其它由模塊創(chuàng)建的記錄已可由繼承模塊修改,僅需引用 對應的完整 XML ID 并在設想的字段上執(zhí)行寫操作。

業(yè)務邏輯層中,可使用模型繼承相同的機制來進行繼承,以及重新聲明要繼承的方法。在方法內(nèi),Python 的super()函數(shù)可用于調(diào)用所繼承方法的代碼,添加代碼可在其之前或之后運行。

對于前端網(wǎng)頁,控制器中的展示邏輯繼承方式和模型方法相似,網(wǎng)頁模板也是包含 XML 結(jié)構(gòu)的視圖,因此可以像其它視圖類型一樣的被繼承。

下一篇文章中,我們將更深入學習模型,探索模型提供給我們的所有功能。

???第五章 Odoo 12開發(fā)之導入、導出以及模塊數(shù)據(jù)

學霸專區(qū)

  1. 如何繼承已有模型來添加 minin,如mail.thread?
  2. 要在會員表單視圖中添加Phone字段需要做哪些修改?
  3. 如果創(chuàng)建一個與繼承屬性的屬性名不同的模型類會發(fā)生什么(例如_name='y' and _inherit='x')?
  4. XPath是否可用于修改其它模塊的數(shù)據(jù)記錄?
  5. 繼承一個模型時,是否可擴展其方法但不使用 super()調(diào)用所繼承的原始代碼?
  6. 如何在不引用任何特定字段名的情況下繼承圖書目錄頁并在末行添加 ISBN 字段?

擴展閱讀

以下是對官方文檔的其它引用,可對模塊的擴展和繼承機制的知識進行補充:

本文首發(fā)地址:Alan Hou 的個人博客

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容