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

news2024/11/18 15:23:22

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

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

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

计算的字段(Computed Fields)

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

本章目标

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

预期效果:

img

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

预期效果:

img

)

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

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

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

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

依赖(Dependencies)

计算的字段的值通常取决于计算记录中其他字段的值。ORM期望开发人员使用修饰符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.xmlestate_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函数

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

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

为了支持这个需求,odoo提供了使用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 模型:
FieldTypeDefault
validityInteger7
date_deadlineDate

其中,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 当数以百万计的记录需要重新计算时,这可能会很快会变得无法承受

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

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

Onchanges

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

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

“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)

其它信息

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

    @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的使用没有严格的规则。

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/791529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每日一题7.26 23

162. 寻找峰值https://leetcode.cn/problems/find-peak-element/ 峰值元素是指其值严格大于左右相邻值的元素。 给你一个整数数组 nums&#xff0c;找到峰值元素并返回其索引。数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回 任何一个峰值 所在位置即可。 你可…

java中判断list是否为空

java中判断list是否为空是日常代码中经常遇到的问题。最近发现一个Utils提供的方法可以一步判断。 废话不多说&#xff0c;直接上代码&#xff01; ArrayList<String> arrayList new ArrayList<>(); System.out.println("集合1&#xff1a;" Collecti…

SQL-每日一题【620.有趣的电影】

题目 某城市开了一家新的电影院&#xff0c;吸引了很多人过来看电影。该电影院特别注意用户体验&#xff0c;专门有个 LED显示板做电影推荐&#xff0c;上面公布着影评和相关电影描述。 作为该电影院的信息部主管&#xff0c;您需要编写一个 SQL查询&#xff0c;找出所有影片…

链表和C++ std::list详解

文章目录 1. 链表和std::list2. list的用法2.1 list的定义和声明2.2 成员函数2.2.1 基本函数构造函数operatorassignget_allocator 2.2.2 元素访问frontback 2.2.3 迭代器begin、end和cbegin、cendrbegin、rend和crbegin、crend 2.2.4 容量emptysizemax_size 2.2.5 修改器clear…

扩展 Python 的 unittest 框架

目录 前言&#xff1a; 如何控制 unittest 用例执行的顺序呢&#xff1f; 方式 1&#xff0c;通过 TestSuite 类的 addTest 方法&#xff0c;按顺序加载测试用例 方式 2&#xff0c;通过修改函数名的方式 那就造个轮子吧 前言&#xff1a; Python 的 unittest 模块提供了…

做好防雷检测的意义和作用

防雷检测是指对雷电防护装置的性能、质量和安全进行检测的活动&#xff0c;是保障人民生命财产和公共安全的重要措施。我国对防雷检测行业有明确的国家标准和管理办法&#xff0c;要求从事防雷检测的单位和人员具备相应的资质和能力&#xff0c;遵守相关的技术规范和规程&#…

Cpp 01 — namespace命名空间、C++的输入与输出、缺省参数、函数重载、引用、隐式类型转换

前言&#xff1a;本文章主要用于个人复习&#xff0c;追求简洁&#xff0c;感谢大家的参考、交流和搬运&#xff0c;后续可能会继续修改和完善。 因为是个人复习&#xff0c;会有部分压缩和省略。 一、namespace命名空间 C使用命名空间(namespace)来避免命名冲突。 在定义一个…

初阶C语言-分支和循环语句(下)

“花会沿途盛开&#xff0c;以后的路也是。” 今天我们一起来继续学完分支语句和循环语句。 分支和循环 3.循环语句3.4 do...while()循环3.4.1 do语句的用法 3.5关于循环的一些练习3.6 goto语句 3.循环语句 3.4 do…while()循环 3.4.1 do语句的用法 do循环语句;//当循环语句…

【unity】ShaderGraph学习笔记

【unity】ShaderGraph学习笔记 创建ShaderGraph 创建URP的shaderGraph文件 在Project面板里Create→ShaderGraph→URP→这里主要有几个选项 Lit Shader Graph&#xff1a;有光照三维着色器 Unlit Shader Graph&#xff1a;无光照三维着色器 Sprite Custom Lit Shader Gra…

【解决方案】医疗行业资产定位管理方案-蓝牙资产定位方案

