第 7 章:模型之间的关系

上一章 介绍了为包含基本字段的模型创建自定义视图。然而,在任何真实的业务场景中,我们需要的都不止一个模型。此外,模型之间的关联也是必不可少的。可以很容易地想象一个模型存放客户、另一个模型存放用户列表的情况。您可能需要在任何现有业务模型上引用某个客户或某个用户。

在房产模块中,我们希望房产包含以下信息:

  • 购买该房产的客户

  • 出售该房产的房地产经纪人

  • 房产类型:独栋房屋、公寓、顶层复式、城堡……

  • 描述房产特征的标签列表:温馨、已翻新……

  • 收到的报价列表

Many2one

参考:与本主题相关的文档可参阅 Many2one。

Note

目标:在本节结束时:

  • 应创建一个新的 estate.property.type 模型,并配上相应的菜单、动作和视图。

房产类型
  • 应向 estate.property 模型添加三个 Many2one 字段:房产类型、买家和销售人员。

房产

在房产模块中,我们希望定义房产类型的概念。例如,房屋或公寓就是房产类型。按类型对房产进行分类是标准的业务需求,尤其有助于细化筛选。

一个房产可以有 一 个类型,但同一个类型可以分配给 多 个房产。这正是 many2one 概念所支持的。

many2one 是指向另一个对象的简单关联。例如,要在我们的测试模型中定义一个指向 res.partner 的关联,可以这样写::

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

按照惯例,many2one 字段带有 _id 后缀。之后就可以通过以下方式轻松访问合作伙伴中的数据::

print(my_test_object.partner_id.name)

更多内容

外键

在实践中,many2one 可以看作表单视图中的下拉列表。

Exercise

添加房产类型(Real Estate Property Type)表。

  • 创建 estate.property.type 模型并添加以下字段:

字段

类型

属性

name

Char

required

  • 按照本节 目标 中所示添加菜单

  • 将字段 property_type_id 添加到您的 estate.property 模型及其表单、列表和搜索视图中

这个练习是对前面各章的一个很好的回顾:您需要创建一个 模型,设置 模型,添加 动作和菜单,并 创建一个视图。

提示:不要忘记在 __init__.py 中导入任何新的 Python 文件,在 __manifest.py__ 中添加新的数据文件,也不要忘记添加访问权限 ;-)

再次重启服务器并刷新页面,看看结果吧!

在房产模块中,房产还缺少两项信息:买家和销售人员。买家可以是任何个人,而销售人员则必须是房产中介机构的员工(即 Odoo 用户)。

在 Odoo 中,我们通常会引用两个模型:

  • res.partner:合作伙伴是自然人或法人。可以是公司、个人,甚至是联系地址。

  • res.users:系统的用户。用户可以是“内部用户”,即可以访问 Odoo 后端;也可以是“门户用户”,即无法访问后端,只能访问前端(例如在电子商务中查看自己以往的订单)。

Exercise

添加买家和销售人员。

使用上述两个常用模型,向 estate.property 模型添加买家和销售人员。它们应添加在表单视图的一个新选项卡中,如本节 目标 所示。

销售人员的默认值必须是当前用户。买家不应被复制。

提示:要获取默认值,请查看下方的注释,或参考 此处 的示例。

Note

对象 self.env 提供对请求参数以及其他有用内容的访问:

  • self.env.cr 或 self._cr 是数据库 游标 对象;它用于查询数据库

  • self.env.uid 或 self._uid 是当前用户的数据库 id

  • self.env.user 是当前用户的记录

  • self.env.context 或 self._context 是上下文字典

  • self.env.ref(xml_id) 返回与 XML id 对应的记录

  • self.env[model_name] 返回给定模型的实例

现在让我们看看其他类型的关联。

Many2many

参考:与本主题相关的文档可参阅 Many2many。

Note

目标:在本节结束时:

  • 应创建一个新的 estate.property.tag 模型,并配上相应的菜单和动作。

房产标签
  • 应向 estate.property 模型添加标签:

