模型之間的關係(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)
參見
練習1
新增房地產型別表
- 建立
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
)使用者,僅可以訪問前端(比如訪問他們之前的電子商務訂單) ,不可以訪問後端。
練習2
會用上述提到的兩種常用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" 新增記錄