Creating Models
model fields 就像普通 python類屬性一樣定義:
from openerp import fields, models, api
class AModel(models.Model):
_name = 'a.model'
field1 = fields.Char()
注意:
這意味着,在model中,兩個field的 name 不能一樣,否則將會出現意想不到的錯誤。
默認的,在用戶界面中 field 的 label 是這個 field name 的 首字母大寫,這個可以通過設置field 的 string 參數來修改
field2 = fields.Integer(string='an other field')
也可以通過設置 default 參數來設置field 的默認值
a_field = fields.Char(default='a value')
或者傳遞一個function給default
def compute_default_value(self):
return self.get_value()
a_field = fields.Char(default=compute_default_value)
Computed fields
Fields 可以通過設置compute
參數來計算設置它的值(而不僅僅是通過讀取數據庫中的值),必須在方法中明確設置這個field的值,如果計算過程,可能會用到其它field,那麼需要在depends()中明確指定。
from openerp import api
total = fields.Float(compute='_compute_total')
@api.depends('value', 'tax')
def _compute_total(self):
for record in self:
record.total = record.value + record.value * record.tax
- 可以通過 ‘.’ 來設置對子字段的依賴
@api.depends('line_ids.value')
def _compute_total(self):
for record in self:
record.total = sum(line.value for line in record.line_ids)
- computed fields 默認情況下,是不存在database中的。它的值,僅僅在請求時纔會計算返回。通過傳入store=True,可以將這個字段存在db中,並且可以用來 search
- 針對computed field來搜索,也可以通過設置 search 參數來實現,值爲一個方法的名字,這個方法將返回一個 search domain
upper_name = field.Char(compute='_compute_upper', search='_search_upper')
def _search_upper(self, operator, value):
if operator == 'like':
operator = 'ilike'
return [('name', operator, value)]
- 如果允許直接對computed field 賦值,可以使用
inverse
參數,傳入值爲 一個方法的名字,這個方法用以反向計算和設置相關字段的值
document = fields.Char(compute='_get_document', inverse='_set_document')
def _get_document(self):
for record in self:
with open(record.get_document_path()) as f:
record.document = f.read()
def _set_document(self):
for record in self:
if not record.document:
continue
with open(record.get_document_path()) as f:
f.write(record.document)
- 多個fields 可以使用相同的 compute method 被同時設置
discount_value = fields.Float(compute='_apply_discount')
total = fields.Float(compute='_apply_discount')
@depends('value', 'discount')
def _apply_discount(self):
for record in self:
# compute actual discount from discount percentage
discount = record.value * record.discount
record.discount_value = discount
record.total = record.value - discount
Related fields
一種computed field 的特殊情況就是, related fields。這將由當前record的 某個 關係型 fields 的某個字段的值來作爲當前record的field的值,也可以被設置成爲store=True
nickname = fields.Char(related='user_id.partner_id.name', store=True)
Onchange: updating UI on the fly
當某個用戶在form view 界面修改了某個field的值,但是還沒有點擊Save時,可以自動的更新form view。
- computed fields 會自動的檢查和計算,他們不需要設置 onchange
- 那些非 computed fields,onchange() 裝飾器將會根據改變,自動變化field的值
@api.onchange('field1', 'field2') # 如果這些field的值變化,將調用這個方法
def check_change(self):
if self.field1 < self.field2:
self.field3 = True
在方法執行時,變化。並且將這些變動傳到客戶端程序,然後顯示出來。
- computed fields 和 new-api onchanges 都會被客戶端自動的調用,而不需要在view中對這些field進行額外的設置
- 可以view中添加參數,用以關閉這個 trigger,
<field name="name" on_change="0"/>
當這個field在用戶界面被用戶修改時,即使這個字段被明確的設置在depends 或 onchange 參數中,也將不會調用任何計算方法。
注意
雖然在 onchange 中對 某個字段進行了賦值,但是這個修改將不會真正的修改database中的值,這個計算僅僅用於將這個值傳給客戶端而已。
Low-level SQL
environments 的 cr
屬性是當前數據庫的事務 cursor,可以直接執行 SQL 語句,主要是爲了那些,不好通過ORM直接描述的查詢需求。
self.env.cr.execute('some_sql', param1, param2, param3)
由於models 使用同樣的cursor,而且 Environment 中也會保存一定cache,在使用raw SQL對數據庫執行修改之前,必須使這個 cache 無效,否則models的調用可能會出現未知錯誤。在執行 CREATE,UPDATE,DELETE 的 SQL語句之前,清空cache是很有必要的,如果只是執行 SELECT 語句,就沒有這個必要了。
清空cache 可以使用 self.env.invalidate_all()
方法
Compatibility between new API and old API
Odoo 最近才從老API 轉移到新API,所以在兩者之間互相轉換是很有必要的。
- PRC 層(XML-RPC 和 JSON-RPC)都是根據 old API 執行的,而 methods 僅能通過新API 被執行。
- 複寫來自 old code 中設置的方法,也有可能按照老式的方法重寫
舊API 與新API 之間最大的不同就是:
- old 中,Enviroment 的值(cursor, user ,context)是被顯示的傳入到method中
- record data(ids)也被顯示的傳入到某些方法中,也有可能不傳入這個值
- method 都是作用於 id 組成的列表中,而不是recordset
默認的,methods 被假定只能用 new API ,而且不能從old API 中調用新式 API
提示:
從new API 中調用 old API
當在new API中調用 old API時,將會自動轉化,不需要做額外的設置
>>> # method in the old API style
>>> def old_method(self, cr, uid, ids, context=None):
... print ids
>>> # method in the new API style
>>> def new_method(self):
... # system automatically infers how to call the old-style
... # method from the new-style method
... self.old_method()
>>> env[model].browse([1, 2, 3, 4]).new_method()
[1, 2, 3, 4]
下面兩個裝飾器可以將使得old api 調用 new api 設置的方法
model()
這個裝飾器,將會不會傳入ids,使得recordset的長度爲0,old api 中就可以根據這些參數調用 cr, uid, *args, context;
@api.model
def some_method(self, a_value):
pass
# from old api
old_style_model.some_method(self, cr, uid, a_value, context=context)
multi()
這個將會傳入 list of ids,也可以是 空 list , old API 就是 cr, uid, ids, *args, context
@api.multi
def some_method(self, a_value):
pass
# can be called as
old_style_model.some_method(cr, uid, [id1, id2], a_value, context=context)
由於new-style 的 API 傾向返回 一個 recordset,而 old-api傾向返回一個 list of ids,這種情況也有一個裝飾器來處理
returns()
這個函數用來返回一個recordset, 第一個參數應該是 recordset model 的name,或者 self
如果從新式API 中調用,將不會產生作用,但是當從old api 中調用時,將會將這些recordset 轉換成 list of ids
>>> @api.multi
... @api.returns('self')
... def some_method(self):
... return self
>>> new_style_model = env['a.model'].browse(1, 2, 3)
>>> new_style_model.some_method()
a.model(1, 2, 3)
>>> old_style_model = pool['a.model']
>>> old_style_model.some_method(cr, uid, [1, 2, 3], context=context)
[1, 2, 3]