Odoo ORM API (二)- Creating Models、Compatibility between new API and old API

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

一種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]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章