Skip to content

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: 能否读取记录(searchread)。
  • 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 记录规则间的逻辑关系

  1. 组特定规则(Groups Rules)之间的关系 如果你属于多个用户组(比如你既是“销售员”又是“库存专员”),每个组都对同一个模型定义了规则:
    • 逻辑关系:OR (并集)。
    • 含义:只要你满足其中任一一组的规则,你就能看到这条数据。
  2. 全局规则(Global Rules)之间的关系 全局规则是指那些没有绑定任何“用户组”的规则(或者在 XML 中 global="True")。
    • 逻辑关系:AND(交集)。
    • 含义: 你必须同时满足所有全局规则,才能看到数据。全局规则就像是漏斗,每一层都会过滤掉一部分数据。
    • 例子:
      • 全局规则1:只能查看“已激活”的记录。
      • 全局规则2:只能查看“本公司”的记录。
      • 结果:只能查看“已激活”且“属于本公司”的记录。
  3. 全局规则与组规则之间的关系 这是最关键的一层,也是很多开发者权限配置出错的地方。
    • 逻辑关系:AND(交集)。
    • 公式:(全局规则1 AND 全局规则2) AND (组规则1 OR 组规则2 OR 组规则3)。
    • 核心逻辑:
      1. 系统先计算所有组规则的并集(把你所有组的权限都加起来)。
      2. 然后使用全局规则对这个并集进行“收缩”过滤。
      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">[
            '|',
                '&amp;',
                    ('target_id.target_type', '=', 'user'),
                    ('target_id.user_id', '=', user.id),
                '&amp;',
                    ('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">[
            '|',
                '&amp;',
                    ('target_id.target_type', '=', 'user'),
                    ('target_id.user_id', '=', user.id),
                '&amp;',
                    ('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): 未登录用户,访问网站前台。

九、权限调试排错

检查为什么看不见这条记录

  1. 检查用户是否有所属组。
  2. 检查ACL(模型权限)。
  3. 检查记录规则。
  4. 检查字段权限。

代码中临时打印

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 来保护敏感数据。