因爲本章有包含不少基礎知識,我的不會所有轉化爲本身的語言。直接機器翻譯了(用斜體標註,機器翻譯反而一字不落,我會過濾掉冗餘的內容),雖然機翻,但會保證意思不會偏。html
本章主要章節以下:python
模型具備定義其行爲的結構屬性。它們的前綴是
下劃線。模型最重要的屬性是_name,由於它定義了內部全局標識符。在內部,Odoo使用_name屬性來建立一個數據庫表。例如,若是您提供library.book,則Odoo ORM將在數據庫中建立library_book表。這就是爲何在Odoo中,_name屬性必須是惟一的。git
定義模型文件models/library_book.pysql
_description = 'Library Book'
_order = 'date_release desc, name'
_rec_name = 'short_name' short_name = fields.Char('Short Title', required=True)
<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更新模塊
api
或者經過命令行升級安全
python3 odoo-bin -u my_library
第一步是爲模型的定義添加一個更加用戶友好的標題。這不是強制性的,但能夠由某些附加組件使用。例如,在建立新記錄時,郵件附加模塊中的跟蹤功能將其用於通知文本。有關更多詳細信息,請參閱第23章,在Odoo中管理電子郵件。若是您不使用模型的描述,在這種狀況下,Odoo將在日誌中顯示警告。
默認狀況下,Odoo使用內部id值(自動生成的主鍵)對記錄進行排序。可是,這能夠更改,這樣咱們就可使用咱們選擇的字段,方法是提供一個帶有字符串的_order屬性,該字符串包含以逗號分隔的字段名列表。
字段名後面能夠跟desc關鍵字,以便按降序排序。數據結構
小貼士
只有存儲在數據庫中的field才能進行排序,對於computed的字段是不支持排序的。app
_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)
重要提醒
Selection類型是可使用數字做爲key的,可是0在odoo中是做爲未設置當前字段存在的。所以,若是key中使用了0,那麼可能存在乎想不到的坑。
對於關係字段,咱們須要事先肯定關係的目標模型(或協同模型)。然而,有時,咱們可能須要把這個決定留給用戶,首先選擇咱們想要的模型,而後選擇咱們想要連接到的記錄。這能夠經過使用參考字段來實現。
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提供了三種方式的繼承
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調用原有模型的函數)。
原型繼承,對現有模塊完整的複製。
原型繼承會用到_name及_inherit的類屬性。
from odoo import models, fields, api class LibraryBookCopy(models.Model): _name = "library.book.copy" _inherit = "library.book" _description = "Library Book's Copy"
在使用_name及_inherit的類屬性的時候,odoo將使用_name做爲類名複製_inherit的模型。
新的模型將體如今數據庫中,有單獨的數據庫表。以上爲例,library_book_copy表。
原型繼承是copy父類完整的內容,包括字段、屬性及方法。若是將要調整這些內容,可直接定義便可。例如, library.book已經有了name_get函數,可是不符合咱們的要求。咱們能夠在library.book.copy模型中直接新增一個name_get函數。
警告
若是_name使用了父類的名稱,那麼原型繼承是不生效的,而是普通的繼承。
雖然官方提供了原型繼承,可是應用場景不多。反而是經過委託繼承,能夠在不復制整個數據結構的狀況下實現咱們想要的功能。
委託繼承使用類屬性_inherits,注意多了一個s。在某些場景下,相較於修改現有模型,建立一個新的模型並與老的模型進行關聯反而是更好的選擇。
委託繼承與面向對象編程的理念更爲貼近。它還支持多態繼承,便可以同時從多個模型繼承。
好比,咱們有一個圖書館。會有不少的讀書人來圖書館讀書,這些人在咱們這有一些基本的信息(姓名、電話等),其中又有一些人是圖書館會員。會員與普通用戶都有姓名、電話等基礎信息,可是又多了辦理會員的日期、會員卡號等特有信息。
class LibraryMember(models.Model): _name = 'library.member' _inherits = {'res.partner': 'partner_id'} partner_id = fields.Many2one( 'res.partner', ondelete='cascade')
# 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
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(嚮導)。將在第八章詳細介紹。