Appearance
Odoo 权限开发实战
在Odoo中,权限系统(Access control)是一个多层防御体系,从“能否登录系统”到“能否看见某个字段”,共有5个控制层级。关联篇 Odoo 权限管理操作实战指南。
一、权限机制概述
Odoo 的权限控制是多层次的,不是单一开关,而是从粗到细的漏斗模型:
- 系统层: 用户是否激活,用户类型(内部用户、门户、公开)、是否为管理员。
- 组层: 用户属于哪些权限组、组与组之间的继承关系。
- 模型层(ACL): 控制用户对整个模型的读、写、创建、删除(CRUD)权限。
- 记录层(Record Rules): 控制用户可以访问模型中的哪些记录(
domain过滤)。 - 字段/视图层: 字段是否可见/可编辑、按钮显示、菜单显示。
关键原则:必须逐层通过才能访问资源。如果用户在模型层没有读取权限,即使记录规则允许,也无法访问。
二、权限组
权限组是 Odoo 权限的控制基础,所有权限都是通过权限组来进行分配。存储在res.groups 模型中。每个组都是 res.groups 模型中的一条数据。
2.1 工作原理
- 每个权限组都是
res.groups模型的一条记录。 - 每个用户可以同时属于多个权限组,权限是累加的。
- 权限组可以继承其他权限组的权限。
- 权限组可以按类别进行分组。
2.2 实现方式
权限组通常在模块的XML中定义:
xml
<record id="sale_performance_group_manager" model="res.groups">
<field name="name">销售绩效管理员</field>
<field name="category_id" ref="sale_performance.module_category_sale_performance"/>
<field name="implied_ids" eval="[Command.link(ref('sale_performance_group_user'))]"></field>
<field name="users" eval="[Command.link(ref('base.group_user'))]">
</record>2.3 属性说明
name: 权限组名称。implied_ids: 组继承(如果组B出现在组A的implied_ids中,意味着“拥有组A权限的用户,自动拥有组B的权限”)。category_id: 权限组所属类别。users: 默认分配至该权限组的用户。
2.4 权限组所属类别
权限组所属类别(Category,技术字段category_id)指的是用于权限组织和分组权限组的分类标签。它决定了该权限组在用户界面的分组位置,通常对应一个应用模块。
xml
<!-- 销售绩效模块分类 -->
<record id="module_category_sale_performance" model="ir.module.category">
<field name="name">Performance</field>
<field name="parent_id" ref="base.module_category_sales" />
<field name="sequence">10</field>
</record>2.5 案例
xml
`security/sale_performance_groups.xml`
<!-- 销售绩效模块分类 -->
<record id="module_category_sale_performance" model="ir.module.category">
<field name="name">Performance</field>
<field name="parent_id" ref="base.module_category_sales" />
<field name="sequence">10</field>
</record>
<!-- 销售绩效管理员组 -->
<record id="sale_performance_group_manager" model="res.groups">
<field name="name">销售绩效管理员</field>
<field name="category_id" ref="sale_performance.module_category_sale_performance"/>
<field name="implied_ids" eval="[Command.link(ref('sale_performance_group_user'))]"></field>
</record>
<!-- 销售绩效用户组 -->
<record id="sale_performance_group_user" model="res.groups">
<field name="name">销售绩效用户</field>
<field name="category_id" ref="sale_performance.module_category_sale_performance" />
</record>该案例中定义两个权限组,“管理员”和“普通用户”,“管理员”继承了“普通用户”的权限,“普通用户”继承了“内部用户”的权限,形成了权限层级结构。
这里有个展示方式需要注意一下,如果两个权限组没有层级关系即没有使用implied_ids字段关联,它的展示形式为选项框模式;如果有层级关系,则为下拉选框模式。 

