odoo16入门教程第九章 计算字段和onchanges

本文介绍了Odoo模块中计算字段和onchanges的概念,用于根据其他字段动态计算值或更新数据。计算字段依赖于其他字段并通过python逻辑实时计算,而onchanges在用户输入时更新表单。文章强调了计算字段的性能考虑,以及onchanges仅在表单视图中触发的特性,并给出了实践示例。
摘要由CSDN通过智能技术生成

第九章 计算字段和onchanges

模型之间的关系是Odoo模块的关键组件。他们对任何商业情景的模型化都是必要的。然而,我们想连接同一个模型中的不同字段。有时候一个字段的值由其他字段来决定,有时候我们想帮助用户进行数据输入。

这些场景可以通过计算字段和onchanges的概念来支持。虽然这一章在技术上不是很复杂,这两个概念的含义很重要。这也是我们第一次写python逻辑,直到现在除了class定义和字段声明我们还没写过其他东西。

计算字段(Computed Fields)

参考:关于这个主题的文档在这里Computed Fields.

目标: 在这一小节结尾

  • 在房产模型中,总面积和最好的报价应该计算出来。

Compute fields

  • I在房产报价模型中,有效时间可以被计算并且可以被更新。

Compute field with inverse

在我们的房地产模块,我们定义了使用面积和花园面积,那很自然的我们可以定义一个总面积,通过这两个面积求和来实现。 这种情况可以使用计算字段的概念,也就是说,这个字段的值是通过其他字段计算得到的。

到目前为止,字段都是通过数据库来直接存储和查询的。除此之外,字段值还可以被计算出来,在这种情况下,字段的值不是从数据库检索出来的,而是通过一个模型的方法实时计算出来的。

创建一个计算字段,可以新建一个字段并设置compute属性为一个方法的名字, 计算方法会为self中每条记录的的计算字段设置值。

一般来说,计算方法是私有的,也就是说不能通过表现层调用,只能通过逻辑层调用,私有的方法名前缀是下划线_

