多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
### 计算字段和默认值 [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 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。