三、访问控制列表(ACL)
ACL(Access Control List)是模型级别的权限控制,定义组对模型的读、写、创建、删除的权限。
3.1 工作原理
- ACL定义了特定权限组对特定模型的权限。
- 每个ACL规则都是
ir.model.access模型的一条记录。 - 权限是累加的,如果用户属于多个权限组,则拥所属权限组的所有权限。
- 如果没有明确授予权限,则默认没有权限。
- 如果不设置组,默认任何用户都有。
3.2 ACL的四个权限维度
perm_read: 能否读取记录(search、read)。perm_write: 能否修改记录 (write)。perm_create: 能否创建记录(create)。perm_unlink: 能否删除记录(unlink)。
3.3 实现方式
- csv方式
csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sale_performance_target_user,sale.performace.target.user,module_sale_performance_target,sale_performance.sale_performance_group_user,1,0,0,0
access_sale_performance_target_manager,sale.performance.target.manager,module_sale_performance_target,sale_performance.sale_performance_group_manager,1,1,1,1- xml方式 (适合动态创建)
xml
<record id="access_sale_performance_target_user" model="ir.model.access">
<field name="name">sale.performance.target.user</field>
<field name="group_id" ref="sale_performance_group_user" />
<field name="perm_read" eval="1" />
<field name="perm_write" eval="0" />
<field name="perm_create" eval="0" />
<field name="perm_unlink" eval="0" />
</record>
<record id="access_sale_performance_target_manager" model="ir.model.access">
<field name="name">sale.performance.target.manager</field>
<field name="group_id" ref="sale_performance_group_manager" />
<field name="perm_read" eval="1" />
<field name="perm_write" eval="1" />
<field name="perm_create" eval="1" />
<field name="perm_unlink" eval="1" />
</record>3.4 字段说明
id:ACL 规则唯一标识符。name:ACL 规则名称。model_id:id:目标模型的外部ID。group_id:id: 权限组的外部ID。
perm_read: 是否有读取权限(0 或 1)。perm_write: 是否有修改权限(0 或 1)。perm_create: 是否有创建权限(0 或 1)。perm_unlink: 是否有删除权限(0 或 1)。
3.5 案例
该案例中定义了销售绩效用户对销售绩效目标(sale_performance_target)、销售绩效阶梯规则(sale-performance_taregt_ladder)、销售绩效成员提成(sale_performance_target_commission_line)只读;销售绩效管理员则能(CRUD)。 

体现:管理员能操作而普通用户只能查看。 

