计算的字段和变更(Computed Fields And Onchanges)

模型之间的关系是任何Odoo模块的关键组成部分。它们对于任何业务案例的建模都是必要的。然而,我们可能需要给定模型中字段之间的链接。有时,一个字段的值是根据其他字段的值确定的,有时我们希望帮助用户输入数据。

“Computed Fields And Onchanges”的概念支持这些情况。虽然本章在技术上并不复杂,但这两个概念的语义都非常重要。这也是我们第一次编写Python逻辑。到目前为止,除了类定义和字段声明之外,我们还没有编写任何其他东西。

计算的字段(Computed Fields)

参考: 主题关联文档可查阅 Computed Fields.

本章目标

  • 在房地产模型中,自动计算总的面积和最佳报价

预期效果:

  • 在地产报价模型中,自动计算合法的日期且可被更新

预期效果:

在我们的房地产模块中,我们定义了生活区和花园区。自然地我们将总面积定义这两者的总和,我们将为此使用计算的字段的概念,即给定字段的值将从其他字段的值中计算出来。

到目前为止,字段已直接存储在数据库中并直接从数据库中检索。字段也可以被计算。在这种情况下,不会从数据库中检索字段的值,而是通过调用模型的方法来动态计算的字段的值。

要创建计算的字段,请创建字段并将其属性compute设置为方法的名称。计算方法应为self中的每个记录设置计算的字段的值。

按约定,compute方法是私有的,这意味着它们不能从表示层调用,只能从业务层调用。私有方法的名称以下划线_开头。

依赖(Dependencies)

计算的字段的值通常取决于计算记录中其他字段的值。ORM期望开发人员使用修饰符depends()指定计算方法上的依赖项。每当修改字段的某些依赖项时,ORM使用给定的依赖项来触发字段的重新计算

  1. from odoo import api, fields, models
  2. class TestComputed(models.Model):
  3. _name = "test.computed"
  4. total = fields.Float(compute="_compute_total")
  5. amount = fields.Float()
  6. @api.depends("amount")
  7. def _compute_total(self):
  8. for record in self:
  9. record.total = 2.0 * record.amount

注解

self 是一个集合

self对象是一个结果集(recordset),即一个有序记录集合。支持标准Python集合运算,比如len(self)iter(self), 外加其它集合操作,比如 recs1 | recs2

self 上迭代,会一个接一个的生成记录,其中每个记录本身是长度为1的集合。可以使用.(比如 record.name)访问单条记录的字段或者给字段赋值。

一个简单的示例

  1. @api.depends('debit', 'credit')
  2. def _compute_balance(self):
  3. for line in self:
  4. line.balance = line.debit - line.credit
练习--计算总面积
  • 添加total_area 字段到 estate.property。该字段被定义为living_areagarden_area的总和。
  • 添加字段到表单视图,正如本章目标中展示的那样

对于关系型字段,可以使用通过字段的路径作为依赖项:

  1. description = fields.Char(compute="_compute_description")
  2. partner_id = fields.Many2one("res.partner")
  3. @api.depends("partner_id.name")
  4. def _compute_description(self):
  5. for record in self:
  6. record.description = "Test for partner %s" % record.partner_id.name

示例以 Many2one为例,针对 Many2many 或者 One2many一样的。

一个简单的示例

  1. @api.depends('line_ids.amount_type')
  2. def _compute_show_decimal_separator(self):
  3. for record in self:
  4. record.show_decimal_separator = any(l.amount_type == 'regex' for l in record.line_ids)

修改odoo14\custom\estate\models\estate_property.py

修改

  1. from odoo import models, fields

  1. from odoo import models, fields, api

最末尾添加以下内容

  1. total_area = fields.Integer(compute='_compute_total_area')
  2. @api.depends("garden_area, living_area")
  3. def _compute_total_area(self):
  4. for record in self:
  5. record.total_area = record.living_area + record.garden_area

