【odoo14】【好書學習】第四章、應用模型


老韓頭的開發日常【好書學習】系列

由於本章有包含很多基礎知識,個人不會全部轉化為自己的語言。直接機器翻譯了(用斜體標注,機器翻譯反而一字不落,我會過濾掉冗余的內容),雖然機翻,但會保證意思不會偏。

本章主要章節如下:

  • 定義模型展示及順序
  • 添加字段
  • 配置帶有小數點精度的float型字段
  • 添加貨幣字段
  • 添加關聯字段
  • 添加層級關系
  • 添加模型約束
  • 添加字段字段
  • 在其他模型中調用關聯字段
  • 使用引用調用關聯信息
  • 使用繼承添加特性
  • 繼承抽象模型復用模型特性
  • 使用委托繼承完整繼承另一個模型

定義模型展示及順序

模型具有定義其行為的結構屬性。它們的前綴是
下划線。模型最重要的屬性是_name,因為它定義了內部全局標識符。在內部,Odoo使用_name屬性來創建一個數據庫表。例如,如果您提供library.book,則Odoo ORM將在數據庫中創建library_book表。這就是為什么在Odoo中,_name屬性必須是唯一的。

  • _rec_name: 以模型為單位展示時,模型展示的field字段。
  • _order: 記錄在展示時候的排序

准備

步驟

定義模型文件models/library_book.py

  1. 添加模型的描述
_description = 'Library Book'
  1. 添加排序
_order = 'date_release desc, name'
  1. 使用short_name作為模型的默認展示字段
_rec_name = 'short_name'
short_name = fields.Char('Short Title', required=True)
  1. 在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定義了基本的模型。

  1. 增量代碼如下
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,
	)
  1. 添加對應的視圖
<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字段(配置小數點)

添加貨幣字段

添加關聯字段

添加層級結構

添加約束驗證

添加計算字段

展示存儲在其他模型中的關聯字段

通過關聯字段添加動態關系

對於關系字段,我們需要事先確定關系的目標模型(或協同模型)。然而,有時,我們可能需要把這個決定留給用戶,首先選擇我們想要的模型,然后選擇我們想要鏈接到的記錄。這可以通過使用參考字段來實現。

准備

步驟

  1. 編輯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]
  1. 添加引用字段
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提供了三種方式的繼承

  • 類的繼承
  • 原型繼承
  • 委托繼承

准備

步驟

  1. 我們將為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' )
  1. 添加新增字段的計算函數
# ...
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的類屬性。

  1. 添加library_book_copy.py文件。
  2. 編輯文件
from odoo import models, fields, api
class LibraryBookCopy(models.Model):
	_name = "library.book.copy"
	_inherit = "library.book"
	_description = "Library Book's Copy"
  1. 添加新文件引用, models/init.py

原理

在使用_name及_inherit的類屬性的時候,odoo將使用_name作為類名復制_inherit的模型。
新的模型將體現在數據庫中,有單獨的數據庫表。以上為例,library_book_copy表。
原型繼承是copy父類完整的內容,包括字段、屬性及方法。如果將要調整這些內容,可直接定義即可。例如, library.book已經有了name_get函數,但是不符合我們的要求。我們可以在library.book.copy模型中直接新增一個name_get函數。

警告
如果_name使用了父類的名稱,那么原型繼承是不生效的,而是普通的繼承。

更多

雖然官方提供了原型繼承,但是應用場景很少。反而是通過委托繼承,可以在不復制整個數據結構的情況下實現我們想要的功能。

使用委托繼承實現復制另一個模型的特性

委托繼承使用類屬性_inherits,注意多了一個s。在某些場景下,相較於修改現有模型,創建一個新的模型並與老的模型進行關聯反而是更好的選擇。
委托繼承與面向對象編程的理念更為貼近。它還支持多態繼承,即可以同時從多個模型繼承。
比如,我們有一個圖書館。會有很多的讀書人來圖書館讀書,這些人在我們這有一些基本的信息(姓名、電話等),其中又有一些人是圖書館會員。會員與普通用戶都有姓名、電話等基礎信息,但是又多了辦理會員的日期、會員卡號等特有信息。

准備

步驟

  1. 添加新的模型, res.partner
class LibraryMember(models.Model):
	_name = 'library.member'
	_inherits = {'res.partner': 'partner_id'}
	partner_id = fields.Many2one(
	'res.partner',
	ondelete='cascade')
  1. 添加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文件中。

  1. 添加虛擬類
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
  1. 編輯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(向導)。將在第八章詳細介紹。


免責聲明!

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



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