四、记录规则(Record Rules)
记录规则解决的是“能看哪些记录”的问题,即使你拥有模型的读权限。记录规则会进一步过滤可见的数据范围。
4.1 工作原理
- 记录规则定义了特定权限组可以访问模型中的哪些记录。
- 每个记录都是
ir.rule模型的一条记录。 - 记录规则使用域表达式(
domain)来过滤记录。 - 如果用户属于多个权限组,则可以访问满足任一记录规则记录。
- 记录规则可以针对读、改、创建、删除操作分别设置。
4.2 记录规则间的逻辑关系
- 组特定规则(Groups Rules)之间的关系 如果你属于多个用户组(比如你既是“销售员”又是“库存专员”),每个组都对同一个模型定义了规则:
- 逻辑关系:OR (并集)。
- 含义:只要你满足其中任一一组的规则,你就能看到这条数据。
- 全局规则(Global Rules)之间的关系 全局规则是指那些没有绑定任何“用户组”的规则(或者在 XML 中 global="True")。
- 逻辑关系:AND(交集)。
- 含义: 你必须同时满足所有全局规则,才能看到数据。全局规则就像是漏斗,每一层都会过滤掉一部分数据。
- 例子:
- 全局规则1:只能查看“已激活”的记录。
- 全局规则2:只能查看“本公司”的记录。
- 结果:只能查看“已激活”且“属于本公司”的记录。
- 全局规则与组规则之间的关系 这是最关键的一层,也是很多开发者权限配置出错的地方。
- 逻辑关系:AND(交集)。
- 公式:(全局规则1 AND 全局规则2) AND (组规则1 OR 组规则2 OR 组规则3)。
- 核心逻辑:
- 系统先计算所有组规则的并集(把你所有组的权限都加起来)。
- 然后使用全局规则对这个并集进行“收缩”过滤。
- 即使你的组规则让你能看全表,只要有一条全局规则禁用了某行,你就绝对看不见。
规则总结表
| 规则类型组合 | 逻辑关系 | 说明 |
|---|---|---|
| 同模型下的多个组规则 | OR (或) | 权限累加,越加越多 |
| 同模型下的多个全局规则 | AND (且) | 权限累加,越加越多 |
| 全局规则与组规则 | AND (且) | 全局规则是最高法律,组规则是地方法规 |
4.3 实现方式
记录规则通常在XML中定义:
xml
<!-- =========================================== -->
<!-- 全局多公司规则(Global Rules) -->
<!-- 适用于所有用户,通过 AND 逻辑与其他规则叠加 -->
<!-- =========================================== -->
<!-- 销售绩效目标:多公司隔离 -->
<record id="sale_performance_target_rule_multi_company" model="ir.rule">
<field name="name">销售绩效目标:多公司隔离</field>
<field name="model_id" ref="model_sale_performance_target"/>
<field name="domain_force">[('company_id', 'in', user.company_ids.ids)]</field>
<field name="global" eval="True"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<!-- =========================================== -->
<!-- 记录规则(Record Rules) -->
<!-- 通过 OR 逻辑,用户满足任一规则即可访问 -->
<!-- =========================================== -->
<!-- 销售员:查看自己的目标 -->
<record id="sale_performance_rule_user" model="ir.rule">
<field name="name">绩效目标:用户只能查看自己的绩效目标</field>
<field name="model_id" ref="model_sale_performance_target" />
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[Command.link(ref('sale_performance_group_user'))]" />
<field name="perm_read" eval="1" />
<field name="perm_write" eval="0" />
<field name="perm_create" eval="0" />
<field name="perm_unlink" eval="0" />
</recrod>4.4 字段说明
name: 记录规则的名称。model_id: 目标模型的引用。domain_force: 域表达式,用于过滤记录。global:是否为全局规则。groups: 适用的权限组。perm_read: 是否适用于读取操作。perm_write: 是否适用于修改操作。perm_create: 是否适用于创建操作。perm_unlink: 是否适用于删除操作。
4.5 案例
xml
<!-- =========================================== -->
<!-- 全局多公司规则(Global Rules) -->
<!-- 适用于所有用户,通过 AND 逻辑与其他规则叠加 -->
<!-- =========================================== -->
<!-- 销售绩效目标:多公司隔离 -->
<record id="rule_sale_performance_target_multi_company" model="ir.rule">
<field name="name">销售绩效目标:多公司隔离</field>
<field name="model_id" ref="model_sale_performance_target"/>
<field name="domain_force">[('company_id', 'in', user.company_ids.ids)]</field>
<field name="global" eval="True"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<!-- 阶梯规则:多公司隔离(通过 target_id 关联) -->
<record id="rule_sale_performance_target_ladder_multi_company" model="ir.rule">
<field name="name">阶梯规则:多公司隔离</field>
<field name="model_id" ref="model_sale_performance_target_ladder"/>
<field name="domain_force">[('target_id.company_id', 'in', user.company_ids.ids)]</field>
<field name="global" eval="True"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<!-- 提成明细:多公司隔离 -->
<record id="rule_sale_performance_target_commission_line_multi_company" model="ir.rule">
<field name="name">提成明细:多公司隔离</field>
<field name="model_id" ref="model_sale_performance_target_commission_line"/>
<field name="domain_force">[('target_id.company_id', 'in', user.company_ids.ids)]</field>
<field name="global" eval="True"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<!-- =========================================== -->
<!-- 记录规则(Record Rules) -->
<!-- 通过 OR 逻辑,用户满足任一规则即可访问 -->
<!-- =========================================== -->
<!-- 用户组:查看个人目标(target_type=user) -->
<record id="rule_sale_performance_target_own_user" model="ir.rule">
<field name="name">用户组:查看个人目标</field>
<field name="model_id" ref="model_sale_performance_target"/>
<field name="domain_force">[
('target_type', '=', 'user'),
('user_id', '=', user.id)
]</field>
<field name="groups" eval="[(4, ref('sale_performance_group_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<!-- 用户组:查看团队目标(自己是团队成员) -->
<record id="rule_sale_performance_target_own_team" model="ir.rule">
<field name="name">用户组:查看团队目标</field>
<field name="model_id" ref="model_sale_performance_target"/>
<field name="domain_force">[
('target_type', '=', 'team'),
('team_id.member_ids', 'in', [user.id])
]</field>
<field name="groups" eval="[(4, ref('sale_performance_group_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<!-- 用户组:查看个人目标下的提成明细或则团队目标下的提成明细 -->
<record id="rule_sale_performance_commission_line_user_own" model="ir.rule">
<field name="name">用户组:查看关联目标所有人的提成</field>
<field name="model_id" ref="model_sale_performance_target_commission_line"/>
<field name="domain_force">[
'|',
'&',
('target_id.target_type', '=', 'user'),
('target_id.user_id', '=', user.id),
'&',
('target_id.target_type', '=', 'team'),
('target_id.team_id.member_ids', 'in', [user.id])
]</field>
<field name="groups" eval="[(4, ref('sale_performance.sale_performance_group_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<!-- 用户组:查看关联的阶梯规则(只读) -->
<!-- 逻辑:阶梯所属的目标是我个人的,或我所在团队的 -->
<record id="rule_sale_performance_ladder_user_read" model="ir.rule">
<field name="name">用户组:查看相关阶梯规则</field>
<field name="model_id" ref="model_sale_performance_target_ladder"/>
<field name="domain_force">[
'|',
'&',
('target_id.target_type', '=', 'user'),
('target_id.user_id', '=', user.id),
'&',
('target_id.target_type', '=', 'team'),
('target_id.team_id.member_ids', 'in', [user.id])
]</field>
<field name="groups" eval="[(4, ref('sale_performance.sale_performance_group_user'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
</record>
<!-- 管理员组:完全控制销售绩效目标 -->
<record id="rule_sale_performance_target_manager_full" model="ir.rule">
<field name="name">管理员组:管理所有绩效目标</field>
<field name="model_id" ref="model_sale_performance_target"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('sale_performance.sale_performance_group_manager'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<!-- 管理员组:完全控制提成明细 -->
<record id="rule_sale_performance_commission_line_manager_full" model="ir.rule">
<field name="name">管理员组:管理所有提成明细</field>
<field name="model_id" ref="model_sale_performance_target_commission_line"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('sale_performance.sale_performance_group_manager'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>
<!-- 管理员组:完全控制阶梯规则 -->
<record id="rule_sale_performance_ladder_manager_full" model="ir.rule">
<field name="name">管理员组:管理所有阶梯规则</field>
<field name="model_id" ref="model_sale_performance_target_ladder"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('sale_performance.sale_performance_group_manager'))]"/>
<field name="perm_read" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_create" eval="1"/>
<field name="perm_unlink" eval="1"/>
</record>引入:
python
{
'data': [
'security/sale_performance_groups.xml', # groups 定义
'security/sale_performance_rules.xml', # 记录规则
'security/ir.model.access.csv', # 基础访问权限
# ...
],
}数据呈现: 

