odoo 开发入门教程系列-模型之间的关系(Relations Between Models) - 授客
source link: https://www.cnblogs.com/shouke/p/17253352.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
模型之间的关系(Relations Between Models)
上一章介绍了为包含基本字段的模型创建自定义视图。然而,在任何真实的业务场景中,我们都需要不止一个模型。此外,模型之间的链接是必要的。人们可以很容易地想象一个模型包含客户,另一个模型则包含用户列表。你可能需要参考任何现有业务模型上的客户或用户。
在我们的estate模块中,我们需要有关房产的以下信息:
- 购买房产的客户
- 出售房产的真实重述代理人
- 房产类型:房子、公寓、顶层公寓、城堡…
- 显示了该酒店特色的一系列标签:舒适、翻新…
- 收到的报价清单
Many2one
参考: 本主题相关文档可查阅 Many2one
在我们的房地产模块中,我们想定义房地产类型的概念,例如,房屋或公寓。根据的类型对房地产进行分类是一种标准的业务需求,尤其是为了优化过滤。
一个房产可以有一个类型,但同一类型可以分配给多个房产。这得到了many2one
概念的支持。
many2one
是指向另一个对象的简单链接。例如,为了在我们的测试模型中定义到 res.partner
的连接,我们可以这样写:
partner_id = fields.Many2one("res.partner", string="Partner")
按约定,many2one
字段以_id
后缀。可通过以下方式轻松的访问partner
中的数据:
print(my_test_object.partner_id.name)
添加房地产类型表
- 创建
estate.property.type
模型,并添加以下字段:
Field | Type | Attributes |
---|---|---|
name | Char | required |
-
添加
property_type_id
到estate.property
模型和表单,树,搜索视图
该练习是对前几章很好的扼要重述:你需要创建一个 model,设置 model,添加 动作和菜单,并且创建视图.
提示: 别忘记在 __init__.py
导入新的Python模块文件,并在__manifest.py__
中添加数据或者访问权限。
新增odoo14/custom/estate/models/estate_property_type.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyType(models.Model):
_name = 'estate.property.type'
_description = 'estate property type'
name = fields.Char(string='type', required=True)
修改odoo14/custom/estate/models/__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from . import estate_property
from . import estate_property_type # 新增内容
修改odoo14/custom/estate/security/ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_estate_model,access_estate_model,model_estate_property,base.group_user,1,1,1,1
access_estate_property_type_model,access_estate_property_type_model,model_estate_property_type,base.group_user,1,1,1,1
修改odoo14/custom/estate/views/estate_menus.view
<?xml version="1.0"?>
<odoo>
<menuitem id="test_menu_root" name="Real Estate">
<menuitem id="test_first_level_menu" name="Advertisements">
<menuitem id="estate_property_menu_action" action="link_estate_property_action"/>
</menuitem>
<menuitem id="property_type_first_level_menu" name="Settings">
<menuitem id="property_type_action" action="estate_property_type_action"/>
</menuitem>
</menuitem>
</odoo>
新增odoo14/custom/estate/views/estate_property_type_views.xml
<?xml version="1.0"?>
<odoo>
<record id="estate_property_type_action" model="ir.actions.act_window">
<field name="name">Property Types</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">tree,form</field>
</record>
<record id="estate_property_type_view_tree" model="ir.ui.view">
<field name="name">estate.property.type.tree</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<tree string="PropertyTypes">
<field name="name" string="Title"/>
</tree>
</field>
</record>
</odoo>
修改odoo14/custom/__manifest__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
{
'name': 'estate',
'depends': ['base'],
'data':['security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml', # 新增内容
'views/estate_menus.xml',
]
}
再次重启服务,并刷新查看结果。
新增一条记录
修改odoo14/custom/estate/models/estate_property.py
,添加property_type_id
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from odoo import models,fields
class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'estate property table'
name = fields.Char(size=15, required=True)
description = fields.Text()
postcode = fields.Char(size=15)
date_availability = fields.Datetime('Availability Date', copy=False, default= lambda self: fields.Datetime.today())
expected_price = fields.Float('expected price', digits=(8, 2), required=True) # 最大8位,小数占2位
selling_price = fields.Float('selling price', digits=(8, 2), readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean('garage')
garden = fields.Boolean('garden')
garden_area = fields.Integer()
garden_orientation = fields.Selection(
string='Orientation',
selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('West','West')],
help="garden orientation"
)
active = fields.Boolean('Active', default=True, invisible=True)
state = fields.Selection(
string='State',
selection=[('New','New'),
('Offer Received','Offer Received'),
('Offer Accepted', 'Offer Accepted'),
('Sold','Sold'),
('Canceled', 'Canceled')],
copy=False
)
property_type_id = fields.Many2one("estate.property.type", "PropertyType")
修改odoo14/custom/estate/views/estate_property_views.xml
tree
,form
视图
<?xml version="1.0"?>
<odoo>
<record id="link_estate_property_action" model="ir.actions.act_window">
<field name="name">Properties</field>
<field name="res_model">estate.property</field>
<field name="view_mode">tree,form</field>
</record>
<record id="estate_property_view_tree" model="ir.ui.view">
<field name="name">estate.property.tree</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<tree string="Tests">
<field name="name" string="Title"/>
<field name="postcode" string="Postcode"/>
<field name="bedrooms" string="Bedrooms"/>
<field name="living_area" string="Living Area"/>
<field name="expected_price" string="Expected Price"/>
<field name="selling_price" string="Selling Price"/>
<field name="date_availability" string="Avalilable Form"/>
<field name="property_type_id" string="Property Type"/>
</tree>
</field>
</record>
<record id="estate_property_view_form" model="ir.ui.view">
<field name="name">estate.property.form</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="estate property form">
<sheet>
<h1>
<field name="name"/>
</h1>
<group>
<group>
<field name="property_type_id" string="Property Type"/>
<field name="postcode" string="Postcode" ></field>
<field name="date_availability" string="Available From"></field>
</group>
<group>
<field name="expected_price" string="Expected Price"></field>
<field name="selling_price" string="Selling Price"></field>
</group>
</group>
<notebook>
<page string="Description">
<group>
<field name="description"></field>
<field name="bedrooms"></field>
<field name="living_area"></field>
<field name="facades"></field>
<field name="garage"></field>
<field name="garden"></field>
<field name="garden_area"></field>
<field name="garden_orientation"></field>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="estate_property_search_view" model="ir.ui.view">
<field name="name">estate.property.search</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<search string="Estate Property">
<!-- 搜索 -->
<field name="name" string="Title" />
<field name="postcode" string="Postcode"></field>
<separator/>
<!-- 筛选 -->
<filter string="Available" name="state" domain="['|',('state', '=', 'New'),('state', '=', 'Offer Received')]"></filter>
<filter name="bedrooms" domain="[('bedrooms', '>', 3)]"></filter>
<filter name="bedrooms and selling_price" domain="[('bedrooms', '>', 2),('selling_price', '>=', 1000)]"></filter>
<!-- 分组 -->
<group expand="1" string="Group By">
<filter string="朝向" name="garden_orientation" context="{'group_by':'garden_orientation'}"/>
</group>
</search>
</field>
</record>
</odoo>
重启服务,刷新浏览器验证
在房地产模块中,我们仍然缺失两条关于房产的信息:买家和销售人员。买家可以是任何个人,然而,销售人员必须是房产机构的员工(即odoo用户)。
在odoo中,有两种我们经常引用的两种模型:
res.partner
: 一个partner
为一个物理实体或者法人实体。可以是一个公司,个人,甚至是一个联系地址。res.users
: 系统用户。可以是内部(internal
)用户,也就是说有odoo后端的访问权限,可以是门户(portal
)用户,仅可以访问前端(比如访问他们之前的电子商务订单) ,不可以访问后端。
会用上述提到的两种常用model添加买家和销售人员到estate.property
模块。将它们添加到form视图中新tab页面。
销售人员的默认值必须是当前用户。买家不能被复制。
提示:要获取默认值,请查看下面的注解或查看示例
user_id = fields.Many2one('res.users', string='Salesperson', index=True, tracking=True, default=lambda self: self.env.user)
self.env
对象为其它请求参数和其它有用的东西提供了访问权限:
self.env.cr
或者self._cr
为数据库游标(cursor)对象。用于查询数据库self.env.uid
或者self._uid
当前用户数据库IDself.env.user
当前用户记录self.env.context
或者self._context
上下文字典self.env.ref(xml_id)
返回和XML id对应的记录self.env[model_name]
返回给定模型的实例
修改odoo14\custom\estate\models\estate_property.py
,内容如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from odoo import models,fields
class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'estate property table'
name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char(size=15)
date_availability = fields.Datetime('Availability Date', copy=False, default= lambda self: fields.Datetime.today())
expected_price = fields.Float('expected price', digits=(8, 2), required=True) # 最大8位,小数占2位
selling_price = fields.Float('selling price', digits=(8, 2), readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean('garage')
garden = fields.Boolean('garden')
garden_area = fields.Integer()
garden_orientation = fields.Selection(
string='Orientation',
selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('West','West')],
help="garden orientation"
)
active = fields.Boolean('Active', default=True, invisible=True)
state = fields.Selection(
string='State',
selection=[('New','New'),
('Offer Received','Offer Received'),
('Offer Accepted', 'Offer Accepted'),
('Sold','Sold'),
('Canceled', 'Canceled')],
copy=False
)
property_type_id = fields.Many2one("estate.property.type", string="PropertyType")
# 以下为本次新增内容
salesman_id = fields.Many2one("res.users", string="Salesman")
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False, default=lambda self: self.env.user)
修改odoo14\custom\estate\views\estate_property_views.xml
中notebook
内容为如下:
<notebook>
<page string="Description">
<group>
<field name="description"></field>
<field name="bedrooms"></field>
<field name="living_area"></field>
<field name="facades"></field>
<field name="garage"></field>
<field name="garden"></field>
<field name="garden_area"></field>
<field name="garden_orientation"></field>
</group>
</page>
<page string="Other info">
<group>
<field name="salesman_id" string="Salesman"></field>
<field name="buyer_id" string="Buyer"></field>
</group>
</page>
</notebook>
重启服务,浏览器中验证
Many2many
参考:和本主题关联的文档可参考Many2many
.
在我们的房地产模块中,我们想定义房产标签的概念。例如,房地产是“舒适”或是“翻新”的
一个地产可以有多个标签,一个标记可以分配给多个房产。这得到了many2many
概念的支持。
many2many
是一种双向多重关系:一侧的任何记录都可以与另一侧的任何数量的记录相关。例如,为了在我们的测试模型中定义到 account.tax
的链接,我们可以这样写:
tax_ids = fields.Many2many("account.tax", string="Taxes")
按约定,many2many
字段拥有_ids
后缀。这意味着可以将多个"tax"添加到我们的测试模型。它表现为一个记录列表,意味着必须通过循环访问数据:
for tax in my_test_object.tax_ids:
print(tax.name)
记录列表即为众所周知的recordset
,即记录的有序集合。它支持标准Python的集合操作,如len()
和iter()
,以及recs1 | recs2
等额外的集合操作。
添加房产标签表
- 创建
estate.property.tag
模型和并添加以下字段:
Field | Type | Attributes |
---|---|---|
name | Char | required |
- 添加
tag_ids
到estate.property
模型,表单和列表视图
提示: 视图中,使用 widget="many2many_tags"
属性正如这里展示的一样。
<group string="Website Traffic Conditions">
<field name="country_ids" widget="many2many_tags"/>
<field name="website_id" options="{'no_open': True, 'no_create_edit': True}" groups="website.group_multi_website"/>
...
</group>
添加odoo14\custom\estate\models\estate_property_tag.py
,内容如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'estate property tag'
name = fields.Char(string='tag', required=True)
修改odoo14\custom\estate\models\__init__.py
,内容如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from . import estate_property
from . import estate_property_type
from . import estate_property_tag # 本次新增内容
修改odoo14\custom\estate\security\ir.model.access.csv
,内容如下:
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_estate_model,access_estate_model,model_estate_property,base.group_user,1,1,1,1
access_estate_property_type_model,access_estate_property_type_model,model_estate_property_type,base.group_user,1,1,1,1
access_estate_property_tag_model,access_estate_property_tag_model,model_estate_property_tag,base.group_user,1,1,1,1
修改odoo14/custom/estate/views/estate_menus.view
<?xml version="1.0"?>
<odoo>
<menuitem id="test_menu_root" name="Real Estate">
<menuitem id="test_first_level_menu" name="Advertisements">
<menuitem id="estate_property_menu_action" action="link_estate_property_action"/>
</menuitem>
<menuitem id="settings_menu" name="Settings">
<menuitem id="property_type_action" action="estate_property_type_action"/>
<!-- 本次新增内容 -->
<menuitem id="property_tag_action" action="estate_property_tag_action"/>
</menuitem>
</menuitem>
</odoo>
新增odoo14/custom/estate/views/estate_property_tag_views.xml
<?xml version="1.0"?>
<odoo>
<record id="estate_property_tag_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">tree,form</field>
</record>
<record id="estate_property_tag_view_tree" model="ir.ui.view">
<field name="name">estate.property.tag.tree</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<tree string="PropertyTags">
<field name="name" string="tag"/>
</tree>
</field>
</record>
</odoo>
修改odoo14/custom/estate/__manifest__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
{
'name': 'estate',
'depends': ['base'],
'data':['security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml', # 本次新增内容
'views/estate_menus.xml',
]
}
重启服务,刷新浏览器验证,效果如下
添加2条记录,供下文使用
修改odoo14/custom/estate/models/estate_property.py
,末尾添加property_tag_id
tag_ids = fields.Many2many("estate.property.tag")
修改odoo14\custom\estate\views\estate_property_views.xml
中estate_property_view_form
视图
<record id="estate_property_view_form" model="ir.ui.view">
<field name="name">estate.property.form</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="estate property form">
<sheet>
<h1>
<field name="name"/>
</h1>
<!--<p>元素为本次添加内容-->
<p>
<field name="tag_ids" widget="many2many_tags"/>
</p>
<group>
<group>
<field name="property_type_id" string="Property Type"/>
<field name="postcode" string="Postcode" ></field>
<field name="date_availability" string="Available From"></field>
</group>
<group>
<field name="expected_price" string="Expected Price"></field>
<field name="selling_price" string="Selling Price"></field>
</group>
</group>
<notebook>
<page string="Description">
<group>
<field name="description"></field>
<field name="bedrooms"></field>
<field name="living_area"></field>
<field name="facades"></field>
<field name="garage"></field>
<field name="garden"></field>
<field name="garden_area"></field>
<field name="garden_orientation"></field>
</group>
</page>
<page string="Other info">
<group>
<field name="salesman_id" string="Salesman"></field>
<field name="buyer_id" string="Buyer"></field>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
重启服务,验证效果
One2many
参考:主题关联文档可以参考One2many
在我们的房地产模块中,我们想定义房产报价的概念。房地产报价是潜在买家向卖家提供的金额。报价可能低于或高于预期价格。
报价适用于一个房产,但同一个房产可以有多个报价。many2one
的概念再次出现。然而,在本例中,我们希望显示给定地产的报价列表,因此我们将使用one2many
概念。
one2many
是many2one
的反向实现。例如,我们通过partner_id
字段,在测试模型上定义了到res.partner
模型的链接。我们可以定义反向关系,即与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
,必须在comodel
中定义Many2one
字段
添加房地产报价表
- 创建
estate.property.offer
模型,并添加以下字段:
Field | Type | Attributes | Values |
---|---|---|---|
price | Float | ||
status | Selection | no copy | 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
添加odoo14\custom\estate\models\estate_property_offer.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from odoo import models, fields
class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'estate property offer'
price = fields.Float(string='Price')
status = fields.Selection(string='Status',
selection=[('Accepted', 'Accepted'), ('Refused', 'Refused')],
copy=False
)
partner_id = fields.Many2one('res.partner', required=True)
property_id = fields.Many2one('estate.property', required=True)
添加odoo14\custom\estate\views\estate_property_offer_views.xml
<?xml version="1.0"?>
<odoo>
<record id="estate_property_offer_view_tree" model="ir.ui.view">
<field name="name">estate.property.offer.tree</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<tree string="PropertyOffers">
<field name="price" string="Price"/>
<field name="partner_id" string="partner ID"/>
<field name="validity" string="Validity(days)"/>
<field name="deadline" string="Deadline"/>
<field name="status" string="Status"/>
</tree>
</field>
</record>
<record id="estate_property_offer_view_form" model="ir.ui.view">
<field name="name">estate.property.offer.form</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<form string="estate property offer form">
<sheet>
<group>
<field name="price" string="Price"/>
<field name="validity" string="Validity(days)"/>
<field name="deadline" string="Deadline"/>
<field name="partner_id" string="partner ID"/>
<field name="status" string="Status"/>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
修改odoo14\custom\estate\__manifest__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
{
'name': 'estate',
'depends': ['base'],
'data':['security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_offer_views.xml', # 本次新增内容
'views/estate_menus.xml',
]
}
修改odoo14\custom\estate\models\__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
from . import estate_property
修改odoo14\custom\estate\security\ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_estate_model,access_estate_model,model_estate_property,base.group_user,1,1,1,1
access_estate_property_type_model,access_estate_property_type_model,model_estate_property_type,base.group_user,1,1,1,1
access_estate_property_tag_model,access_estate_property_tag_model,model_estate_property_tag,base.group_user,1,1,1,1
access_estate_property_offer_model,access_estate_property_offer_model,model_estate_property_offer,base.group_user,1,1,1,1
修改odoo14\custom\estate\models\estate_property.py
,最末尾添加offer_ids
字段,如下
offer_ids = fields.One2many("estate.property.offer", "property_id", string="PropertyOffer")
修改odoo14\custom\estate\views\estate_property_views.xml
中estate_property_view_form
表单视图
<record id="estate_property_view_form" model="ir.ui.view">
<field name="name">estate.property.form</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="estate property form">
<sheet>
<h1>
<field name="name"/>
</h1>
<p>
<field name="tag_ids" widget="many2many_tags"/>
</p>
<group>
<group>
<field name="property_type_id" string="Property Type"/>
<field name="postcode" string="Postcode" ></field>
<field name="date_availability" string="Available From"></field>
</group>
<group>
<field name="expected_price" string="Expected Price"></field>
<field name="selling_price" string="Selling Price"></field>
</group>
</group>
<notebook>
<page string="Description">
<group>
<field name="description"></field>
<field name="bedrooms"></field>
<field name="living_area"></field>
<field name="facades"></field>
<field name="garage"></field>
<field name="garden"></field>
<field name="garden_area"></field>
<field name="garden_orientation"></field>
</group>
</page>
<page>
<field name="offer_ids" />
</page>
<page string="Other info">
<group>
<field name="salesman_id" string="Salesman"></field>
<field name="buyer_id" string="Buyer"></field>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
重启服务,浏览器中验证
点击"Add a line" 添加记录
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK