第 7 章:模型之间的关系
上一章介绍了为包含基本字段的模型创建自定义视图。然而,在任何实际业务场景中,我们都需要不止一个模型。此外,模型之间的链接也是必要的。我们可以轻松想象一个模型包含客户,另一个模型包含用户列表。您可能需要在任何现有业务模型上引用客户或用户。
在我们的房地产模块中,我们需要房地产的以下信息:
购买房产的客户
出售房产的房地产经纪人
房产类型:住宅、公寓、顶层公寓、城堡......
描述房产特征的标签列表:舒适、翻新...
收到的报价清单
我们将完成以下功能:
新的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可以看作是表单视图中的下拉列表。
创建 estate.property.type 模型并添加字段:
Field Type Attributes
name Char required
修改我们之前创建的models/estate_property.py
from odoo import models, fields, api
from dateutil.relativedelta import relativedelta
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.Text()
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")
class EstatePropertyType(models.Model):
_name = "estate_property_type"
_description = "Estate Property Type"
name = fields.Char(required=True)
提示:不要忘记在__init__.py中导入任何新的 Python 文件,在__manifest__.py中添加新的数据文件或添加访问权限 ;-)
再次重启服务器并刷新以查看结果!
在房地产模块中,我们仍然缺少关于房产的两项信息:买方partner和销售人员salesperson。买方可以是任何个人,但另一方面,销售人员必须是房地产公司的员工(即Odoo用户)。
在Odoo中,我们有两个模型model我们是经常用到的:
res.partner:合作伙伴是一个物理或法律实体。它可以是公司、个人,甚至是一个联系地址。
res.users:系统用户。用户可以是 "内部 "用户,即他们可以访问Odoo后台。也可以是 "门户 "用户,即他们不能访问后台,只能访问前台(例如,在电子商务中访问他们以前的订单)。
下面给我们的estate_property模型加上:
user_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user)
partner_id = fields.Many2one("res.partner", string="Partner", copy=False)
注意user_id的默认值必须是当前用户。合作伙伴应该不能被复制。
注意
对象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是一种双向多重关系:一方的任何记录都可以与另一方的任意数量的记录相关联。例如,为了在我们的测试模型上定义一个与 account.tax 模型的链接,我们可以这样写:
tax_ids = fields.Many2many("account.tax", string="Taxes")
按照惯例,many2many 字段的后缀是 _ids。这意味着我们可以在测试模型中添加多个税项。它的行为就像一个记录列表,这意味着必须在循环中访问数据:
for tax in my_test_object.tax_ids:
print(tax.name)
记录列表被称为记录集,即记录的有序集合。它支持标准的 Python 集合操作,如len()和 iter(),以及额外的集合操作,如 recs1 | recs2。
添加房地产属性标签表
class EstatePropertyTag(models.Model):
_name = "estate_property_tag"
_description = "Estate Property Tag"
name = fields.Char(required=True)
提示:在视图中,使用 widget="many2many_tags" 属性。widget属性将在后面的培训章节中详细讲解。现在,你可以尝试添加或删除它,看看效果如何;-)
以下是我们现在的estate_property_views.xml的完整代码:
<?xml version="1.0"?>
<odoo>
<record id="estate_property_action" model="ir.actions.act_window">
<field name="name">房产列表</field>
<field name="res_model">estate_property</field>
<field name="view_mode">tree,form</field>
</record>
<record id="estate_property_type_action" model="ir.actions.act_window">
<field name="name">房产类型</field>
<field name="res_model">estate_property_type</field>
<field name="view_mode">tree,form</field>
</record>
<record id="estate_property_tag_action" model="ir.actions.act_window">
<field name="name">房产标签</field>
<field name="res_model">estate_property_tag</field>
<field name="view_mode">tree,form</field>
</record>
<record id="estate_property_view_tree" model="ir.ui.view">
<field name="name">房产列表</field>
<field name="model">estate_property</field>
<field name="arch" type="xml">
<tree string="房产列表">
<field name="name"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="expected_price"/>
<field name="selling_price"/>
<field name="date_available"/>
</tree>
</field>
</record>
<record id="estate_property_view_form" model="ir.ui.view">
<field name="name">房产详情</field>
<field name="model">estate_property</field>
<field name="arch" type="xml">
<form string="详情">
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<field name="tag_ids" widget="many2many_tags"/>
<group name="estate_property_header">
<group name="estate_property_details">
<group col="2">
<field name="property_type_id"/>
<field name="postcode"/>
<field name="expected_price"/>
</group>
<group col="2">
<field name="date_available"/>
<field name="selling_price"/>
</group>
</group>
<notebook>
<page string="Description">
<group name="estate_property_details">
<field name="description"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="facades"/>
<field name="garage"/>
<field name="garden_area"/>
<field name="garden_orientation"/>
<field name="state"/>
</group>
</page>
<page string="Other info">
<group name="estate_property_details">
<field name="user_id"/>
<field name="partner_id"/>
</group>
</page>
</notebook>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_estate_property_search" model="ir.ui.view">
<field name="name">estate_property</field>
<field name="model">estate_property</field>
<field name="arch" type="xml">
<search string="estate_property">
<field name="bedrooms" />
<field name="postcode" />
<field name="living_area" />
<field name="property_type_id" />
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<filter string="New or Offer Accepted" name="new_offer_accepted" domain="[('state', 'in', ['new', 'offer_accepted'])]"/>
<group expand="1" string="Group By">
<filter string="邮编分组" name="postcode" context="{'group_by':'postcode'}"/>
</group>
</search>
</field>
</record>
</odoo>
model模型文件estate_property.py的完整代码如下:
from odoo import models, fields, api
from dateutil.relativedelta import relativedelta
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.Text()
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")
class EstatePropertyType(models.Model):
_name = "estate_property_type"
_description = "Estate Property Type"
name = fields.Char(required=True)
class EstatePropertyTag(models.Model):
_name = "estate_property_tag"
_description = "Estate Property Tag"
name = fields.Char(required=True)
在房地产模块中,我们要定义房产出价的概念。房产出价是潜在买家向卖家提出的金额。出价可以低于或高于预期价格。
一个出价适用于一处房产,但同一房产可以有多个出价。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)
因为One2many是一种虚拟关系,因此comodel中必须定义一个Many2one字段。
根据下面表格添加房地产报价表estate_property_offer:
Field |
Type |
Values |
|
price |
Float |
||
status |
Selection |
no copy |
Accepted, Refused |
partner_id |
Many2one (res.partner) |
required |
|
property_id |
Many2one (estate.property) |
required |
在estate_property模型中添加以下字段:
offer_ids = fields.One2many("estate_property_offer", "property_id", string="Offers")
新增estate_property_offer表:
class EstatePropertyOffer(models.Model):
_name = "estate_property_offer"
_description = "Estate Property Offer"
price = fields.Float()
status = fields.Selection([
("accepted", "Accepted"),
("refused", "Refused"),
],copy=False)
partner_id = fields.Many2one("res.partner")
property_id = fields.Many2one("estate_property")
仍然活着?这一章绝对不是最简单的一章。它引入了几个新概念 同时依赖于之前引入的所有内容。下一章会更轻松,别担心 ;-)
第7章:模型之间的关系