五、字段级权限控制
用于控制用户可以查看、编辑模型中的哪些字段。
工作原理
- 通过字段
groups属性来控制。 - 属于特定权限组的用户才能查看或编辑字段。
- 可以在模型中设置,也可在视图中设置。
| 纬度 | 模型字段 | 视图字段 |
|---|---|---|
| 控制层面 | ORM/数据库 | UI/前端 |
| 安全强度 | 硬性条件 | 视觉 |
| 数据是否传输 | 不查询、不传输 | 传输,不显示 |
| 影响范围 | 全局 | 当前视图 |
实现方式
- 模型中设置py
class SalePerformanceTarget(models.Model): . # 尽管理人员可见 . private_field = fields.Float('敏感字段', groups="sale_performance.sale_performance_group_manager") - 视图中设置xml
<field name="private_field" groups="sale_performance.sale_performance_group_manager">
六、按钮级别权限控制
用于控制用户可以使用界面上的哪些功能按钮。
工作原理
- 通过
groups属性控制。 - 只有制定权限组用户可以看见及操作。
- 在视图中设置。
实现方式
xml
<button name="action_button" type="object" groups="sale_performance.sale_perfformance_group_manager"></button>
### 案例
```xml
<button name="action_confirm"
type="object"
string="确认"
class="btn btn-primary"
groups="sale_performance.sale_performance_group_manager"
invisible="state != 'draft'" />数据呈现: 

七、菜单级权限控制
用于控制用户可以看到哪些菜单项。
工作原理
- 通过菜单的
groups属性控制。 - 只有特定权限组的用户才能看到该菜单。
- 在菜单中定义。
实现方式
xml
<menuitem id="sale_peformance_menu_result" name="销售绩效原子报表"
groups="sale_performance.sale_performance_group_manager"></menuitem>案例
xml
<menuitem
id="sale_performance_menu_dashboard"
name="销售绩效统计图表"
action="action_sale_performance_result_dashboard"
groups="sale_performance.sale_performance_group_manager"
sequence="20" />数据呈现: 

八、特殊权限
超级用户
uid=1 的系统管理员或通过sudo调用的环境。
- 跳过记录规则(Record Rules)。
- 跳过访问权限检查(ACL)。
- 不跳过字段级
groups限制。
门户用户(Portal)和公开用户(Public)
这两个组权限极低,通常只能读取特定共享记录。
- Portal(
base.group_portal): 外部客户,访问特定门户页面。 - Public(
base.group_public): 未登录用户,访问网站前台。
九、权限调试排错
检查为什么看不见这条记录
- 检查用户是否有所属组。
- 检查ACL(模型权限)。
- 检查记录规则。
- 检查字段权限。
代码中临时打印
python
record = self.env['sale.performane.target'].browse(1)
record.check_access_rights('write') # 有权限返回None,无权限抛异常
record.check_access_rule('write') # 检查记录规则十、设计原则
- 最小权限原则:默认不给权限,按需授予。
- 使用组继承:避免重复定义,通过
implied_ids建立层级。 - 记录规则防跨数据:特别是多公司场景,必须限制
company_id。 - 敏感字段使用模型层
groups:不要使用视图invisible来保护敏感数据。
