
计算的字段和变更(Computed Fields And Onchanges
“Computed Fields And Onchanges”的概念支持这些情况。虽然本章在技术上并不复杂,但这两个概念的语义都非常重要。这也是我们第一次编写Python逻辑。到目前为止,除了类定义和字段声明之外,我们还没有编写任何其他东西。
计算的字段(Computed Fields
参考: 主题关联文档可查阅 Computed Fields。
在房地产模型中,自动计算总的面积和最佳报价
预期效果:
在地产报价模型中,自动计算合法的日期且可被更新
到目前为止,字段已直接存储在数据库中并直接从数据库中检索。字段也可以被计算。在这种情况下,不会从数据库中检索字段的值,而是通过调用模型的方法来动态计算的字段的值。
compute设置为方法的名称。计算方法应为self中的每个记录设置计算的字段的值。
compute方法是私有的,这意味着它们不能从表示层调用,只能从业务层调用。私有方法的名称以下划线_开头。
依赖(Dependencies
depends(指定计算方法上的依赖项。每当修改字段的某些依赖项时,ORM使用给定的依赖项来触发字段的重新计算
from odoo import api, fields, models
class TestComputed(models.Model:
_name = "test.computed"
total = fields.Float(compute="_compute_total"
amount = fields.Float(
@api.depends("amount"
def _compute_total(self:
for record in self:
record.total = 2.0 * record.amount
注解
self 是一个集合
self对象是一个结果集(recordset,即一个有序记录集合。支持标准Python集合运算,比如
len(self和iter(self, 外加其它集合操作,比如recs1 | recs2。self 上迭代,会一个接一个的生成记录,其中每个记录本身是长度为1的集合。可以使用
.(比如record.name访问单条记录的字段或者给字段赋值。
@api.depends('debit', 'credit'
def _compute_balance(self:
for line in self:
line.balance = line.debit - line.credit
练习--计算总面积
- 添加
- 添加字段到表单视图,正如本章目标中展示的那样
total_area 字段到 estate.property。该字段被定义为living_area 和 garden_area的总和。
对于关系型字段,可以使用通过字段的路径作为依赖项:
description = fields.Char(compute="_compute_description"
partner_id = fields.Many2one("res.partner"
@api.depends("partner_id.name"
def _compute_description(self:
for record in self:
record.description = "Test for partner %s" % record.partner_id.name
示例以 Many2one为例,针对 Many2many 或者 One2many一样的。
@api.depends('line_ids.amount_type'
def _compute_show_decimal_separator(self:
for record in self:
record.show_decimal_separator = any(l.amount_type == 'regex' for l in record.line_ids
修改odoo14\custom\estate\models\estate_property.py
from odoo import models, fields
为
from odoo import models, fields, api
最末尾添加以下内容
total_area = fields.Integer(compute='_compute_total_area'
@api.depends("garden_area, living_area"
def _compute_total_area(self:
for record in self:
record.total_area = record.living_area + record.garden_area
修改odoo14\custom\estate\views\estate_property_views.xml,estate_property_view_form视图,Description描述页,添加total_area字段
<page string="Description">
<group>
<field name="description"></field>
<field name="bedrooms"></field>
<field name="living_area"></field>
<field name="facades"></field>
<field name="garage"></field>
<field name="garden"></field>
<field name="garden_area"></field>
<field name="garden_orientation"></field>
<field name="total_area" string="Total Area"></field><!--本次添加的内容-->
</group>
</page>
重启服务,刷新浏览器验证效果
练习--计算最佳报价
- 添加
- 添加该字段到表单视图,正如本章目标中的第一个动画
best_price字段到estate.property。该字段被定义为最高报价
mapped( 方法,查看示例
writeoff_amount = sum(writeoff_lines.mapped('amount_currency'
修改odoo14\custom\estate\models\estate_property.py,在total_area下方添加best_price
best_price = fields.Float(compute='_compute_best_offer'
最末尾添加以下函数
@api.depends('offer_ids.price'
def _compute_best_offer(self:
for record in self:
prices = record.mapped('offer_ids.price'
if prices:
record.best_price = max(prices
else:
record.best_price = 0.00
修改odoo14\custom\estate\views\estate_property_views.xml文件estate_property_view_form视图
<group>
<field name="expected_price" string="Expected Price"></field>
<field name="selling_price" string="Selling Price"></field>
</group>
修改为
<group>
<field name="expected_price" string="Expected Price"></field>
<field name="best_price" string="Best Price" />
<field name="selling_price" string="Selling Price"></field>
</group>
重启服务,验证效果(参考本章目标中第一个动画连接
Inverse函数
某些情况下,可以直接设置值可能会很有用。在我们的房产示例中,我们可以定义报价的有效期间并设置有效日期。我们希望能够设置有效期间或日期,并且两者之间相互影响。
inverse函数的能力:
from odoo import api, fields, models
class TestComputed(models.Model:
_name = "test.computed"
total = fields.Float(compute="_compute_total", inverse="_inverse_total"
amount = fields.Float(
@api.depends("amount"
def _compute_total(self:
for record in self:
record.total = 2.0 * record.amount
def _inverse_total(self:
for record in self:
record.amount = record.total / 2.0
一个简单的示例
@api.depends('partner_id.email'
def _compute_email_from(self:
for lead in self:
if lead.partner_id.email and lead.partner_id.email != lead.email_from:
lead.email_from = lead.partner_id.email
def _inverse_email_from(self:
for lead in self:
if lead.partner_id and lead.email_from != lead.partner_id.email:
lead.partner_id.email = lead.email_from
compute方法设置字段,而inverse方法设置字段的相关性。
注意,保存记录时调用inverse方法,而每次更改依赖项时调用compute方法。
练习--为报价计算一个有效期
- 添加以下字段到
estate.property.offer 模型:
| Field | Type | Default |
|---|---|---|
| validity | Integer | 7 |
| date_deadline | Date |
date_deadline 为一个计算的字段,定义为 create_date和 validity两个字段的和。定义一个适当的inverse函数这样,以便用户可以编辑 create_date或 validity。
create_date 仅在记录创建时被填充,因此需要一个回退,防止创建时的奔溃
- 在表单和列表视图中添加字段,正如本章目标中显示的第二个动画中的一样。
odoo14\custom\estate\models\estate_property_offer.py
from odoo import models, fields
修改为
from odoo import models, fields, api
from datetime import timedelta
末尾添加以下代码
validity = fields.Integer(default=7
date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline'
@api.depends('validity', 'create_date'
def _compute_date_deadline(self:
for record in self:
if record.create_date:
record.date_deadline = record.create_date.date( + timedelta(days=record.validity
else:
record.date_deadline = datetime.now(.date( + timedelta(days=record.validity
@api.depends('validity', 'create_date'
def _inverse_date_deadline(self:
for record in self:
if record.create_date:
record.validity = (record.date_deadline - record.create_date.date(.days
else:
record.validity = 7
修改odoo14\custom\estate\views\estate_property_offer_views.xml
<?xml version="1.0"?>
<odoo>
<record id="estate_property_offer_view_tree" model="ir.ui.view">
<field name="name">estate.property.offer.tree</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<tree string="PropertyOffers">
<field name="price" string="Price"/>
<field name="partner_id" string="partner ID"/>
<field name="validity" string="Validity(days)"/>
<field name="date_deadline" string="Deadline"/>
<field name="status" string="Status"/>
</tree>
</field>
</record>
<record id="estate_property_offer_view_form" model="ir.ui.view">
<field name="name">estate.property.offer.form</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<form string="estate property offer form">
<sheet>
<group>
<field name="price" string="Price"/>
<field name="validity" string="Validity(days)"/>
<field name="date_deadline" string="Deadline"/>
<field name="partner_id" string="partner ID"/>
<field name="status" string="Status"/>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
重启服务,浏览器中验证(参考本章目标中的第二个动画视图
其它信息
search 方法。该主题不在训练范围内,所以,这里不做介绍。一个简单示例
is_ongoing = fields.Boolean('Is Ongoing', compute='_compute_is_ongoing', search='_search_is_ongoing'
另一个解决方法是使用store=True属性存储该字段。虽然这通常很方便,但请注意给模型增加的潜在计算压力。让我们重新使用我们的示例。复用我们的示例:
description = fields.Char(compute="_compute_description", store=True
partner_id = fields.Many2one("res.partner"
@api.depends("partner_id.name"
def _compute_description(self:
for record in self:
record.description = "Test for partner %s" % record.partner_id.name
每次partnername被改变, 自动为所有引用了它的记录更新 description 当数以百万计的记录需要重新计算时,这可能会很快会变得无法承受
通常,在定义计算的字段时,必须始终牢记性能。要计算的字段越复杂(例如,具有大量依赖项或当计算的字段依赖于其他计算的字段时),计算所需的时间就越长。请务必事先花一些时间评估计算的字段的成本。大多数时候,只有当您的代码到达生产服务器时,你才意识到它会减慢整个过程。
Onchanges
参考: 主题关联文档可查看onchange(:
“onchange”机制为客户端界面提供了一种,无论用户合适填写字段值更新表单,都无需存储任何东西到数据库的一种方法。为了实现这一点,我们定义了一个方法,其中self表示表单视图中的记录,并用 onchange(修饰该方法,以指明它由哪个字段触发。你对self所做的任何更改都将反映在表单上:
from odoo import api, fields, models
class TestOnchange(models.Model:
_name = "test.onchange"
name = fields.Char(string="Name"
description = fields.Char(string="Description"
partner_id = fields.Many2one("res.partner", string="Partner"
@api.onchange("partner_id"
def _onchange_partner_id(self:
self.name = "Document for %s" % (self.partner_id.name
self.description = "Default description for %s" % (self.partner_id.name
这个例子中,修改partner的同时也将改变名称和描述值。最终取决于用户是否修改名称和描述值。 同时,需要注意的是,不要循环遍历 self,因为该方法在表单视图中触发,self总是代表单条记录。
练习--为花园面积和朝向赋值
estate.property模型中创建 onchange 方法以便当勾选花园时,设置花园面积(10和朝向(North,未勾选时,移除花园面积和朝向值。
odoo14\custom\estate\models\estate_property.py,末尾添加一下代码
@api.onchange("garden"
def _onchange_garden(self:
if self.garden:
self.garden_area = 10
self.garden_orientation = 'North'
else:
self.garden_area = 0
self.garden_orientation = ''
重启服务,验证效果(预期效果参考动画:https://www.odoo.com/documentation/14.0/zh_CN/_images/onchange.gif
其它信息
@api.onchange('provider', 'check_validity'
def onchange_check_validity(self:
if self.provider == 'authorize' and self.check_validity:
self.check_validity = False
return {'warning': {
'title': _("Warning",
'message': ('This option is not supported for Authorize.net'}}
如何使用它们?
对于computed field 和Onchanges的使用没有严格的规则。
非常糟糕的想法,因为在以编程方式创建记录时不会自动触发onchanges;它们仅在表单视图中触发。
computed field往往更容易调试:这样的字段是由给定的方法设置的,因此很容易跟踪设置值的时间。另一方面,onchanges可能会令人困惑:很难知道onchange的程度。由于几个onchange方法可能会设置相同的字段,因此跟踪值的来源很容易变得困难。
存储computed fields时,请密切注意依赖项。当计算字段依赖于其他计算字段时,更改值可能会触发大量重新计算。这会导致性能不佳。
编程笔记 » odoo 开发入门教程系列-计算的字段和变更(Computed Fields And Onchanges)