房产

在房产模块中,我们希望定义房产标签的概念。例如,房产标签可以是“温馨”或“已翻新”。

一个房产可以有 多 个标签,一个标签也可以分配给 多 个房产。这正是 many2many 概念所支持的。

many2many 是一种双向的多重关联:任一侧的任意记录都可以与另一侧任意数量的记录相关联。例如,要在我们的测试模型上定义一个指向 account.tax 模型的关联,可以这样写::

tax_ids = fields.Many2many("account.tax", string="Taxes")

按照惯例,many2many 字段带有 _ids 后缀。这意味着可以为我们的测试模型添加多笔税费。它的行为如同一个记录列表,也就是说必须通过循环来访问数据::

for tax in my_test_object.tax_ids:
    print(tax.name)

记录列表称为 记录集 (recordset),即记录的有序集合。它支持集合上的标准 Python 操作,例如 len() 和 iter(),还支持额外的集合运算,如 recs1 | recs2。

Exercise

添加房产标签(Real Estate Property Tag)表。

  • 创建 estate.property.tag 模型并添加以下字段:

字段

类型

属性

name

Char

required

  • 按照本节 目标 中所示添加菜单

  • 将字段 tag_ids 添加到您的 estate.property 模型及其表单和列表视图中

提示:在视图中使用 widget="many2many_tags" 属性,如 此处 所示。widget 属性将在 培训的后续章节 中详细讲解。现在,您可以尝试添加和移除它,看看效果 ;-)

One2many

参考:与本主题相关的文档可参阅 One2many。

Note

目标:在本节结束时:

  • 应创建一个新的 estate.property.offer 模型,并配上相应的表单和列表视图。

  • 应向 estate.property 模型添加报价:

房产报价

在房产模块中,我们希望定义房产报价的概念。房产报价是潜在买家向卖家报出的金额。报价可以低于或高于预期价格。

一份报价适用于 一 个房产,但同一个房产可以有 多 份报价。这又一次体现了 many2one 概念。不过在本例中,我们希望显示给定房产的报价列表,因此将使用 one2many 概念。

one2many 是 many2one 的反向关联。例如,我们在测试模型上通过字段 partner_id 定义了指向 res.partner 模型的关联。我们可以定义其反向关联,即链接到该合作伙伴的测试模型列表::

test_ids = fields.One2many("test_model", "partner_id", string="Tests")

第一个参数称为 comodel,第二个参数则是要取其反向关联的字段。

按照惯例,one2many 字段带有 _ids 后缀。它们的行为如同一个记录列表,也就是说必须通过循环来访问数据::

for test in partner.test_ids:
    print(test.name)

Danger

由于 One2many 是一种虚拟关联,comodel 中 必须 定义一个 Many2one 字段。

Exercise

添加房产报价(Real Estate Property Offer)表。

  • 创建 estate.property.offer 模型并添加以下字段:

字段

类型

属性

值

price

Float

status

选择

不可复制

Accepted, Refused

partner_id

Many2one (res.partner)

required

property_id

Many2one (estate.property)

required

  • 创建一个列表视图和一个包含 price、partner_id 和 status 字段的表单视图。无需创建动作或菜单。

  • 将字段 offer_ids 添加到您的 estate.property 模型及其表单视图中,如本节 目标 所示。

这里有几个需要注意的重点。首先,并非所有模型都需要动作或菜单。有些模型旨在仅通过另一个模型访问。我们的练习正是这种情况:报价总是通过房产来访问。

其次,尽管 property_id 字段是必填的,我们并没有把它放进视图中。那么 Odoo 是如何知道我们的报价关联到哪个房产的呢?这正是 Odoo 框架的神奇之处:有些东西是隐式定义的。当我们通过 one2many 字段创建记录时,为方便起见,对应的 many2one 会被自动填充。

还活着吗?这一章确实不是最容易的一章。它在依赖此前所学全部内容的同时,引入了几个新概念。下一章 会轻松一些,别担心 ;-)