修改odoo14\custom\estate\views\estate_property_views.xmlestate_property_view_form视图,Description描述页,添加total_area字段

  1. <page string="Description">
  2. <group>
  3. <field name="description"></field>
  4. <field name="bedrooms"></field>
  5. <field name="living_area"></field>
  6. <field name="facades"></field>
  7. <field name="garage"></field>
  8. <field name="garden"></field>
  9. <field name="garden_area"></field>
  10. <field name="garden_orientation"></field>
  11. <field name="total_area" string="Total Area"></field><!--本次添加的内容-->
  12. </group>
  13. </page>

重启服务,刷新浏览器验证效果



)

练习--计算最佳报价
  • 添加best_price字段到estate.property。该字段被定义为最高报价
  • 添加该字段到表单视图,正如本章目标中的第一个动画

提示:你可能会想用 mapped() 方法,查看示例

  1. writeoff_amount = sum(writeoff_lines.mapped('amount_currency'))

修改odoo14\custom\estate\models\estate_property.py,在total_area下方添加best_price

  1. best_price = fields.Float(compute='_compute_best_offer')

最末尾添加以下函数

  1. @api.depends('offer_ids.price')
  2. def _compute_best_offer(self):
  3. for record in self:
  4. prices = record.mapped('offer_ids.price')
  5. if prices:
  6. record.best_price = max(prices)
  7. else:
  8. record.best_price = 0.00

修改odoo14\custom\estate\views\estate_property_views.xml文件estate_property_view_form视图

  1. <group>
  2. <field name="expected_price" string="Expected Price"></field>
  3. <field name="selling_price" string="Selling Price"></field>
  4. </group>

修改为

  1. <group>
  2. <field name="expected_price" string="Expected Price"></field>
  3. <field name="best_price" string="Best Price" />
  4. <field name="selling_price" string="Selling Price"></field>
  5. </group>

重启服务,验证效果(参考本章目标中第一个动画连接)

Inverse函数

你可能已经注意到,计算的字段默认总是只读的。这正是我们期望的,因为不支持用户设置值。

某些情况下,可以直接设置值可能会很有用。在我们的房产示例中,我们可以定义报价的有效期间并设置有效日期。我们希望能够设置有效期间或日期,并且两者之间相互影响。

为了支持这个需求,odoo提供了使用inverse函数的能力:

  1. from odoo import api, fields, models
  2. class TestComputed(models.Model):
  3. _name = "test.computed"
  4. total = fields.Float(compute="_compute_total", inverse="_inverse_total")
  5. amount = fields.Float()
  6. @api.depends("amount")
  7. def _compute_total(self):
  8. for record in self:
  9. record.total = 2.0 * record.amount
  10. def _inverse_total(self):
  11. for record in self:
  12. record.amount = record.total / 2.0

一个简单的示例

  1. @api.depends('partner_id.email')
  2. def _compute_email_from(self):
  3. for lead in self:
  4. if lead.partner_id.email and lead.partner_id.email != lead.email_from:
  5. lead.email_from = lead.partner_id.email
  6. def _inverse_email_from(self):
  7. for lead in self:
  8. if lead.partner_id and lead.email_from != lead.partner_id.email:
  9. 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_datevalidity两个字段的和。定义一个适当的inverse函数这样,以便用户可以编辑 create_datevalidity

提示: create_date 仅在记录创建时被填充,因此需要一个回退,防止创建时的奔溃

  • 在表单和列表视图中添加字段,正如本章目标中显示的第二个动画中的一样。

修改odoo14\custom\estate\models\estate_property_offer.py

  1. from odoo import models, fields

修改为

  1. from odoo import models, fields, api
  2. from datetime import timedelta