依赖(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是一个记录集,支持python的集合运算。

可以对for循环对self进行便利操作。 用. 来获取字段的值。

Odoo中有很多关于计算字段的例子,这里有一个简单的例子Here

练习:

计算总面积

  • 给estate.property增加字段 total_area ,通过living_area 和`garden_area 二者求和来计算。
  • 在form视图中增加这个字段

For relational fields it’s possible to use paths through a field as a dependency:

对于关系字段来说,可以使用关联字段生成的路径作为依赖项

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

这个例子是一个多对一的例子,但是对于多对多和1对多来说也有效。这里有个例子 here.

让我们在我们的模块中尝试以下练习。

练习:

计算最好的报价

  • 给estate.property模型增加 best_price字段, 它被定义为最高的报价
  • 将这个字段添加到form view中

Tip: 你可能想使用mapped()方法,这里here 有个简单的例子

    @api.depends("offer_ids.price")
    def _get_max_price(self):
        for rec in self:
            list_price = rec.offer_ids.mapped("price")

            if list_price:
                rec.best_price = max(list_price)
            else:
                rec.best_price =0

Inverse Function

你可能注意到了,计算字段默认是只读的,这是合理的因为用户并不想设置一个值。

但是有时候,用户希望直接设置一个值,在我们的房地产的例子里,我们可以设置一个有效天数然后计算一个日期,也可以先设置一个有效日期反推出有效天数。

为了支持这个功能,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

这里有个例子 here.

一个计算方法设置计算字段同时一个相反的方法设置依赖字段。

注意: inverse方法只有在保存数据的时候被调用,而compute方法在每次依赖项发生变化的时候都会计算。

练习:

计算报价的有效期

  • estate.property.offer model 增加下列字段
FieldTypeDefault
validityInteger7
date_deadlineDate

date_deadline 是一个计算字段依赖于create_date 和validity, 定义一个合适的反推方法,用户可以设置时间或者有效期。

    validity = fields.Integer(string="过期天数",default = 7)
    date_deadline = fields.Date(string="过期时间",compute="_compute_date",inverse="_inverse_validity")
    @api.depends("create_date","validity")
    def _compute_date(self):
        for rec in self:
            if rec.create_date:
                rec.date_deadline = rec.create_date + timedelta(days=rec.validity)
            else:
                rec.date_deadline = fields.date.today() + timedelta(days=rec.validity)

    def _inverse_validity(self):
        for rec in self:
            rec.validity = (rec.date_deadline-rec.create_date.date()).days

Tip: create_date 只有当创建记录的时候产生,所以你需要一个fallback来阻止创建 的时候崩溃。 fallback是啥玩意?

  • 在form和list视图中添加字段,就像上面第二张图片显示的那样。

额外的信息(Additional Information)

计算字段默认不在数据库中保存。 因此查询一个计算字段是不可能的,除非定义一个search方法。 这个主题已经超出了本章的范围,这里有个简单的例子here.

另外一个解决方案是存储数据,通过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

每当partner name变化的时候,引用它的所有的记录的description字段会自动的重新计算, 当记录数达到百万级别的时候,这个代价是非常高昂的。

另外一点也值得注意: 一个计算字段依赖另外一个计算字段,ORM足够聪明按照正确的顺序重新计算,但是有时候也很消耗性能。

一般来说,在定义计算字段时必须始终牢记性能。你要计算的字段越复杂(例如,有很多依赖项,或者当一个计算字段依赖于其他计算字段时),计算所需的时间就越多。总是花一些时间事先评估计算字段的成本。大多数情况下,只有当您的代码到达生产服务器时,您才会意识到它减慢了整个过程。这不好玩。。

Onchanges

参考:关于这个主题的文档在这里onchange():

目标: 这一小节结尾,花园面积给一个默认值10 朝向 北。North.

Onchange

在我们的房地产模块中,我们希望帮助用户输入数据。 当graden字段被设置,我们希望给花园面积和朝向一个默认值。 另外,当graden没有设置的时候,我们希望花园面积重置成0,朝向也重置为空。这种情况意味着给定字段的值会修改其他字段的值。

’ onchange ‘机制为客户端界面提供了一种方法,可以在用户填写字段值时更新表单,而无需将任何内容保存到数据库中。为了实现这一点,我们定义了一个方法,其中’ self '代表表单视图中的记录,并使用’ onchange() '来修饰它,以指定触发它的字段。您可能想尝试使用mapped()方法。请看这里的一个简单示例。你对“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将会同时改变name和description的值。 注意这里不用使用循环。 因为这个方法只有在form视图中被触发,self是一条记录。

练习: 设置花园面积和朝向

在estate.property模型中 新建一个onchange方法,当garden设置为true的时候设置花园面积为10,朝向为北,garden为false的时候,清空这两个字段。

    @api.onchange("garden")
    def set_area(self):
        if self.garden:
            self.garden_area=10
            self.garden_orientation="北"
        else:
            self.garden_area=0
            self.garden_orientation=None

其他信息

onchanges方法可以返回一个非阻塞的警告信息

  return {'warning': {
                'title': _("Warning"),
                'message': ('This option is not supported for Authorize.net')}}

怎么使用他们?

对计算字段和onchanges并没有严格的规则。

很多时候,计算字段和onchanges可以实现相同的结果。 总是首选计算字段,因为它们也会在表单视图的上下文之外触发。永远不要使用onchange向模型添加业务逻辑。这是一个非常糟糕的想法,因为当以编程方式创建记录时,onchanges不会自动触发;它们只在表单视图中被触发。

The usual pitfall of computed fields and onchanges is trying to be ‘too smart’ by adding too much logic. This can have the opposite result of what was expected: the end user is confused from all the automation.

计算字段和onchanges的常见陷阱是试图通过添加太多逻辑来“过于聪明”。这可能会产生与预期相反的结果:最终用户对所有的自动化感到困惑。

Computed fields tend to be easier to debug: such a field is set by a given method, so it’s easy to track when the value is set. Onchanges, on the other hand, may be confusing: it is very difficult to know the extent of an onchange. Since several onchange methods may set the same fields, it easily becomes difficult to track where a value is coming from.

计算字段往往更容易调试:这样的字段是由给定的方法设置的,因此很容易跟踪设置值的时间。另一方面,Onchanges可能会令人困惑:很难知道onchange的范围。由于几个onchange方法可能会设置相同的字段,因此很容易难以跟踪值的来源。

When using stored computed fields, pay close attention to the dependencies. When computed fields depend on other computed fields, changing a value can trigger a large number of recomputations. This leads to poor performance.

当使用存储的计算字段,注意依赖,当计算字段依赖其他的计算字段,改变一个值可能会触发大量的重新计算,这往往导致性能低下。

总结一波:

1 onchanges 只在form视图中起作用,self是单条记录,它只是在客户端起作用,不要有跟后台交互的逻辑
2 计算字段可以用,但是不要过于复杂,self是一个记录集

3 如果要保存计算字段,更要注意值的改变引发的重新计算引发的性能问题。

下一章,让我们看看通过点击按钮来触发商业逻辑。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值