第8章:计算域和onchanges

第 8 章:Computed Fields And Onchanges

模型之间的关系是任何 Odoo 模块的关键组成部分。任何业务案例的建模都离不开它们。然而,我们可能希望在给定模型内的字段之间建立联系。有时,一个字段的值由其他字段的值决定,有时,我们希望帮助用户输入数据。

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

 

在房地产模块中,我们定义了居住面积和花园面积。因此,将总面积定义为这两个字段的总和是很自然的。为此,我们将使用计算字段的概念,即某个字段的值将根据其他字段的值计算得出。

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

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

按照惯例,计算方法是私有的,这意味着它们不能从表现层调用,只能从业务层调用(参见第1章:架构概述)。私有方法的名称以下划线_开头。

 

计算字段的值通常取决于计算记录中其他字段的值。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 集合操作,如len(self) 和 iter(self),以及额外的集合操作,如recs1 | recs2。

对self进行迭代会逐条得到记录,其中每条记录本身就是一个大小为1的集合。您可以使用点符号访问/分配单条记录上的字段,例如record.name。

 

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

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

 

计算总面积

在estate.property中添加total_area字段。它的定义是居住面积和花园面积之和。

计算最佳报价。

在estate.property中添加best_price字段。它被定义为报价中的最高价(即最大值)。

修改estate_perperty模型为如下代码:

class EstateProperty(models.Model):

    _name = "estate_property"

    _description = "Estate Property"

 

    def _get_default_date_available(self):

        # 使用 fields.Date.today() 获取当前日期,并添加三个月

        return fields.Date.today() + relativedelta(months=+3)

    name = fields.Char(required=True, default="Unknown")

    description = fields.Char(compute="_compute_description", store=True)

    postcode = fields.Char()

    date_available = fields.Date(copy=False, default=_get_default_date_available)

    expected_price = fields.Float(required=True)

    selling_price = fields.Float(readonly=True, copy=False)

    bedrooms = fields.Integer()

    living_area = fields.Integer()

    facades = fields.Integer()

    garage = fields.Boolean()

    garden = fields.Boolean()

    garden_area = fields.Integer()

    garden_orientation = fields.Selection([

        ("north", "North"),

        ("south", "South"),

        ("east", "East"),

        ("west", "West"),

    ])

    active = fields.Boolean(default=True)

    state = fields.Selection([

        ("new", "New"),

        ("offer_received", "Offer Received"),

        ("offer_accepted", "Offer Accepted"),

        ("sold", "Sold"),

        ("canceled", "Canceled"),

    ], default="new", copy=False)

    property_type_id = fields.Many2one("estate_property_type", string="Property Type")

    user_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user)

    partner_id = fields.Many2one("res.partner", string="Partner", copy=False)

    tag_ids = fields.Many2many("estate_property_tag", string="Tags")

    offer_ids = fields.One2many("estate_property_offer", "property_id", string="Offers")

    total_area = fields.Integer(compute="_compute_total_area")

    best_price = fields.Float(compute="_compute_best_price", string="Best Price")

 

    @api.depends("offer_ids")

    def _compute_best_price(self):

        for record in self:

            record.best_price = max(record.offer_ids.mapped("price"))

 

    @api.depends("living_area", "garden_area")

    def _compute_total_area(self):

        for record in self:

            record.total_area = record.living_area + record.garden_area

 

    @api.depends("partner_id.name")

    def _compute_description(self):

        for record in self:

            record.description = "Test for partner %s" % record.partner_id.name

 

您可能已经注意到,计算字段默认为只读。这是意料之中的,因为用户不应该设置值。

在某些情况下,直接设置值可能还是有用的。在我们的房地产示例中,我们可以定义要约的有效期并设置有效日期。我们希望能够设置有效期或有效日期,其中一个会影响另一个。

为了支持这一点,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

计算方法设置字段,而反函数则设置字段的依赖。

请注意,逆方法在保存记录时被调用,而计算方法则在每次改变其依赖关系时被调用。

 

计算报价的有效日期


Field Type Default

validity

Integer

7

date_deadline

Date

其中date_deadline是一个计算字段,定义为报价中两个字段的总和:创建日期和有效期。定义一个适当的反函数,以便用户可以设置日期或有效期。

 

计算字段默认不存储在数据库中。因此,除非定义了搜索方法,否则无法对计算字段进行搜索。这一主题超出了本培训的范围,因此我们将不作介绍。

另一种解决方案是使用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

每次更改合作伙伴名称时,都会自动重新计算提及该合作伙伴的所有记录的描述!当数百万条记录需要重新计算时,重新计算的费用很快就会过高。

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

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

 

在我们的房地产模块中,我们还希望帮助用户输入数据。当设置 "花园 "字段时,我们希望给出花园面积和方向的默认值。此外,当 "花园 "字段未设置时,我们希望将花园面积重置为零,并删除朝向。在这种情况下,给定字段的值会修改其他字段的值。

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

 

设置花园面积和方向的值。

在estate.property模型中创建一个 onchange,以便在花园设置为 True 时设置花园面积 (10) 和朝向 (North) 的值。当未设置时,清除字段。

@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 = ""

 

对于计算字段和 onchanges 的使用没有严格的规定。

在很多情况下,使用计算字段和 onchanges 可以达到相同的效果。我们总是倾向于使用计算字段,因为它们也是在表单视图的上下文之外触发的。切勿使用 onchange 向模型添加业务逻辑。这是一个非常糟糕的想法,因为在以编程方式创建记录时,不会自动触发 onchange;它们只会在表单视图中触发。

使用存储的计算字段时,请密切注意其依赖关系。当计算字段依赖于其他计算字段时,更改一个值可能会触发大量的重新计算。这会导致性能低下。

 

 

 

第8章:计算域和onchanges
谢潮聪 2024年12月23日
分析这篇文章

存档
登录 留下评论
第7章:模型之间的关系