### 計算字段和默認值
[TOC]
到目前為止,我們接觸的字段都是存儲在數據庫中并直接從數據庫檢索。字段也可以通過計算獲得。在這種情況下,字段的值不是直接檢索自數據庫,而是通過調用模型的方法來實時計算獲得。要創建計算字段,需要設置它的`compute`屬性為方法名。這個計算方法通過計算`self`的每條記錄來設置字段的值。
> 注意
> `self`是一個記錄的有序集合,它支持標準的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層期望開發人員使用`depends()`裝飾器來指定計算方法的依賴性。當某些依賴關系被修改后,ORM層通過給定的依賴關系來觸發字段的重新計算。
~~~
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
~~~
> 練習計算字段
>
> * 加入座席占用百分比字段到授課模型。
> * 在列表視圖和表單視圖中顯示這個字段
> * 以進度條的方式顯示這個字段
`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` 對象給出了訪問請求參數和其他有用的信息:
>
> * `self.env.cr` 或者 `self._cr`是數據庫游標對象,通常用于查詢數據庫
> * `self.env.uid`或者`self._uid`是當前用戶的數據庫ID
> * `self.env.user`是當前用戶記錄
> * `self.env.ref(xml_id)`返回XML ID對應的記錄
> * `self.env[model_name]`返回給定模型的實例
>
> 練習默認值
>
> * 定義start_date默認值為今天
> * 在授課類添加字段`active`,并且設置其默認值為`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 有內置規則:`active`字段值為`False`時記錄不可見
#### Onchange機制
"onchange"機制為客戶端界面提供了一種方法,當用戶在字段中填寫了值,不需要向數據庫保存任何內容,就可以更新表單。例如,假設模型有三個字段`amount`,`unit_price`和`price`,當數量和單價改變時,自動重新計算價格,并在表單界面更新。要實現這個需求,需要定義一個方法,并使用`onchange()`裝飾器,`onchange()`的參數指定了在那個字段改變時,觸發方法。其中`self`代表表單視圖中的記錄,你所做的任何更改,`self`都將立刻反應在表單上。
~~~
<!-- 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",
}
}
~~~
對于計算字段的值,系統內置了`onchange()`裝飾器。通過授課表單我們可以觀察到:當改變座席數和參與者人數,`taken_seats`進度條會自動更新。
> 練習
> 通過"onchange"機制顯示的驗證無效值,例如座位數為負數或者座位數多與參與者。
`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提供兩種方式實現自動驗證,`python constraints`和`sql constraints`
Python約束通過方法裝飾器`constraints()`來定義,并在記錄集上調用這個方法。裝飾器參數指定了約束涉及的字段,當涉及的字段中任一發生改變時觸發方法執行。如果不滿足約束條件,該方法將引發異常:
~~~
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
~~~
> 練習添加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`是約束規則,`message`是違反約束規則時的警告信息。
> 練習添加SQL約束,在Postgre SQL文檔幫助下,添加下列約束:
>
> * 驗證課程描述與課程標題不能完全一樣
> * 驗證課程名是唯一的
`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'
~~~
> 練習添加重復項,因為我們為課程名稱添加了唯一性約束,所以不能再使用"復制"功能(表單->復制)。重寫"復制"方法,允許復制課程對象,將原始名稱更改為"原始名稱的副本"。
`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)',
~~~
作者:luohuayong
鏈接:http://www.jianshu.com/p/0c5b3e8fb160
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
- 開發教程
- Odoo10開發教程一(構建模塊)
- Odoo10開發教程二(基本視圖)
- Odoo10開發教程三(模型關聯)
- Odoo10開發教程四(繼承)
- Odoo10開發教程五(計算字段和默認值)
- Odoo10開發教程六(高級視圖)
- Odoo10開發教程七(工作流和安全)
- 參考手冊
- odoo V10中文參考手冊(一:ORM API)
- odoo V10中文參考手冊(指導規范)
- 技巧
- odoo 常用widget
- Odoo(OpenERP)開發實踐:菜單隱藏(1)
- Odoo(OpenERP)開發實踐:菜單隱藏(2)
- Odoo(OpenERP)開發實踐:數據模型學習
- Odoo中自動備份數據庫
- Odoo(OpenERP)應用實踐: 使用db-filter參數實現通過域名指定訪問哪個數據庫
- Odoo(OpenERP)配置文件openerp-server.conf詳解
- Odoo(OpenERP v8)數據模型(Data Model)
- odoo10學習筆記十七:controller
- Qweb定義