末尾添加以下代码

  1. validity = fields.Integer(default=7)
  2. date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline')
  3. @api.depends('validity', 'create_date')
  4. def _compute_date_deadline(self):
  5. for record in self:
  6. if record.create_date:
  7. record.date_deadline = record.create_date.date() + timedelta(days=record.validity)
  8. else:
  9. record.date_deadline = datetime.now().date() + timedelta(days=record.validity)
  10. @api.depends('validity', 'create_date')
  11. def _inverse_date_deadline(self):
  12. for record in self:
  13. if record.create_date:
  14. record.validity = (record.date_deadline - record.create_date.date()).days
  15. else:
  16. record.validity = 7

修改odoo14\custom\estate\views\estate_property_offer_views.xml

  1. <?xml version="1.0"?>
  2. <odoo>
  3. <record id="estate_property_offer_view_tree" model="ir.ui.view">
  4. <field name="name">estate.property.offer.tree</field>
  5. <field name="model">estate.property.offer</field>
  6. <field name="arch" type="xml">
  7. <tree string="PropertyOffers">
  8. <field name="price" string="Price"/>
  9. <field name="partner_id" string="partner ID"/>
  10. <field name="validity" string="Validity(days)"/>
  11. <field name="date_deadline" string="Deadline"/>
  12. <field name="status" string="Status"/>
  13. </tree>
  14. </field>
  15. </record>
  16. <record id="estate_property_offer_view_form" model="ir.ui.view">
  17. <field name="name">estate.property.offer.form</field>
  18. <field name="model">estate.property.offer</field>
  19. <field name="arch" type="xml">
  20. <form string="estate property offer form">
  21. <sheet>
  22. <group>
  23. <field name="price" string="Price"/>
  24. <field name="validity" string="Validity(days)"/>
  25. <field name="date_deadline" string="Deadline"/>
  26. <field name="partner_id" string="partner ID"/>
  27. <field name="status" string="Status"/>
  28. </group>
  29. </sheet>
  30. </form>
  31. </field>
  32. </record>
  33. </odoo>

重启服务,浏览器中验证(参考本章目标中的第二个动画视图)

其它信息

默认的,计算的字段不会存到数据库中,因此,不可能基于计算的字段进行搜索,除非定义一个search 方法。该主题不在训练范围内,所以,这里不做介绍。一个简单示例

  1. is_ongoing = fields.Boolean('Is Ongoing', compute='_compute_is_ongoing', search='_search_is_ongoing')

另一个解决方法是使用store=True属性存储该字段。虽然这通常很方便,但请注意给模型增加的潜在计算压力。让我们重新使用我们的示例。复用我们的示例:

  1. description = fields.Char(compute="_compute_description", store=True)
  2. partner_id = fields.Many2one("res.partner")
  3. @api.depends("partner_id.name")
  4. def _compute_description(self):
  5. for record in self:
  6. record.description = "Test for partner %s" % record.partner_id.name

每次partnername被改变, 自动为所有引用了它的记录更新 description 当数以百万计的记录需要重新计算时,这可能会很快会变得无法承受

还值得注意的是,计算的字段可以依赖于另一个计算的字段。ORM足够聪明,可以按照正确的顺序正确地重新计算所有依赖项……但有时会以降低性能为代价。

通常,在定义计算的字段时,必须始终牢记性能。要计算的字段越复杂(例如,具有大量依赖项或当计算的字段依赖于其他计算的字段时),计算所需的时间就越长。请务必事先花一些时间评估计算的字段的成本。大多数时候,只有当您的代码到达生产服务器时,你才意识到它会减慢整个过程。

Onchanges

参考: 主题关联文档可查看onchange():

在我们的房地产模块中,我们还想帮助用户输入数据。设置“garden”字段后,我们希望为花园面积和朝向提供默认值。此外,当“花园”字段未设置时,我们希望花园面积和重置为零,并删除朝向。在这种情况下,给定字段的值会影响其他字段的值。

