Computed fields and default values
目前爲止,fields 都是直接將數據寫到數據庫或者從數據庫讀取數據。Fields 中同樣可以通過調用方法獲取動態計算值,而不是從數據庫中取數據。
創建 computed field 的方式就是給一個 field 設置 compute
屬性,並將其值 = method name。那麼這個method 就會對 self 這個model的所有record自動生效。
注意
self 不僅可以像普通 python class 定義中一樣,調用方法,在 Odoo 中 self 同時代表 recordset,是一個有序的自己 model record 的集合,支持普通python 方法, len(self) and iter(self), 同時也可以將兩個recordset 相加。 recs1 + recs2
迭代 self, 將會把 records 中的record 一個一個返回,同時返回的每一個 record 也是一個 recordset,只不過 size == 1。也可以通過 點 來調用 單個record的屬性。 record.name
import random
from openerp import models, fields, api
class ComputedModel(models.Model):
_name = 'test.computed'
name = fields.Char(compute='_compute_name')
@api.multi
def _compute_name(self):
for record in self:
record.name = str(random.randint(1, 1e6))
Dependencies
computed field 的值通常是根據其他 field 的 value 計算出來的。Odoo 的 ORM 就希望開發者能夠明確指出需要依賴的 field 是哪些,所以提供了一個裝飾器 depend()。無論在哪,只要當被依賴的 fields 變動時,Odoo就會根據這個自動重新計算 computed field 的值。
from openerp import models, fields, api
class ComputedModel(models.Model):
_name = 'test.computed'
name = fields.Char(compute='_compute_name')
value = fields.Integer()
@api.depends('value')
def _compute_name(self):
for record in self:
record.name = "Record with value %s" % record.value
練習 4-1
- 在 session model 中添加座位使用的 百分比
- 將這個新增 field 添加到 tree form view 中
- 將這個field 以 process bar的形式展現出來
openacademy/models.py
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
# 新增
taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')
@api.depends('seats', 'attendee_ids')
def _taken_seats(self):
for r in self:
if not r.seats:
r.taken_seats = 0.0
else:
r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
# 結束
openacademy/views/openacademy.xml
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<!-- 新增 -->
<field name="taken_seats" widget="progressbar"/>
<!-- 結束 -->
</group>
</group>
<label for="attendee_ids"/>
<tree string="Session Tree">
<field name="name"/>
<field name="course_id"/>
<!-- 新增 -->
<field name="taken_seats" widget="progressbar"/>
<!-- 結束 -->
</tree>
</field>
</record>
Default values
任何類型的 field 都可以設置 default value,只需要在定義時,添加 default=x
。這個 x 可以是python 中的 boolean, string, int, float 等。也可以接受一個函數,以recordset 作爲參數,返回一個值
name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)
Note:
self.env
提供了一系列的藉口,用以處理一些東西
self.env.cr
或者self._cr
是database的corsor, 可以通過這個,直接調用SQLself.env.uid
orself._uid
時當前登錄用戶的數據表 idself.env.user
當前登錄用戶的 recordself.env.context
orself._context
當前的 context directoryself.env.ref(xml_id)
通過 xml_id 返回 對應的 recordself.env[model_name]
返回 model_name 對應的 空 record
練習 4-2
- 給 start_date 設置默認值, 爲 當前時間
- session 中添加 active 字段
openacademy/models.py
_name = 'openacademy.session'
name = fields.Char(required=True)
start_date = fields.Date(default=fields.Date.today) # 修改
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
active = fields.Boolean(default=True) # 新增
instructor_id = fields.Many2one('res.partner', string="Instructor",
domain=['|', ('instructor', '=', True),
openacademy/views/openacademy.xml
<field name="course_id"/>
<field name="name"/>
<field name="instructor_id"/>
<field name="active"/> <!-- 新增 -->
</group>
<group string="Schedule">
<field name="start_date"/>
注意:
Odoo 有一個內建機制, 將會自動的隱藏掉 active 字段值爲 False 的record
Onchange
Odoo onchange 機制主要用於在 client 中的 form view。當某個字段改變時,自動 update form view,不會主動存在database 中。注意,這個主要作用於 客戶端
比如:某個model 擁有三個字段 amout, unit_price and price。我們希望在form中修改其它兩個字段時, price 的值會自動的改變。爲此,提供了一個裝飾器 onchange() 來實現這個功能,
<!-- content of form view -->
<field name="amount"/>
<field name="unit_price"/>
<field name="price" readonly="1"/>
# onchange handler
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
# set auto-changing field
self.price = self.amount * self.unit_price
# Can optionally return a warning and domains
return {
'warning': {
'title': "Something bad happened",
'message': "It was very bad indeed",
}
}
練習 4-3
添加一個針對 字段變化的 onchange, 當form中字段值填寫不合法的時候,彈出警告
openacademy/models.py
r.taken_seats = 0.0
else:
r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
# 新增
@api.onchange('seats', 'attendee_ids')
def _verify_valid_seats(self):
if self.seats < 0:
return {
'warning': {
'title': "Incorrect 'seats' value",
'message': "The number of available seats may not be negative",
},
}
if self.seats < len(self.attendee_ids):
return {
'warning': {
'title': "Too many attendees",
'message': "Increase seats or remove excess attendees",
},
}
# 結束
Model constraints
Odoo提供了兩種方式來設置自動驗證 invariants: Python constraints and SQL constraints
Python constraints 就像普通method一樣,只不過需要使用裝飾器 constrains(), 不過也是需要指明 record中的那些字段需要使用 constraint,如果不滿足要求,method應該 raise an exception
from openerp.exceptions import ValidationError
@api.constrains('age')
def _check_something(self):
for record in self:
if record.age > 20:
raise ValidationError("your record is too old: %s" % record.age)
練習 4-4
添加一個限制條件,用以明確,instructor not in the attendees in his/her own session
openacademy/models.py
# -*- coding: utf-8 -*-
from openerp import models, fields, api, exceptions # 修改
class Course(models.Model):
_name = 'openacademy.course'
'message': "Increase seats or remove excess attendees",
},
}
# 新增
@api.constrains('instructor_id', 'attendee_ids')
def _check_instructor_not_in_attendees(self):
for r in self:
if r.instructor_id and r.instructor_id in r.attendee_ids:
raise exceptions.ValidationError("A session's instructor can't be an attendee")
SQL constraints 是通過給 model 設置屬性 _sql_constraints
, 這個也和domain一樣是由一堆 三個元素組成的 tuple 的 list集合。(name, sql_definition, message),name 就是這個 SQL constraint 的名字,sql_definition 就是 table_constraint, message 就是 error message。
練習 4-5
- CHECK course 的title 和 description 是否一樣
- 明確 course name 是 unique
openacademy/models.py
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")
# 新增
_sql_constraints = [
('name_description_check',
'CHECK(name != description)',
"The title of the course should not be the description"),
('name_unique',
'UNIQUE(name)',
"The course title must be unique"),
]
# 結束
class Session(models.Model):
_name = 'openacademy.session'