由於本章有包含很多基礎知識,個人不會全部轉化為自己的語言。直接機器翻譯了(用斜體標注,機器翻譯反而一字不落,我會過濾掉冗余的內容),雖然機翻,但會保證意思不會偏。
本章主要章節如下:
- 定義模型展示及順序
- 添加字段
- 配置帶有小數點精度的float型字段
- 添加貨幣字段
- 添加關聯字段
- 添加層級關系
- 添加模型約束
- 添加字段字段
- 在其他模型中調用關聯字段
- 使用引用調用關聯信息
- 使用繼承添加特性
- 繼承抽象模型復用模型特性
- 使用委托繼承完整繼承另一個模型
定義模型展示及順序
模型具有定義其行為的結構屬性。它們的前綴是
下划線。模型最重要的屬性是_name,因為它定義了內部全局標識符。在內部,Odoo使用_name屬性來創建一個數據庫表。例如,如果您提供library.book,則Odoo ORM將在數據庫中創建library_book表。這就是為什么在Odoo中,_name屬性必須是唯一的。
- _rec_name: 以模型為單位展示時,模型展示的field字段。
- _order: 記錄在展示時候的排序
准備
步驟
定義模型文件models/library_book.py
- 添加模型的描述
_description = 'Library Book'
- 添加排序
_order = 'date_release desc, name'
- 使用short_name作為模型的默認展示字段
_rec_name = 'short_name'
short_name = fields.Char('Short Title', required=True)
- 在form中添加shot_name字段
<field name="short_name"/>
完整的library_book.py文件如下:
from odoo import models, fields
class LibraryBook(models.Model):
_name = 'library.book'
_description = 'Library Book'
_order = 'date_release desc, name'
_rec_name = 'short_name'
name = fields.Char('Title', required=True)
short_name = fields.Char('Short Title', required=True)
date_release = fields.Date('Release Date')
author_ids = fields.Many2many('res.partner', string='Authors')
完整的form視圖內容如下:
<form>
<group>
<group>
<field name="name"/>
tags"/>
<field name="author_ids" widget="many2many_
</group>
<group>
<field name="short_name"/>
<field name="date_release"/>
</group>
</group>
</form>
通過UI更新模塊
或者通過命令行升級
python3 odoo-bin -u my_library
原理
第一步是為模型的定義添加一個更加用戶友好的標題。這不是強制性的,但可以由某些附加組件使用。例如,在創建新記錄時,郵件附加模塊中的跟蹤功能將其用於通知文本。有關更多詳細信息,請參閱第23章,在Odoo中管理電子郵件。如果您不使用模型的描述,在這種情況下,Odoo將在日志中顯示警告。
默認情況下,Odoo使用內部id值(自動生成的主鍵)對記錄進行排序。但是,這可以更改,這樣我們就可以使用我們選擇的字段,方法是提供一個帶有字符串的_order屬性,該字符串包含以逗號分隔的字段名列表。
字段名后面可以跟desc關鍵字,以便按降序排序。
小貼士
只有存儲在數據庫中的field才能進行排序,對於computed的字段是不支持排序的。
_order是縮減版的SQL ORDER BY,他不支持NULLS FIRST等。
模型記錄從其他記錄引用時使用表示。例如,值為1的用戶標識字段表示管理員用戶。在窗體視圖中顯示時,Odoo將顯示用戶名,而不是數據庫ID。簡而言之,_rec_name是Odoo GUI用來表示該記錄的記錄的顯示名稱。默認情況下,使用名稱字段。實際上,這是_rec_name屬性的默認值,這就是為什么在我們的模型中有一個name字段比較方便的原因。在我們的例子中圖書館.bookmodel有一個name字段,因此,默認情況下,Odoo將使用它作為顯示名。我們想在步驟3中更改此行為;我們使用了short_name作為_rec_name。之后,library.book模型的顯示名從form視圖的名稱name改為short_name,Odoo GUI將使用short_name的值來表示記錄。
警告
若模型中沒有name字段也沒有配置_rec_name,那么將展示記錄的模型名稱及記錄ID(library.book, 1)
我們新增了short_name的字段,其實是在數據庫表中新增了一列,同時我們需要在視圖中展示相應的字段。
更多
自odoo8之后,計算字段magic_display字段被默認添加到模型中。他的值是通過nage_get()的模型方法生成的。
name_get()方法默認是通過_rec_name屬性去生成展示的名稱的。如果你想自己實現展示的名稱,可以重寫name_get()函數。函數必須返回包含由記錄ID和Unicode字符串組成的元組的列表。
舉例:
def name_get(self):
result = []
for record in self:
rec_name = "%s (%s)" % (record.name, record.date_ release)
result.append((record.id, rec_name))
return result
添加數據字段
准備
步驟
還是my_library模塊為例,models/library_book.py定義了基本的模型。
- 增量代碼如下
from odoo import models, fields
class LibraryBook(models.Model):
# ...
short_name = fields.Char('Short Title')
notes = fields.Text('Internal Notes')
state = fields.Selection(
[('draft', 'Not Available'),
('available', 'Available'),
('lost', 'Lost')],
'State')
description = fields.Html('Description')
cover = fields.Binary('Book Cover')
out_of_print = fields.Boolean('Out of Print?')
date_release = fields.Date('Release Date')
date_updated = fields.Datetime('Last Updated')
pages = fields.Integer('Number of Pages')
reader_rating = fields.Float(
'Reader Average Rating',
digits=(14, 4), # Optional precision decimals,
)
- 添加對應的視圖
<form>
<group>
<group>
<field name="name"/>
tags"/>
<field name="author_ids" widget="many2many_
<field name="state"/>
<field name="pages"/>
<field name="notes"/>
</group>
<group>
<field name="short_name"/>
<field name="date_release"/>
<field name="date_updated"/>
avatar"/>
<field name="cover" widget="image" class="oe_
<field name="reader_rating"/>
</group>
</group>
<group>
<field name="description"/>
</group>
</form>
下面的代碼定義了字段常用的屬性,可以先有個印象
short_name = fields.Char('Short Title',translate=True, index=True)
state = fields.Selection(
[('draft', 'Not Available'),
('available', 'Available'),
('lost', 'Lost')],
'State', default="draft")
description = fields.Html('Description', sanitize=True, strip_ style=False)
pages = fields.Integer('Number of Pages',
groups='base.group_user',
states={'lost': [('readonly', True)]},
help='Total book page count', company_dependent=False)
原理
- Char: 字符串類型
- Text: 跨行字符串類型
- Selection: 選擇列表類型。
重要提醒
Selection類型是可以使用數字作為key的,但是0在odoo中是作為未設置當前字段存在的。因此,如果key中使用了0,那么可能存在意想不到的坑。
- Html跟text類似,可存儲HTML的富文本內容。
- Binary: 可以存儲圖片及文檔。
- Boolean: True/False
- Date: 存儲日期。可使用fields.Date.today()作為默認值。
- Datetime: 是作為navi的UTC時間存儲(不包含時區信息)。可通過fields.Date.now()設置默認值。
- Integer: 整型。
- Float: 浮點型。可設置小數點精度。
- Monetary: 貨幣類型。
在定義字段的時候,除了以上的類型外,還包含了一些屬性。 - string: 是字段展示的名稱,如果沒有設置,則會取字段名稱(將_替換為空格)。
- translate: 設置為true,說明該字段是可翻譯的。
- default: 設置默認值。可以是一個具體的值,也可以是一個函數。default=_compute_default。
- help: 是該字段在頁面以tooltips展示的幫助信息
- groups: 該字段隸屬於哪些權限組。如果沒有,則默認跟隨模型權限組。
- states: states允許用戶界面根據state字段的值動態設置readonly、required和invisible屬性的值。因此,它需要一個狀態字段存在並在表單視圖中使用(即使它是不可見的)。state屬性的名稱在Odoo中是硬編碼的,不能更改。
- copy: 字段的值是否跟隨記錄的copy到新的記錄。對於非關系型字段及many2one的字段,默認是True;對於One2many字段默認是False。
- index: 設置為True,將在數據庫表中創建該字段的鍵,可加快搜索。
- readonly: UI頁面上該字段表現為只讀。
- required: 該字段是當前模型必備字段。
- company_operator標志位表示為不同的公司存儲不同的值。
- group_operator: 當記錄以類似於sql中的group by進行操作時,該字段通過哪種運算方式計算結果。常用的運算方式有count, count_distinct, array_agg, bool_and, bool_or, max, min, avg, and sum。Integer、float及貨幣類型默認采用sum計算方式。
- sanitize:用於HTML字段,用於清除html可能包含的不安全標識。
如果你想進一步控制HMTL的清洗,可通過如下屬性: - sanitize_tags=True,移除不屬於白名單的標簽。(白名單定義在odoo/tools/ mail.py)
- sanitize_attributes=True, 移除不屬於白名單的屬性。
- sanitize_style=True, 移除不屬於白名單的樣式。
- strip_style=True, 移除所有的樣式。
- strip_class=True, 移除所有的類屬性。
更多
添加float字段(配置小數點)
添加貨幣字段
添加關聯字段
添加層級結構
添加約束驗證
添加計算字段
展示存儲在其他模型中的關聯字段
通過關聯字段添加動態關系
對於關系字段,我們需要事先確定關系的目標模型(或協同模型)。然而,有時,我們可能需要把這個決定留給用戶,首先選擇我們想要的模型,然后選擇我們想要鏈接到的記錄。這可以通過使用參考字段來實現。
准備
步驟
- 編輯models/library_book.py文件
from odoo import models, fields, api
class LibraryBook(models.Model):
# ...
@api.model
def _referencable_models(self):
models = self.env['ir.model'].search([
('field_id.name', '=', 'message_ids')])
return [(x.model, x.name) for x in models]
- 添加引用字段
ref_doc_id = fields.Reference( selection='_referencable_models',
string='Reference Document')
原理
引用字段與m2o字段相似,他們都允許用戶自己選擇關聯的模型。
目標模型通過selection屬性進行選擇,selection必須是包含兩個元素元組的列表,第一個元素是內部標識,第二個標識是展示的內容。
例如:
[('res.users', 'User'), ('res.partner', 'Partner')]
但是,我們可以使用最常見的模型,而不是提供固定的列表。為了簡單起見,我們使用了所有具有消息傳遞功能的模型。使用可引用的模型方法,我們動態地提供了一個模型列表。
我們的方法是通過提供一個函數來瀏覽所有可以被引用的模型記錄,從而動態地構建一個將提供給selection屬性的列表。雖然這兩種形式都是允許的,但是我們在引號中聲明了函數名,而不是直接引用不帶引號的函數。這是更靈活的,它允許引用的函數只在稍后的代碼中定義,例如,這在使用直接引用時是不可能的。
函數運行在模型層面,因此需要用@api.model裝飾器。
雖然這個特性看起來不錯,但它會帶來很大的執行開銷。顯示大量記錄的引用字段(例如,在列表視圖中)會造成沉重的數據庫負載,因為每個值都必須在單獨的查詢中查找。與常規關系字段不同,它也無法利用數據庫引用完整性。
通過繼承為模型添加新特性
odoo可以實現對歸屬於其他模塊的模型功能進行擴展,而不去動原有的代碼。可以添加字段、方法、以及修改以存在的字段、方法。
odoo提供了三種方式的繼承
- 類的繼承
- 原型繼承
- 委托繼承
准備
步驟
- 我們將為respartner用戶添加關聯的圖書
class ResPartner(models.Model):
_inherit = 'res.partner'
_order = 'name'
authored_book_ids = fields.Many2many(
'library.book', string='Authored Books')
count_books = fields.Integer( 'Number of Authored Books',
compute='_compute_count_books' )
- 添加新增字段的計算函數
# ...
from odoo import api # if not already imported
# class ResPartner(models.Model):
# ...
@api.depends('authored_book_ids')
def _compute_count_books(self):
for r in self:
r.count_books = len(r.authored_book_ids)
原理
我們通過_inherit屬性實現對於已有模塊的繼承。新增的字段將直接體現在原有模型上。
已有字段也可以進行增量修改。可以對原模型中的函數進行重寫或修改(可通過super調用原有模型的函數)。
通過繼承實現模型的copy
原型繼承,對現有模塊完整的復制。
准備
步驟
原型繼承會用到_name及_inherit的類屬性。
- 添加library_book_copy.py文件。
- 編輯文件
from odoo import models, fields, api
class LibraryBookCopy(models.Model):
_name = "library.book.copy"
_inherit = "library.book"
_description = "Library Book's Copy"
- 添加新文件引用, models/init.py
原理
在使用_name及_inherit的類屬性的時候,odoo將使用_name作為類名復制_inherit的模型。
新的模型將體現在數據庫中,有單獨的數據庫表。以上為例,library_book_copy表。
原型繼承是copy父類完整的內容,包括字段、屬性及方法。如果將要調整這些內容,可直接定義即可。例如, library.book已經有了name_get函數,但是不符合我們的要求。我們可以在library.book.copy模型中直接新增一個name_get函數。
警告
如果_name使用了父類的名稱,那么原型繼承是不生效的,而是普通的繼承。
更多
雖然官方提供了原型繼承,但是應用場景很少。反而是通過委托繼承,可以在不復制整個數據結構的情況下實現我們想要的功能。
使用委托繼承實現復制另一個模型的特性
委托繼承使用類屬性_inherits,注意多了一個s。在某些場景下,相較於修改現有模型,創建一個新的模型並與老的模型進行關聯反而是更好的選擇。
委托繼承與面向對象編程的理念更為貼近。它還支持多態繼承,即可以同時從多個模型繼承。
比如,我們有一個圖書館。會有很多的讀書人來圖書館讀書,這些人在我們這有一些基本的信息(姓名、電話等),其中又有一些人是圖書館會員。會員與普通用戶都有姓名、電話等基礎信息,但是又多了辦理會員的日期、會員卡號等特有信息。
准備
步驟
- 添加新的模型, res.partner
class LibraryMember(models.Model):
_name = 'library.member'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one(
'res.partner',
ondelete='cascade')
- 添加member特有的字段
# class LibraryMember(models.Model):
# ...
date_start = fields.Date('Member Since')
date_end = fields.Date('Termination Date')
member_number = fields.Char()
date_of_birth = fields.Date('Date of birth')
原理
我們通過_inherits實現對res.partner對象的委托繼承,這是一個key-value的字典。key是繼承模型的類名,value是當前模型關聯到繼承模型的字段。
當我們對新模型創建記錄時,會現在res.partner、library.member中分別創建一條記錄,並通過partner_id進行關聯。
委托繼承只是對字段的繼承,並不包含函數。
更多
關於委托繼承,有個簡寫方式。即在m2o中添加delegate=True屬性,去掉_inherits的類屬性。上面的例子可以寫成
class LibraryMember(models.Model):
_name = 'library.member'
partner_id = fields.Many2one('res.partner', ondelete='cascade', delegate=True)
date_start = fields.Date('Member Since')
一個典型的委托繼承是用戶模型,res.users,繼承自res.partner。也就是説我們在res.users中看到的一些字段其實是partner中的。
傳統繼承和原型繼承都是可以為模型添加新的特性,但是效率偏低。
使用抽象模型復用模型特性
有時我們有一個特性,向同時添加到好幾個模型中。抽象模型是可以實現我們想要的特性,然后被其他幾個模型繼承。
舉個例子,我們將實現一個簡單的歸檔特性。它將活動字段添加到模型中(如果它還不存在),並提供一個存檔方法來切換活動標志。這是因為活動是一個魔法場。如果默認情況下存在於模型中,則active=False的記錄將從查詢中過濾掉。
准備
步驟
歸檔特性一般會有自己的模塊,至少是自己的python文件。此處為了簡單,就放在library_book.py文件中。
- 添加虛擬類
class BaseArchive(models.AbstractModel):
_name = 'base.archive'
active = fields.Boolean(default=True)
def do_archive(self):
for record in self:
record.active = not record.active
- 編輯library.book類以繼承archive模型。
class LibraryBook(models.Model):
_name = 'library.book'
_inherit = ['base.archive']
# ...
原理
抽象模型基於models.AbstractModel創建。他與普通的models.Model功能基本類似,只是他並不在數據庫中創建相應的數據表。他的存在就是為了讓其他模型繼承用的。
當一個模型定義了_inherit屬性,那么他將繼承該收藏模型所有的字段、屬性及方法。
注意,此處_inherit的值是列表。
其實,_inhiret有兩種形式。列表代表繼承自多個模型,單獨的字符串是繼承自一個模型。
更多
最值得一提的抽象模型是mail.thread,它定義在mail(Discuss)模塊中。它為模型添加了討論的特性。我們可以在模型form視圖下方看到消息。
還有一個模型,models.TransientModel。它跟model.Model類似,只是它存儲的數據是暫時的,odoo會有定時任務清理掉。拋掉這個不同,瞬態模型跟常規模型一樣。
瞬態模型在用戶交互比較復雜的場景下比較有幫助,比如wizards(向導)。將在第八章詳細介紹。