“onchange”机制为客户端界面提供了一种,无论用户合适填写字段值更新表单,都无需存储任何东西到数据库的一种方法。为了实现这一点,我们定义了一个方法,其中self表示表单视图中的记录,并用 onchange()修饰该方法,以指明它由哪个字段触发。你对self所做的任何更改都将反映在表单上:

  1. from odoo import api, fields, models
  2. class TestOnchange(models.Model):
  3. _name = "test.onchange"
  4. name = fields.Char(string="Name")
  5. description = fields.Char(string="Description")
  6. partner_id = fields.Many2one("res.partner", string="Partner")
  7. @api.onchange("partner_id")
  8. def _onchange_partner_id(self):
  9. self.name = "Document for %s" % (self.partner_id.name)
  10. 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,末尾添加一下代码

  1. @api.onchange("garden")
  2. def _onchange_garden(self):
  3. if self.garden:
  4. self.garden_area = 10
  5. self.garden_orientation = 'North'
  6. else:
  7. self.garden_area = 0
  8. self.garden_orientation = ''

重启服务,验证效果(预期效果参考动画:https://www.odoo.com/documentation/14.0/zh_CN/_images/onchange.gif)

其它信息

Onchanges方法也可以返回非阻塞告警消息(示例)

  1. @api.onchange('provider', 'check_validity')
  2. def onchange_check_validity(self):
  3. if self.provider == 'authorize' and self.check_validity:
  4. self.check_validity = False
  5. return {'warning': {
  6. 'title': _("Warning"),
  7. 'message': ('This option is not supported for Authorize.net')}}

如何使用它们?

对于computed field 和Onchanges的使用没有严格的规则。

在许多情况下,可以使用computed field和onchanges来实现相同的结果。始终首选computed field,因为它们也是在表单视图上下文之外触发的。永远不要使用onchange将业务逻辑添加到模型中。这是一个非常糟糕的想法,因为在以编程方式创建记录时不会自动触发onchanges;它们仅在表单视图中触发。

computed field和onchanges的常见陷阱是试图通过添加过多逻辑来变得“过于智能”。这可能会产生与预期相反的结果:终端用户被所有自动化所迷惑。

computed field往往更容易调试:这样的字段是由给定的方法设置的,因此很容易跟踪设置值的时间。另一方面,onchanges可能会令人困惑:很难知道onchange的程度。由于几个onchange方法可能会设置相同的字段,因此跟踪值的来源很容易变得困难。

存储computed fields时,请密切注意依赖项。当计算字段依赖于其他计算字段时,更改值可能会触发大量重新计算。这会导致性能不佳。

odoo 开发入门教程系列-计算的字段和变更(Computed Fields And Onchanges)的更多相关文章

  1. 基于Nodejs生态圈的TypeScript+React开发入门教程

    基于Nodejs生态圈的TypeScript+React开发入门教程   概述 本教程旨在为基于Nodejs npm生态圈的前端程序开发提供入门讲解. Nodejs是什么 Nodejs是一个高性能Ja ...

  2. ActiveMQ详细入门教程系列(一)

    一.什么是消息中间件 两个系统或两个客户端之间进行消息传送,利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成.通过提供消息传递和消息排队模型,它可以在分布式环境下 ...

  3. 一看就懂的Android APP开发入门教程

    一看就懂的Android APP开发入门教程 作者: 字体:[增加 减小] 类型:转载   这篇文章主要介绍了Android APP开发入门教程,从SDK下载.开发环境搭建.代码编写.APP打包等步骤 ...

  4. WPF入门教程系列(一) 创建你的第一个WPF项目

    WPF入门教程系列(一) 创建你的第一个WPF项目 WPF基础知识 快速学习绝不是从零学起的,良好的基础是快速入手的关键,下面先为大家摞列以下自己总结的学习WPF的几点基础知识: 1) C#基础语法知 ...

  5. ENVI Services Engine5.1 应用开发入门教程

    原文地址: ENVI Services Engine5.1 应用开发入门教程_ENVI-IDL中国_新浪博客 http://blog.sina.com.cn/s/blog_764b1e9d0102uy ...

  6. Android Studio JNI开发入门教程

    Android Studio JNI开发入门教程 2016-08-29 14:38 3269人阅读 评论(0) 收藏 举报  分类: JNI(3)    目录(?)[+]   概述 在Andorid ...

  7. SeaJS入门教程系列之使用SeaJS(二)

    SeaJS入门教程系列之使用SeaJS(二) 作者: 字体:[增加 减小] 类型:转载 时间:2014-03-03我要评论 这篇文章主要介绍了SeaJS入门教程系列之使用SeaJS,着重介绍了SeaJ ...

  8. 移动H5开发入门教程:12点webAPP前端开发经验

    如果你是一名移动H5前端开发人员,25学堂的小编认为下面的分享的12点webAPP前端开发经验是你必须掌握的基础知识点.算是一篇移动H5开发入门教程吧! 1. viewport:也就是可视区域.对于桌 ...

  9. Silverlight,Windows 8应用开发实例教程系列汇总

    Kevin Fan分享开发经验,记录开发点滴 Silverlight,Windows 8应用开发实例教程系列汇总 2012-06-18 01:05 by jv9, 2145 阅读, 3 评论, 收藏, ...

  10. WPF入门教程系列二十三——DataGrid示例(三)

    DataGrid的选择模式 默认情况下,DataGrid 的选择模式为“全行选择”,并且可以同时选择多行(如下图所示),我们可以通过SelectionMode 和SelectionUnit 属性来修改 ...

