第12讲、Odoo 18 权限控制机制详解
目录
- 引言
- 权限机制概述
- 权限组(Groups)
- 访问控制列表(ACL)
- 记录规则(Record Rules)
- 字段级权限控制
- 按钮级权限控制
- 菜单级权限控制
- 综合案例:多层级权限控制
- 最佳实践与注意事项
- 总结
引言
Odoo 18 提供了一套完整而灵活的权限控制机制,可以精确控制用户对系统各个层面的访问权限。本文将深入介绍 Odoo 18 的权限控制机制,从权限组到记录规则,从字段级别到按钮级别,再到菜单级别,全面解析其工作原理,并附上实际案例说明。
权限机制概述
Odoo 的权限控制体系是多层次的,从粗到细可以分为以下几个层级:
- 菜单级权限:控制用户是否可以看到特定菜单
- 模型级权限(ACL):控制用户对整个模型的读、写、创建、删除权限
- 记录级权限(Record Rules):控制用户可以访问模型中的哪些记录
- 字段级权限:控制用户可以查看或编辑模型中的哪些字段
- 按钮级权限:控制用户可以使用界面上的哪些功能按钮
这些权限控制机制相互配合,形成了一个完整的权限管理体系。下面我们将逐一深入介绍每个层级的权限控制机制。
权限组(Groups)
权限组是 Odoo 权限控制的基础,所有权限都是通过权限组来分配的。用户被分配到不同的权限组,从而获得相应的权限。
工作原理
- 每个权限组都是
res.groups
模型的一条记录 - 用户可以同时属于多个权限组,权限是累加的
- 权限组可以继承其他权限组的权限
- 权限组可以按类别进行分组
实现方式
权限组通常在模块的 XML 文件中定义:
<record id="group_project_manager" model="res.groups"><field name="name">项目经理</field><field name="category_id" ref="base.module_category_project_management"/><field name="implied_ids" eval="[(4, ref('group_project_user'))]"/><field name="users" eval="[(4, ref('base.user_admin'))]"/>
</record>
字段说明
name
: 权限组名称category_id
: 权限组所属类别implied_ids
: 该权限组隐含的其他权限组(继承关系)users
: 默认分配到该权限组的用户
案例:项目管理模块的权限组设计
<!-- 项目用户组 -->
<record id="group_project_user" model="res.groups"><field name="name">项目用户</field><field name="category_id" ref="base.module_category_project_management"/>
</record><!-- 项目经理组 -->
<record id="group_project_manager" model="res.groups"><field name="name">项目经理</field><field name="category_id" ref="base.module_category_project_management"/><field name="implied_ids" eval="[(4, ref('group_project_user'))]"/>
</record><!-- 项目总监组 -->
<record id="group_project_director" model="res.groups"><field name="name">项目总监</field><field name="category_id" ref="base.module_category_project_management"/><field name="implied_ids" eval="[(4, ref('group_project_manager'))]"/><field name="users" eval="[(4, ref('base.user_admin'))]"/>
</record>
在这个案例中,我们定义了三个权限组:项目用户、项目经理和项目总监。项目经理继承了项目用户的权限,项目总监继承了项目经理的权限,形成了一个权限层级结构。
访问控制列表(ACL)
访问控制列表(ACL)是模型级别的权限控制,用于控制用户对整个模型的读、写、创建、删除权限。
工作原理
- ACL 定义了特定权限组对特定模型的权限
- 每个 ACL 规则都是
ir.model.access
模型的一条记录 - 权限是累加的,如果用户属于多个权限组,则拥有这些权限组的所有权限
- 如果没有明确授予权限,则默认没有权限
实现方式
ACL 通常在模块的 security/ir.model.access.csv
文件中定义:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_project_task_user,project.task.user,model_project_task,group_project_user,1,0,0,0
access_project_task_manager,project.task.manager,model_project_task,group_project_manager,1,1,1,1
字段说明
id
: ACL 规则的唯一标识符name
: ACL 规则的名称model_id:id
: 目标模型的外部 IDgroup_id:id
: 权限组的外部 IDperm_read
: 是否有读取权限(0 或 1)perm_write
: 是否有修改权限(0 或 1)perm_create
: 是否有创建权限(0 或 1)perm_unlink
: 是否有删除权限(0 或 1)
案例:项目任务的 ACL 设计
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_project_task_user,project.task.user,model_project_task,group_project_user,1,1,1,0
access_project_task_manager,project.task.manager,model_project_task,group_project_manager,1,1,1,1
access_project_milestone_user,project.milestone.user,model_project_milestone,group_project_user,1,0,0,0
access_project_milestone_manager,project.milestone.manager,model_project_milestone,group_project_manager,1,1,1,1
在这个案例中,项目用户可以读取、修改和创建任务,但不能删除任务;项目经理可以读取、修改、创建和删除任务。对于里程碑,项目用户只能查看,而项目经理可以完全控制。
记录规则(Record Rules)
记录规则是记录级别的权限控制,用于控制用户可以访问模型中的哪些记录。
工作原理
- 记录规则定义了特定权限组可以访问模型中的哪些记录
- 每个记录规则都是
ir.rule
模型的一条记录 - 记录规则使用域表达式(domain)来过滤记录
- 如果用户属于多个权限组,则可以访问满足任一记录规则的记录
- 记录规则可以针对读取、修改、创建和删除操作分别设置
实现方式
记录规则通常在模块的 XML 文件中定义:
<record id="project_task_rule_user" model="ir.rule"><field name="name">项目任务:用户只能看到自己的任务</field><field name="model_id" ref="model_project_task"/><field name="domain_force">[('user_id', '=', user.id)]</field><field name="groups" eval="[(4, ref('group_project_user'))]"/><field name="perm_read" eval="1"/><field name="perm_write" eval="1"/><field name="perm_create" eval="1"/><field name="perm_unlink" eval="0"/>
</record>
字段说明
name
: 记录规则的名称model_id
: 目标模型的引用domain_force
: 域表达式,用于过滤记录groups
: 适用的权限组perm_read
: 是否适用于读取操作perm_write
: 是否适用于修改操作perm_create
: 是否适用于创建操作perm_unlink
: 是否适用于删除操作
案例:项目任务的记录规则设计
<!-- 项目用户只能看到自己负责的任务 -->
<record id="project_task_rule_user" model="ir.rule"><field name="name">项目任务:用户只能看到自己的任务</field><field name="model_id" ref="model_project_task"/><field name="domain_force">[('user_id', '=', user.id)]</field><field name="groups" eval="[(4, ref('group_project_user'))]"/>
</record><!-- 项目经理可以看到自己项目中的所有任务 -->
<record id="project_task_rule_manager" model="ir.rule"><field name="name">项目任务:经理可以看到自己项目中的所有任务</field><field name="model_id" ref="model_project_task"/><field name="domain_force">[('project_id.user_id', '=', user.id)]</field><field name="groups" eval="[(4, ref('group_project_manager'))]"/>
</record><!-- 项目总监可以看到所有任务 -->
<record id="project_task_rule_director" model="ir.rule"><field name="name">项目任务:总监可以看到所有任务</field><field name="model_id" ref="model_project_task"/><field name="domain_force">[(1, '=', 1)]</field><field name="groups" eval="[(4, ref('group_project_director'))]"/>
</record>
在这个案例中,项目用户只能看到分配给自己的任务,项目经理可以看到自己负责的项目中的所有任务,而项目总监可以看到所有任务。
字段级权限控制
字段级权限控制用于控制用户可以查看或编辑模型中的哪些字段。
工作原理
- 字段级权限通过字段的
groups
属性来控制 - 只有属于指定权限组的用户才能查看或编辑该字段
- 字段级权限可以在模型定义中设置,也可以在视图中设置
实现方式
在模型定义中设置字段级权限
class ProjectTask(models.Model):_name = 'project.task'_description = '项目任务'name = fields.Char('任务名称', required=True)description = fields.Text('任务描述')priority = fields.Selection([('0', '低'),('1', '中'),('2', '高'),], string='优先级', default='1')budget = fields.Float('预算', groups='project.group_project_manager')actual_cost = fields.Float('实际成本', groups='project.group_project_director')
在视图中设置字段级权限
<field name="budget" groups="project.group_project_manager"/>
<field name="actual_cost" groups="project.group_project_director"/>
案例:项目任务的字段级权限设计
class ProjectTask(models.Model):_name = 'project.task'_description = '项目任务'name = fields.Char('任务名称', required=True)description = fields.Text('任务描述')user_id = fields.Many2one('res.users', string='负责人')date_deadline = fields.Date('截止日期')priority = fields.Selection([('0', '低'),('1', '中'),('2', '高'),], string='优先级', default='1')# 只有项目经理及以上权限才能看到预算字段budget = fields.Float('预算', groups='project.group_project_manager')# 只有项目总监才能看到实际成本字段actual_cost = fields.Float('实际成本', groups='project.group_project_director')# 只有项目总监才能看到利润率字段profit_margin = fields.Float('利润率 (%)', compute='_compute_profit_margin', groups='project.group_project_director')@api.depends('budget', 'actual_cost')def _compute_profit_margin(self):for task in self:if task.budget and task.actual_cost:task.profit_margin = (task.budget - task.actual_cost) / task.budget * 100else:task.profit_margin = 0.0
在视图中的应用:
<record id="view_task_form" model="ir.ui.view"><field name="name">project.task.form</field><field name="model">project.task</field><field name="arch" type="xml"><form><sheet><group><field name="name"/><field name="user_id"/><field name="date_deadline"/><field name="priority"/><!-- 只有项目经理及以上权限才能看到预算字段 --><field name="budget" groups="project.group_project_manager"/><!-- 只有项目总监才能看到实际成本和利润率字段 --><field name="actual_cost" groups="project.group_project_director"/><field name="profit_margin" groups="project.group_project_director"/></group><field name="description"/></sheet></form></field>
</record>
在这个案例中,预算字段只对项目经理及以上权限可见,而实际成本和利润率字段只对项目总监可见。
按钮级权限控制
按钮级权限控制用于控制用户可以使用界面上的哪些功能按钮。
工作原理
- 按钮级权限通过按钮的
groups
属性来控制 - 只有属于指定权限组的用户才能看到和使用该按钮
- 按钮级权限在视图中设置
实现方式
<button name="action_approve" string="批准" type="object" groups="project.group_project_manager"/>
案例:项目任务的按钮级权限设计
<record id="view_task_form" model="ir.ui.view"><field name="name">project.task.form</field><field name="model">project.task</field><field name="arch" type="xml"><form><header><field name="state" widget="statusbar"/><!-- 任何用户都可以提交任务 --><button name="action_submit" string="提交" type="object" attrs="{'invisible': [('state', '!=', 'draft')]}"/><!-- 只有项目经理才能批准任务 --><button name="action_approve" string="批准" type="object" groups="project.group_project_manager"attrs="{'invisible': [('state', '!=', 'submitted')]}"/><!-- 只有项目总监才能关闭任务 --><button name="action_close" string="关闭" type="object" groups="project.group_project_director"attrs="{'invisible': [('state', '!=', 'approved')]}"/><!-- 任何用户都可以取消任务,但需要确认 --><button name="action_cancel" string="取消" type="object" confirm="确定要取消这个任务吗?"attrs="{'invisible': [('state', 'in', ['cancelled', 'done'])]}"/></header><sheet><!-- 表单内容 --></sheet></form></field>
</record>
对应的 Python 方法:
class ProjectTask(models.Model):_name = 'project.task'_description = '项目任务'state = fields.Selection([('draft', '草稿'),('submitted', '已提交'),('approved', '已批准'),('done', '已完成'),('cancelled', '已取消'),], string='状态', default='draft', tracking=True)def action_submit(self):self.write({'state': 'submitted'})def action_approve(self):self.write({'state': 'approved'})def action_close(self):self.write({'state': 'done'})def action_cancel(self):self.write({'state': 'cancelled'})
在这个案例中,任何用户都可以提交和取消任务,但只有项目经理才能批准任务,只有项目总监才能关闭任务。
菜单级权限控制
菜单级权限控制用于控制用户可以看到哪些菜单项。
工作原理
- 菜单级权限通过菜单的
groups
属性来控制 - 只有属于指定权限组的用户才能看到该菜单
- 菜单级权限在菜单定义中设置
实现方式
<menuitem id="menu_project_task" name="任务" parent="menu_project" action="action_project_task" groups="group_project_user"/>
案例:项目管理模块的菜单级权限设计
<!-- 主菜单:所有项目用户可见 -->
<menuitem id="menu_project_root" name="项目" sequence="40" groups="group_project_user"/><!-- 项目菜单:所有项目用户可见 -->
<menuitem id="menu_project" name="项目" parent="menu_project_root" sequence="10"/>
<menuitem id="menu_project_list" name="项目列表" parent="menu_project" action="action_project_list" sequence="10"/>
<menuitem id="menu_project_task" name="任务" parent="menu_project" action="action_project_task" sequence="20"/><!-- 报告菜单:只有项目经理可见 -->
<menuitem id="menu_project_report" name="报告" parent="menu_project_root" sequence="20" groups="group_project_manager"/>
<menuitem id="menu_project_task_analysis" name="任务分析" parent="menu_project_report" action="action_project_task_analysis" sequence="10"/><!-- 配置菜单:只有项目总监可见 -->
<menuitem id="menu_project_config" name="配置" parent="menu_project_root" sequence="30" groups="group_project_director"/>
<menuitem id="menu_project_tags" name="标签" parent="menu_project_config" action="action_project_tags" sequence="10"/>
<menuitem id="menu_project_stages" name="阶段" parent="menu_project_config" action="action_project_stages" sequence="20"/>
在这个案例中,所有项目用户都可以看到项目和任务菜单,但只有项目经理才能看到报告菜单,只有项目总监才能看到配置菜单。
综合案例:多层级权限控制
下面我们通过一个完整的项目需求管理系统案例,展示如何综合运用各种权限控制机制。
业务场景
我们要开发一个项目需求管理系统,包含模型 project.requirement
,需求如下:
用户角色 | 权限需求 |
---|---|
普通员工(Employee) | 只能查看和创建需求,只能看到自己创建的需求记录,不能看到预算和成本信息 |
部门经理(Manager) | 可以查看、创建、修改需求,可以看到本部门所有员工的需求,可以看到预算信息但不能看到成本信息,可以批准需求 |
高级管理层(Director) | 拥有所有权限,可以看到所有需求,可以看到预算和成本信息,可以批准和关闭需求 |
实现步骤
1. 定义权限组
<!-- 需求用户组 -->
<record id="group_requirement_user" model="res.groups"><field name="name">需求用户</field><field name="category_id" ref="base.module_category_project_management"/>
</record><!-- 需求经理组 -->
<record id="group_requirement_manager" model="res.groups"><field name="name">需求经理</field><field name="category_id" ref="base.module_category_project_management"/><field name="implied_ids" eval="[(4, ref('group_requirement_user'))]"/>
</record><!-- 需求总监组 -->
<record id="group_requirement_director" model="res.groups"><field name="name">需求总监</field><field name="category_id" ref="base.module_category_project_management"/><field name="implied_ids" eval="[(4, ref('group_requirement_manager'))]"/><field name="users" eval="[(4, ref('base.user_admin'))]"/>
</record>
2. 定义访问控制列表(ACL)
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_project_requirement_user,project.requirement.user,model_project_requirement,group_requirement_user,1,0,1,0
access_project_requirement_manager,project.requirement.manager,model_project_requirement,group_requirement_manager,1,1,1,0
access_project_requirement_director,project.requirement.director,model_project_requirement,group_requirement_director,1,1,1,1
3. 定义记录规则
<!-- 普通用户只能看到自己创建的需求 -->
<record id="rule_requirement_user_own" model="ir.rule"><field name="name">需求用户只能看到自己的需求</field><field name="model_id" ref="model_project_requirement"/><field name="domain_force">[('create_uid', '=', user.id)]</field><field name="groups" eval="[(4, ref('group_requirement_user'))]"/>
</record><!-- 经理可以看到本部门所有需求 -->
<record id="rule_requirement_manager_department" model="ir.rule"><field name="name">需求经理可以看到本部门的需求</field><field name="model_id" ref="model_project_requirement"/><field name="domain_force">[('department_id', '=', user.employee_id.department_id.id)]</field><field name="groups" eval="[(4, ref('group_requirement_manager'))]"/>
</record><!-- 总监可以看到所有需求 -->
<record id="rule_requirement_director_all" model="ir.rule"><field name="name">需求总监可以看到所有需求</field><field name="model_id" ref="model_project_requirement"/><field name="domain_force">[(1, '=', 1)]</field><field name="groups" eval="[(4, ref('group_requirement_director'))]"/>
</record>
4. 定义模型和字段级权限
class ProjectRequirement(models.Model):_name = 'project.requirement'_description = '项目需求'name = fields.Char('需求名称', required=True)description = fields.Text('需求描述')department_id = fields.Many2one('hr.department', string='所属部门', default=lambda self: self.env.user.employee_id.department_id)priority = fields.Selection([('0', '低'),('1', '中'),('2', '高'),], string='优先级', default='1')state = fields.Selection([('draft', '草稿'),('submitted', '已提交'),('approved', '已批准'),('done', '已完成'),('cancelled', '已取消'),], string='状态', default='draft', tracking=True)# 只有经理及以上权限才能看到预算字段budget = fields.Float('预算', groups='project_requirement.group_requirement_manager')# 只有总监才能看到成本字段cost = fields.Float('成本', groups='project_requirement.group_requirement_director')# 只有总监才能看到利润率字段profit_margin = fields.Float('利润率 (%)', compute='_compute_profit_margin', groups='project_requirement.group_requirement_director')@api.depends('budget', 'cost')def _compute_profit_margin(self):for req in self:if req.budget and req.cost:req.profit_margin = (req.budget - req.cost) / req.budget * 100else:req.profit_margin = 0.0def action_submit(self):self.write({'state': 'submitted'})def action_approve(self):self.write({'state': 'approved'})def action_done(self):self.write({'state': 'done'})def action_cancel(self):self.write({'state': 'cancelled'})
5. 定义视图和按钮级权限
<record id="view_requirement_form" model="ir.ui.view"><field name="name">project.requirement.form</field><field name="model">project.requirement</field><field name="arch" type="xml"><form><header><field name="state" widget="statusbar"/><!-- 任何用户都可以提交需求 --><button name="action_submit" string="提交" type="object" attrs="{'invisible': [('state', '!=', 'draft')]}"/><!-- 只有经理及以上才能批准需求 --><button name="action_approve" string="批准" type="object" groups="project_requirement.group_requirement_manager"attrs="{'invisible': [('state', '!=', 'submitted')]}"/><!-- 只有总监才能完成需求 --><button name="action_done" string="完成" type="object" groups="project_requirement.group_requirement_director"attrs="{'invisible': [('state', '!=', 'approved')]}"/><!-- 任何用户都可以取消需求 --><button name="action_cancel" string="取消" type="object" confirm="确定要取消这个需求吗?"attrs="{'invisible': [('state', 'in', ['cancelled', 'done'])]}"/></header><sheet><group><field name="name"/><field name="department_id"/><field name="priority"/><!-- 只有经理及以上才能看到预算 --><field name="budget" groups="project_requirement.group_requirement_manager"/><!-- 只有总监才能看到成本和利润率 --><field name="cost" groups="project_requirement.group_requirement_director"/><field name="profit_margin" groups="project_requirement.group_requirement_director"/></group><field name="description"/></sheet></form></field>
</record>
6. 定义菜单级权限
<!-- 主菜单:所有需求用户可见 -->
<menuitem id="menu_requirement_root" name="需求管理" sequence="50" groups="project_requirement.group_requirement_user"/><!-- 需求菜单:所有需求用户可见 -->
<menuitem id="menu_requirement" name="需求" parent="menu_requirement_root" sequence="10"/>
<menuitem id="menu_requirement_list" name="需求列表" parent="menu_requirement" action="action_requirement_list" sequence="10"/><!-- 报告菜单:只有经理及以上可见 -->
<menuitem id="menu_requirement_report" name="报告" parent="menu_requirement_root" sequence="20" groups="project_requirement.group_requirement_manager"/>
<menuitem id="menu_requirement_analysis" name="需求分析" parent="menu_requirement_report" action="action_requirement_analysis" sequence="10"/><!-- 配置菜单:只有总监可见 -->
<menuitem id="menu_requirement_config" name="配置" parent="menu_requirement_root" sequence="30" groups="project_requirement.group_requirement_director"/>
<menuitem id="menu_requirement_tags" name="标签" parent="menu_requirement_config" action="action_requirement_tags" sequence="10"/>
权限效果
-
普通员工:
- 可以看到需求管理菜单和需求列表菜单
- 可以查看和创建需求,但不能修改和删除
- 只能看到自己创建的需求
- 看不到预算、成本和利润率字段
- 可以提交和取消需求,但不能批准和完成需求
-
部门经理:
- 可以看到需求管理菜单、需求列表菜单和报告菜单
- 可以查看、创建和修改需求,但不能删除
- 可以看到本部门所有员工的需求
- 可以看到预算字段,但看不到成本和利润率字段
- 可以提交、批准和取消需求,但不能完成需求
-
高级管理层:
- 可以看到所有菜单
- 可以查看、创建、修改和删除需求
- 可以看到所有需求
- 可以看到预算、成本和利润率字段
- 可以执行所有操作(提交、批准、完成和取消需求)
最佳实践与注意事项
-
权限设计原则:
- 遵循最小权限原则,只给用户必要的权限
- 使用权限组继承关系简化权限管理
- 权限控制应该从粗到细,先控制菜单和模型级权限,再控制记录级和字段级权限
-
性能考虑:
- 记录规则会影响查询性能,特别是复杂的域表达式
- 避免使用过于复杂的记录规则
- 考虑使用索引优化记录规则的性能
-
安全注意事项:
- 菜单级权限只是隐藏菜单,不是真正的安全控制
- 必须结合 ACL 和记录规则来实现完整的权限控制
- 不要依赖客户端的权限控制,服务器端必须进行权限验证
-
调试技巧:
- 使用开发者模式查看权限问题
- 检查用户所属的权限组
- 检查模型的 ACL 规则
- 检查记录规则的域表达式
总结
Odoo 18 提供了一套完整而灵活的权限控制机制,可以从多个层面精确控制用户的权限:
- 权限组(Groups):权限控制的基础,用户通过所属的权限组获得相应的权限
- 访问控制列表(ACL):模型级别的权限控制,控制用户对整个模型的读、写、创建、删除权限
- 记录规则(Record Rules):记录级别的权限控制,控制用户可以访问模型中的哪些记录
- 字段级权限:控制用户可以查看或编辑模型中的哪些字段
- 按钮级权限:控制用户可以使用界面上的哪些功能按钮
- 菜单级权限:控制用户可以看到哪些菜单项
这些权限控制机制相互配合,形成了一个完整的权限管理体系,可以满足各种复杂的业务需求。通过合理设计和配置这些权限控制机制,可以确保系统的安全性和可用性,同时提供良好的用户体验。