Chapter 8: Computed Fields And Onchanges
第8章:计算字段与触发变更
原文来自:
https://www.odoo.com/documentation/17.0/zh_CN/developer/tutorials/server_framework_101/08_compute_onchange.html
使用通义千问翻译。
测试代码:https://gitee.com/zhang-wei918/estate
The relations between models are a key component of any Odoo module. They are necessary for the modelization of any business case. However, we may want links between the fields within a given model. Sometimes the value of one field is determined from the values of other fields and other times we want to help the user with data entry.
模型之间的关系是任何Odoo模块的关键组成部分。它们对于任何业务案例的模型化都是必需的。然而,我们可能想要在给定模型内的字段之间建立联系。有时一个字段的值是由其他字段的值决定的,其他时候我们想要帮助用户输入数据。
These cases are supported by the concepts of computed fields and onchanges. Although this chapter is not technically complex, the semantics of both concepts is very important. This is also the first time we will write Python logic. Until now we haven’t written anything other than class definitions and field declarations.
这些情况由计算字段和触发变更的概念所支持。尽管这一章在技术上并不复杂,但这两个概念的语义非常重要。这也是我们第一次编写Python逻辑。到目前为止,我们还只编写了类定义和字段声明,没有编写其他内容。
Computed Fields 计算字段
Reference: the documentation related to this topic can be found in Computed Fields.
参考:与本主题相关的文档可以在“计算字段”中找到。
注解
Goal: at the end of this section: 目标:在本节结束时:
- In the property model, the total area and the best offer should be computed:
在属性模型中,总面积和最佳报价应该是计算得出的:
- In the property offer model, the validity date should be computed and can be updated:
在属性报价模型中,有效期应该是计算得出的,并且可以更新:
In our real estate module, we have defined the living area as well as the garden area. It is then natural to define the total area as the sum of both fields. We will use the concept of a computed field for this, i.e. the value of a given field will be computed from the value of other fields.
在我们的房地产模块中,我们已经定义了居住面积和花园面积。然后自然地定义总面积为这两个字段之和。我们将为此使用计算字段的概念,即给定字段的值将根据其他字段的值计算得出。
So far fields have been stored directly in and retrieved directly from the database. Fields can also be computed. In this case, the field’s value is not retrieved from the database but computed on-the-fly by calling a method of the model.
到目前为止,字段是直接存储在数据库中并直接从数据库中检索的。字段也可以是计算得出的。在这种情况下,字段的值不是从数据库中检索的,而是通过调用模型的方法即时计算的。
To create a computed field, create a field and set its attribute compute
to the name of a method. The computation method should set the value of the computed field for every record in self
.
要创建一个计算字段,创建一个字段并将其属性compute设置为一个方法的名称。计算方法应该为self中的每条记录设置计算字段的值。
By convention, compute
methods are private, meaning that they cannot be called from the presentation tier, only from the business tier (see Chapter 1: Architecture Overview). Private methods have a name starting with an underscore _
.
按照惯例,计算方法应该是私有的,这意味着它们不能从表示层调用,只能从业务层调用(见第1章:架构概览)。私有方法的名称以下划线_开头。
Dependencies 依赖性
The value of a computed field usually depends on the values of other fields in the computed record. The ORM expects the developer to specify those dependencies on the compute method with the decorator depends()
. The given dependencies are used by the ORM to trigger the recomputation of the field whenever some of its dependencies have been modified:
计算字段的值通常依赖于计算记录中其他字段的值。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
is a collection. self是一个集合。
The object self
is a recordset, i.e. an ordered collection of records. It supports the standard Python operations on collections, e.g. len(self)
and iter(self)
, plus extra set operations such as recs1 | recs2
.
对象self是一个记录集,即记录的有序集合。它支持集合的标准Python操作,例如len(self)
和iter(self)
,以及额外的集合操作,如recs1 | recs2
。
Iterating over self
gives the records one by one, where each record is itself a collection of size 1. You can access/assign fields on single records by using the dot notation, e.g. record.name
.
遍历self会逐个给出记录,其中每个记录本身是一个大小为1的集合。你可以使用点表示法在单个记录上访问/赋值字段,例如record.name
。
Many examples of computed fields can be found in Odoo. Here is a simple one.
在Odoo中可以找到许多计算字段的例子。这里是一个简单的示例。
Exercise
Compute the total area. 计算总面积。
- Add the
total_area
field toestate.property
. It is defined as the sum of theliving_area
and thegarden_area
.
将total_area
字段添加到estate.property
中。它被定义为living_area
和garden_area
的总和。 - Add the field in the form view as depicted on the first image of this section’s Goal.
按照本节目标中的第一张图片所示,在表单视图中添加字段。
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
The example is given with a Many2one
, but it is valid for Many2many
or a One2many
. An example can be found here.
示例给出了一个Many2one的情况,但它适用于Many2many或One2many。可以在这里找到一个例子。
Let’s try it in our module with the following exercise!
让我们尝试用下面的练习在我们的模块中实现它!
Exercise
Compute the best offer. 计算最佳报价。
- Add the
best_price
field toestate.property
. It is defined as the highest (i.e. maximum) of the offers’price
.
将best_price
字段添加到estate.property
中。它被定义为报价中最高的(即最大)价格。 - Add the field to the form view as depicted in the first image of this section’s Goal.
按照本节目标中的第一张图片所示,在表单视图中添加字段。
Tip: you might want to try using the mapped()
method. See here for a simple example.
提示:你可能想尝试使用mapped()
方法。请参阅这里获取一个简单的例子。
Inverse Function 逆向函数
You might have noticed that computed fields are read-only by default. This is expected since the user is not supposed to set a value.
你可能已经注意到,默认情况下计算字段是只读的。这是预期的,因为用户不应该设置一个值。
In some cases, it might be useful to still be able to set a value directly. In our real estate example, we can define a validity duration for an offer and set a validity date. We would like to be able to set either the duration or the date with one impacting the other.
在某些情况下,能够直接设置一个值可能是有用的。在我们的房地产示例中,我们可以为一个报价定义一个有效期限,并设置一个有效日期。我们希望能够直接设置期限或日期,并且一个影响另一个。
To support this Odoo provides the ability to use an inverse
function:
为了支持这一点,Odoo提供了使用逆向函数的能力:
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
An example can be found here. 示例可以在此处找到。
A compute method sets the field while an inverse method sets the field’s dependencies.
计算方法设置了字段,而逆向方法设置了字段的依赖项。
Note that the inverse
method is called when saving the record, while the compute
method is called at each change of its dependencies.
请注意,当保存记录时调用逆向方法,而在依赖项每次更改时调用计算方法。
Exercise
Compute a validity date for offers. 为报价计算有效日期。
- Add the following fields to the
estate.property.offer
model:
向estate.property.offer模型添加以下字段:
Field | Type | Default |
---|---|---|
validity | Integer | 7 |
date_deadline | Date |
Where date_deadline
is a computed field which is defined as the sum of two fields from the offer: the create_date
and the validity
. Define an appropriate inverse function so that the user can set either the date or the validity.
其中date_deadline是一个计算字段,它被定义为报价中的两个字段之和:create_date和validity。定义一个适当的逆向函数,以便用户可以设置日期或有效性。
Tip: the create_date
is only filled in when the record is created, therefore you will need a fallback to prevent crashing at time of creation.
提示:create_date仅在创建记录时填充,因此你将需要一个回退机制,以防止在创建时崩溃。
- Add the fields in the form view and the list view as depicted on the second image of this section’s Goal.
按照本节目标的第二张图片所示,在表单视图和列表视图中添加字段
Additional Information 附加信息
Computed fields are not stored in the database by default. Therefore it is not possible to search on a computed field unless a search
method is defined. This topic is beyond the scope of this training, so we won’t cover it. An example can be found here.
默认情况下,计算字段不会存储在数据库中。因此,除非定义了搜索方法,否则无法对计算字段进行搜索。这个问题超出了本次培训的范围,所以我们不会涉及。你可以在此处找到一个示例。
Another solution is to store the field with the store=True
attribute. While this is usually convenient, pay attention to the potential computation load added to your model. Lets re-use our example:
另一种解决方案是在字段上使用 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
Every time the partner name
is changed, the description
is automatically recomputed for all the records referring to it! This can quickly become prohibitive to recompute when millions of records need recomputation.
每当合作伙伴名称发生变化时,所有引用它的记录都会自动重新计算描述!当需要重新计算数百万条记录时,这很快就会变得难以承受。
It is also worth noting that a computed field can depend on another computed field. The ORM is smart enough to correctly recompute all the dependencies in the right order… but sometimes at the cost of degraded performance.
还值得注意的是,一个计算字段可以依赖于另一个计算字段。ORM足够智能,可以正确地按正确的顺序重新计算所有的依赖关系……但是有时会以性能下降为代价。
In general performance must always be kept in mind when defining computed fields. The more complex is your field to compute (e.g. with a lot of dependencies or when a computed field depends on other computed fields), the more time it will take to compute. Always take some time to evaluate the cost of a computed field beforehand. Most of the time it is only when your code reaches a production server that you realize it slows down a whole process. Not cool 🙁
在定义计算字段时,始终要考虑性能。计算字段越复杂(例如,具有大量依赖项或计算字段依赖于其他计算字段),计算所需的时间就越长。务必提前花时间评估计算字段的成本。大多数时候,只有当你代码达到生产服务器时才会意识到它减慢了整个过程。不太酷 🙁
Onchanges
Reference: the documentation related to this topic can be found in onchange()
:
参考文献:与本主题相关的文档可在 onchange 中找到。
注解
Goal: at the end of this section, enabling the garden will set a default area of 10 and an orientation to North.
目标:在这个部分结束时,启用花园将设置默认面积为 10 并将朝向设为北。
In our real estate module, we also want to help the user with data entry. When the ‘garden’ field is set, we want to give a default value for the garden area as well as the orientation. Additionally, when the ‘garden’ field is unset we want the garden area to reset to zero and the orientation to be removed. In this case, the value of a given field modifies the value of other fields.
在我们的房地产模块中,我们也希望帮助用户输入数据。当设置了“花园”字段时,我们希望为花园面积以及方向提供默认值。此外,当取消设置“花园”字段时,我们希望花园面积重置为零,并移除方向。在这种情况下,某个字段的值修改了其他字段的值。
The ‘onchange’ mechanism provides a way for the client interface to update a form without saving anything to the database whenever the user has filled in a field value. To achieve this, we define a method where self
represents the record in the form view and decorate it with onchange()
to specify which field it is triggered by. Any change you make on self
will be reflected on the form:
“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)
In this example, changing the partner will also change the name and the description values. It is up to the user whether or not to change the name and description values afterwards. Also note that we do not loop on self
, this is because the method is only triggered in a form view, where self
is always a single record.
在这个例子中,更改合作伙伴也会更改名字和描述的值。之后是否要更改名字和描述的值取决于用户。还要注意,我们不需要循环遍历 self
,这是因为该方法只在表单视图中触发,self
总是一条记录。
Exercise
Set values for garden area and orientation. 设置花园面积和方向的值。
Create an onchange
in the estate.property
model in order to set values for the garden area (10) and orientation (North) when garden is set to True. When unset, clear the fields.
在 estate.property
模型中创建一个 onchange
,以便在设置花园为 True
时设置花园面积(10)和方向(北)。取消设置时,清除这些字段。
Additional Information 额外信息
Onchanges methods can also return a non-blocking warning message (example).onchange
方法还可以返回非阻塞警告消息(示例)
How to use them? 如何使用它们?
There is no strict rule for the use of computed fields and onchanges.
对于何时使用计算字段和 onchange
没有严格的规定。
In many cases, both computed fields and onchanges may be used to achieve the same result. Always prefer computed fields since they are also triggered outside of the context of a form view. Never ever use an onchange to add business logic to your model. This is a very bad idea since onchanges are not automatically triggered when creating a record programmatically; they are only triggered in the form view.
在许多情况下,计算字段和 onchange
可能用于实现相同的结果。总是优先选择计算字段,因为它们也在表单视图之外触发。永远不要使用 onchange
在您的模型中添加业务逻辑。这是一个非常糟糕的想法,因为 onchange
不会在程序化创建记录时自动触发;它们只在表单视图中触发。
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.
计算字段和 onchange
的常见陷阱是试图通过添加过多的逻辑来显得“太聪明”。这可能导致相反的结果:最终用户对自动化感到困惑。
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.
计算字段往往更容易调试:这样的字段是由特定的方法设置的,所以很容易跟踪何时设置值。另一方面,onchange
可能令人困惑:很难知道 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.
使用存储的计算字段时,请密切关注依赖性。当计算字段依赖于其他计算字段时,更改值可能会触发大量的重新计算。这会导致性能不佳。
In the next chapter, we’ll see how we can trigger some business logic when buttons are clicked.
使用存储的计算字段时,请密切关注依赖性。当计算字段依赖于其他计算字段时,更改值可能会触发大量的重新计算。这会导致性能不佳。
发表回复