随机推荐

  1. Day10-数组

    数组 一.什么是数组 数组是相同数据类型的有序集合 数字描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成 其中.每一个数据称作一个数组元素,每个数组元素可以通过一个下表来访问它们 二.数组 ...

  2. Ubuntu linux下gcc、g++不同版本的安装和切换

    讲解linux下gcc.g++不同版本的安装和切换 Ubuntu 18.04预装GCC版本为7.3,但有时在编译是需要用的不同gcc版本,下面介绍,如何安装不同的gcc 和g++,并设置根据不同的需要 ...

  3. Linux下查看全部的环境变量

    在Windows下,查看环境变量的命令是:set,这个命令会输出系统当前的环境变量. ... Linux查看环境变量使用env命令显示所有的环境变量 $ env

  4. 狐漠漠养成日记 Cp.00001 开始养成计划

    开始养成计划 今天是我开始这个"狐漠漠养成计划"的第一天(划掉). 看来是昨天出门前忘记保存了,昨天写的几百字内容全都没有了,今天其实已经是计划开始的第二天了. 因为昨天晚上出去喝 ...

  5. Software Engineering homework 3

    博客信息 沈阳航空航天大学计算机学院2020软件工程作业 作业要求 https://edu.cnblogs.com/campus/sau/Computer1701-1705/homework/1061 ...

  6. 3月2号Android开发学习

    (2)视图基础 1.设置视图的高度 视图宽度通过属性Android:layout_width表达,视图高度通过属性android:layout_heigth表达,宽高的取值主要有以下三种 1.matc ...

  7. PVE设置硬盘休眠并解决经常唤醒问题

    查询硬盘编号: ls -l /dev/disk/by-id/ 查询硬盘状态: smartctl -i -n standby /dev/sda |grep "mode"|awk '{ ...

  8. 关于IllegalMonitorStateException异常的解释之一

    注意 在同步控制方法或同步控制块里调用wait(),notify()和notifyAll().如果在非同步控制方法里调用这些方法,程序能通过编译,但运行的时候,将得到IllegalMonitorSta ...

  9. C语言中链表与队列

    https://www.cnblogs.com/lanhaicode/p/10432004.html

  10. 关于数据传递 json

    关于这几种语言的json 操作 Lua local cjson2 = require "cjson" local lua_object = { ["name"] ...