計算字段和默認值
到目前為止难衰,我們接觸的字段都是存儲在數(shù)據(jù)庫中并直接從數(shù)據(jù)庫檢索钦无。字段也可以通過計算獲得。在這種情況下盖袭,字段的值不是直接檢索自數(shù)據(jù)庫失暂,而是通過調(diào)用模型的方法來實時計算獲得。要創(chuàng)建計算字段鳄虱,需要設(shè)置它的compute
屬性為方法名弟塞。這個計算方法通過計算self
的每條記錄來設(shè)置字段的值。
注意
self
是一個記錄的有序集合拙已,它支持標(biāo)準(zhǔn)的Python集合操作决记,如len(self)
和iter(self)
,加上額外的集合操作recs1 + recs2
悠栓。迭代過程逐個提供self
記錄霉涨,其中每個記錄本身是大小為1的集合。你可以通過點記號來訪問/分配單個記錄上的字段record.name
惭适。
import random
from odoo 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))
依賴
計算字段的值通常取決于所在記錄行的其它字段的值笙瑟。ORM層期望開發(fā)人員使用depends()
裝飾器來指定計算方法的依賴性。當(dāng)某些依賴關(guān)系被修改后癞志,ORM層通過給定的依賴關(guān)系來觸發(fā)字段的重新計算往枷。
from odoo 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
練習(xí)計算字段
- 加入座席占用百分比字段到授課模型。
- 在列表視圖和表單視圖中顯示這個字段
- 以進度條的方式顯示這個字段
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=x
,x可以是Python字面值(bool,int戒突,float屯碴,string),也可以是一個有返回值的方法膊存。
name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)
注意
self.env
對象給出了訪問請求參數(shù)和其他有用的信息:
-
self.env.cr
或者self._cr
是數(shù)據(jù)庫游標(biāo)對象导而,通常用于查詢數(shù)據(jù)庫 -
self.env.uid
或者self._uid
是當(dāng)前用戶的數(shù)據(jù)庫ID -
self.env.user
是當(dāng)前用戶記錄 -
self.env.ref(xml_id)
返回XML ID對應(yīng)的記錄 -
self.env[model_name]
返回給定模型的實例
練習(xí)默認值
- 定義start_date默認值為今天
- 在授課類添加字段
active
,并且設(shè)置其默認值為True
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 有內(nèi)置規(guī)則:active
字段值為False
時記錄不可見
Onchange機制
"onchange"機制為客戶端界面提供了一種方法隔崎,當(dāng)用戶在字段中填寫了值今艺,不需要向數(shù)據(jù)庫保存任何內(nèi)容,就可以更新表單爵卒。例如虚缎,假設(shè)模型有三個字段amount
,unit_price
和price
,當(dāng)數(shù)量和單價改變時钓株,自動重新計算價格实牡,并在表單界面更新陌僵。要實現(xiàn)這個需求,需要定義一個方法铲掐,并使用onchange()
裝飾器拾弃,onchange()
的參數(shù)指定了在那個字段改變時,觸發(fā)方法摆霉。其中self
代表表單視圖中的記錄,你所做的任何更改奔坟,self
都將立刻反應(yīng)在表單上携栋。
<!-- 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",
}
}
對于計算字段的值,系統(tǒng)內(nèi)置了onchange()
裝飾器咳秉。通過授課表單我們可以觀察到:當(dāng)改變座席數(shù)和參與者人數(shù)婉支,taken_seats
進度條會自動更新。
練習(xí)
通過"onchange"機制顯示的驗證無效值澜建,例如座位數(shù)為負數(shù)或者座位數(shù)多與參與者向挖。
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",
},
}
模型約束
odoo提供兩種方式實現(xiàn)自動驗證,python constraints
和sql constraints
Python約束通過方法裝飾器constraints()
來定義炕舵,并在記錄集上調(diào)用這個方法何之。裝飾器參數(shù)指定了約束涉及的字段,當(dāng)涉及的字段中任一發(fā)生改變時觸發(fā)方法執(zhí)行咽筋。如果不滿足約束條件溶推,該方法將引發(fā)異常:
from odoo.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)
# all records passed the test, don't return anything
練習(xí)添加Python約束,講師不能在自己的授課出席人中
openacademy/models.py
# -*- coding: utf-8 -*-
from odoo 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約束通過模型屬性_sql_constraints
進行定義奸攻。它是一個三元素的元組的列表(name, sql_definition, message)蒜危,其中name
是SQL約束名稱,sql_definition
是約束規(guī)則睹耐,message
是違反約束規(guī)則時的警告信息辐赞。
練習(xí)添加SQL約束,在Postgre SQL文檔幫助下,添加下列約束:
- 驗證課程描述與課程標(biāo)題不能完全一樣
- 驗證課程名是唯一的
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'
練習(xí)添加重復(fù)項硝训,因為我們?yōu)檎n程名稱添加了唯一性約束响委,所以不能再使用"復(fù)制"功能(表單->復(fù)制)。重寫"復(fù)制"方法,允許復(fù)制課程對象捎迫,將原始名稱更改為"原始名稱的副本"晃酒。
openacademy/models.py
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")
@api.multi
def copy(self, default=None):
default = dict(default or {})
copied_count = self.search_count(
[('name', '=like', u"Copy of {}%".format(self.name))])
if not copied_count:
new_name = u"Copy of {}".format(self.name)
else:
new_name = u"Copy of {} ({})".format(self.name, copied_count)
default['name'] = new_name
return super(Course, self).copy(default)
_sql_constraints = [
('name_description_check',
'CHECK(name != description)',