蓝牙资产定位追踪技术已经开始应用于多种行业的资产、设备管理方面&#xff0c;有不少机构、企业开始尝试使用实时蓝牙定位系统对其各类资产、机器设备、工具、材料、产品等进行精准定位&#xff0c;提高各类资产、设备的管理和使用效率&#xff0c;从而节省运营成本。 医疗行业…

汽配企业建设数字化工厂的步骤是什么

随着信息技术的迅猛发展&#xff0c;汽车行业也面临着数字化转型的迫切需求。汽配企业作为汽车产业链上重要的一环&#xff0c;也需要积极采取措施&#xff0c;建设数字化工厂系统&#xff0c;以适应市场竞争的变化。下面将介绍建设汽配企业数字化工厂的具体步骤。 第一步&…

在niso ii中读写EPCS时出现“Can‘t open flash device”

在niso ii中读写EPCS时出现“Can’t open flash device”&#xff0c;即alt_flash_open_dev(EPCS_FLASH_NAME)函数返回0。 Cant open flash device函数alt_flash_open_dev(EPCS_FLASH_NAME)打开epcs不成功&#xff0c;返回为0&#xff0c;试了几次硬件重新烧入、重启开发板还是…

呕心沥血解决xampp启动mysql异常停止due to a blocked port, missing dependencies问题

Error: MySQL shutdown unexpectedly.<br> This may be due to a blocked port, missing dependencies 这一行就是我问题的开始。 原因是因为&#xff0c;之前一直使用xampp用作本地mysql的启动管理&#xff0c;是个很好用的工具&#xff0c;但是近日想要给一个项目配…

STM32速成笔记—串口IAP

本文涉及到串口通信和Flash知识&#xff0c;对于这部分知识不熟悉的小伙伴可以到博主STM32速成笔记专栏查看。 文章目录 一、串口IAP简介1.1 什么是IAP1.2 STM32下载程序 二、串口IAP有什么作用三、启动流程3.1 正常启动流程3.2 加入IAP后的启动流程 四、必备知识4.1 修改程序运…

日常开发中Git遇到的问题-记录贴

日常开发中Git遇到的问题 前言场景一&#xff1a;clone代码并进行开发完毕后&#xff0c;进行代码合并时要求在新分支下提交代码场景二&#xff1a;远程分支被删除后&#xff0c;本地分支缓存需要更新&#xff0c;防止提交错误 前言 本篇博客只是用来记录平时开发过程之中用gi…

从头开始:数据结构和算法入门(时间复杂度、空间复杂度)

目录 文章目录 前言 1.算法效率 1.1 如何衡量一个算法的好坏 1.2 算法的复杂度 2.时间复杂度 2.1 时间复杂度的概念 2.2 大O的渐进表示法 2.3常见时间复杂度计算 3.空间复杂度 4.常见复杂度对比 总结 前言 C语言的学习篇已经结束&#xff0c;今天开启新的篇章——数据结构和算…

Hive视图

hive的视图 简介 hive的视图简单理解为逻辑上的表hive只支持逻辑视图&#xff0c;不支持物化视图视图存在的意义 对数据进行局部暴露&#xff08;涉及隐私的数据不暴露&#xff09;简化复杂查询 创建视图&#xff1a; create view if not exists v_1 as select uid,movie f…

国内十大精准的现货黄金价格走势图软件最新排名(综合版)

选择国内现货黄金价格走势图软件时&#xff0c;需要考虑几个因素。首先&#xff0c;软件的稳定性和可靠性至关重要。应选择有良好声誉和长期稳定运行的平台&#xff0c;以确保价格数据的准确性和及时性。其次&#xff0c;要选择功能齐全的软件。较为优秀的软件应该提供多种技术…

C++学习——类和对象(三)

接着我们就继续学习我们C当中的相关的知识。 一&#xff1a;初始化列表 还记得我们之前讲过的构造函数吗&#xff1f;我们在构造函数的函数体里面可以对对象当中的属性进行初始化。但是作为我们的构造函数来说&#xff0c;初始化的方式并不只是在构造函数体当中进行赋值。我们…

Junit4+MultiThreadedTestRunner 并发测试

目录 前言&#xff1a; 具体步骤&#xff1a; 前言&#xff1a; 在进行软件测试时&#xff0c;我们需要确保应用程序在不同的并发情况下仍能正常运行。 最近要对一个类里的方法&#xff0c;进行压力测试。下面讲一下写出的 Junit4 的并发测试代码吧。如果要复用的话&#x…