
一、先认清 Odoo 原生权限的三层结构
很多企业装完 Odoo 之后,管理员在「设置 → 用户」里勾了半天组,回头还是被业务部门追问:为什么销售能看见别的销售的项目?为什么仓库文员能改采购单价?问题的根源是,多数人只用了 Odoo 权限体系里最粗的那一层。
Odoo 原生的权限控制,严格说只有三层,缺一不可,但也仅此而已:
第一层:用户组(res.groups)。决定一个人能进哪些菜单、用哪些功能,比如「销售 / 用户」「库存 / 管理员」。这是界面上唯一能直接勾的东西,也是大多数人以为的「权限管理」。
第二层:模型级访问控制(ir.model.access)。决定某个组对某张表(模型)能不能读、写、建、删,粒度是「整张表」。自定义模块必须自己写,原生界面不管这件事。
第三层:记录级规则(ir.rule)。决定在整张表里,这个人能看到哪些「行」,比如「只看自己负责的项目」。写在 XML 里,界面上藏得很深。
这三层各管一段,谁也替代不了谁。但真实企业的需求往往长这样:同一个项目列表,项目经理要能改,财务只能看金额相关字段,普通成员只能看自己参与的任务——这已经同时压到了三层以上,还叠加了「按项目类别隔离」「关注人可见」这类跨表逻辑。这时候,靠界面勾选是永远做不对的,必须先把需求整理成矩阵,再逐层落地。
二、六维权限模型:先把需求归类,再谈实现
我们在多个生产环境里收敛出一套「六维」归类法。它的价值不在技术上,而在于沟通:业务方提的任何一条权限诉求,都能归到六维之一,你不用每次都从零开始想「这个该怎么实现」。
六维模型如下表:
| 维度 | 解决什么问题 | 落在哪一层 |
|---|---|---|
| 1. 用户维度 | 某个具体的人的例外权限 | 直接加入用户组,慎用 |
| 2. 角色维度 | 岗位能做什么:销售 / 采购 / 财务 / 项目经理 | res.groups + ir.model.access |
| 3. 字段维度 | 同一张表里,哪些字段谁能看、谁能改 | 字段 groups 属性 + 视图继承 |
| 4. 关注人维度 | 我关注 / 参与的单据,即使不是负责人也要能看 | ir.rule 查 message_follower_ids |
| 5. 项目经理维度 | 我负责的项目下的任务、工时、成本全部可见 | ir.rule 跨表关联 |
| 6. Odoo 管理员维度 | 谁能修改权限本身——必须单列、必须极小 | base.group_system + 变更审批 |
使用方法很简单:需求评审时,把业务方说的每一条「谁能看什么、谁能改什么」逐条贴上维度标签。比如「财务要看到项目成本价」归第三维,「出差的同事要能看自己跟的单」归第四维,「分公司只能看本分公司的数据」本质是第五维加多公司规则的组合。
归类完你会发现,80% 的需求集中在第二、三、五维,而最容易出安全事故的恰恰是第六维——谁有权修改权限本身。这一维必须单独立规矩:Odoo 管理员账号极少数人持有、开启双因素验证、所有权限变更走审批记录。权限体系自己没有权限约束,等于整个体系建在沙子上。
三、Excel 权限矩阵:三张表把需求变成代码
矩阵不是画给人看的花架子,它的正确用法是当成源文件:Excel 填完,用脚本机械翻译成 Odoo 的 CSV 和 XML,人和代码各干各的,抄写错误为零。我们固定用三张表。
3.1 角色定义表
一个角色一行,写清楚角色代码、显示名、继承哪些原生组。角色代码一旦定下就不要改,后面所有 CSV、XML、视图都引用它。继承关系要写实:比如「项目经理」继承 base.group_user 加 project.group_project_manager,这样原生功能不用重新授权。
3.2 模型 × 角色 CRUD 矩阵
行是模型,列是角色,格子里填「读 / 增删改查 / 无」。这张表就是 ir.model.access.csv 的原始数据。填表时有个实用口诀:先全部填「无」,再一格一格往里加——从「全部可读再收权」出发的表,十有八九会漏收。
3.3 字段可见矩阵
行是「模型.字段」,列是角色,格子只有「可见 / 不可见」。这张表专门管成本价、毛利率、工资这类敏感字段,是第三维(字段维度)的落地依据。
三张表填完,剩下的就是写生成脚本:表 2 逐行输出 CSV,表 3 逐行输出字段覆写 XML。脚本半天就能写好,之后每次新增角色或模块,改 Excel、跑脚本、提交代码,权限变更从此走版本管理,谁改了什么、什么时候改的,Git 记录一清二楚。
四、模型级权限:ir.model.access.csv 四列写法
模型级权限放在模块的 security/ir.model.access.csv 里,表头固定,一个字符都不能差:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_prj_sales,project.project sales,model_project_project,group_sales_role,1,0,0,0
access_prj_pm,project.project pm,model_project_project,group_pm_role,1,1,1,1
access_acc_fin,account.move fin,model_account_move,group_fin_role,1,1,1,1
三个必须记牢的点,都是实际踩出来的:
第一,没有 ACL 就是一切皆无。自定义模型哪怕在界面上勾了再多组,只要没有 access 记录,任何调用直接抛 AccessError。排查「新建项目报错」这类问题时,第一反应就应该是查这个文件里有没有对应模型的记录。
第二,group_id 留空等于全站放行。包括门户用户和公开用户。除非你明确要这个效果,否则这一列永远不要留空。
第三,Odoo 18 起用户组的分类字段改成了 privilege_id,老教程里的 category_id 写法在 19.0 上已经不适用,直接照抄老代码会导致权限界面的分组显示错乱。涉及版本差异的写法,动手前先核对官方文档(本文第十一节给了清单)。
五、记录级权限 ir.rule:OR 与 AND 的语义必须背下来
记录规则决定「这个人能看到哪些行」。核心是 domain_force 里的一串域表达式,user 变量代表当前登录用户。先看两个最常用的场景。
5.1 场景一:只看自己负责的项目
<record id="rule_project_own" model="ir.rule">
<field name="name">项目:仅本人负责</field>
<field name="model_id" ref="project.model_project_project"/>
<field name="domain_force">[('user_id','=',user.id)]</field>
<field name="groups" eval="[(4, ref('group_pm_role'))]"/>
</record>
5.2 场景二:负责人或关注人可见
<record id="rule_task_visible" model="ir.rule">
<field name="name">任务:负责人或关注人</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">['|',('user_ids','in',[user.id]),('message_follower_ids','in',[user.partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
5.3 多条规则的叠加语义(最容易翻车的地方)
Odoo 对不同类型的规则采用完全不同的逻辑运算,这个语义必须背下来:
| 规则类型 | 多条规则之间 | 实际效果 |
|---|---|---|
| 组规则(global=False) | OR(或) | 用户所在任意一个组放行,即可看到该记录 |
| 全局规则(global=True) | AND(与) | 对所有人生效,叠加后进一步收紧 |
实操含义:组规则之间是「放宽」,全局规则是「收口」。一套健康的权限体系,应该在关键业务模型上放一条 global=True 的收口规则(比如强制 company_id 匹配),再用组规则按角色放宽。只写组规则、不写全局规则,等于大门敞开只靠房间锁。
还有两个致命细节:其一,记录规则对超级用户和 sudo() 调用不生效。代码评审时看到业务逻辑里随手写 sudo(),基本可以认定这层权限已经被拆了——只在定时任务、数据修复这类后台场景允许使用。其二,域里引用了不存在的字段,规则会静默失效。字段改名升级之后,规则不会跟着改,这属于最高优先级的回归测试项。
六、字段级权限:groups 属性是唯一正路
「成本价只有财务能看」这类需求,正确做法只有一个:在字段定义上加 groups,通过 _inherit 覆写实现,绝不去改原生源码:
standard_price = fields.Float(groups='group_fin_role')
加上之后会发生两件事。第一,视图层面,无权限用户的表单、列表、筛选、导出里都不会出现该字段;第二,也是关键区别——ORM 层面同样被拦截。没有该组的用户通过接口直接读取这个字段,得到的不是空值,而是访问错误。也就是说,界面隐藏和接口防护是一起生效的,对方绕不过去。
这比常见的三种错误做法安全一个量级:视图里设 readonly(只挡界面不挡接口);前端 CSS 藏字段(开发者工具一秒破);计算字段返回空(逻辑入侵业务,维护成本极高)。
实操建议:把表 3(字段可见矩阵)里所有「不可见」格子集中生成一个覆写文件,统一挂在 models/field_security.py,一个文件管全部敏感字段,别散落在各个继承文件里。
🔧 我整理的软件库
这些年折腾服务器、虚拟化和开发环境,装过的系统镜像、运维工具、开发套件、数据库与中间件,我都归档在自建的文件库里,持续补充新版本。没有弹窗广告,不强制注册,需要什么直接取。
常用工具随手可取 · 不定期补档
七、视图层隐藏只是第三道防线
按钮和菜单的显隐,用视图继承的 groups 属性即可,比如把「确认订单」按钮限定给项目经理:
<xpath expr="//button[@name='action_confirm']" position="attributes">
<attribute name="groups">group_pm_role</attribute>
</xpath>
但必须建立一个共识:视图隐藏是体验优化,不是安全措施。按钮藏了,方法还在,懂技术的人换个客户端直接调 RPC 照样执行。正确的叠法是三层防线:
第一层(安全):ir.rule 让对方根本没有那条记录的写权限——记录都看不见,方法调了也是空操作;第二层(安全):业务方法内做 check_access_rights / check_access_rule 或手工条件校验,多公司场景校验 company_id;第三层(体验):视图隐藏,让普通用户看不到不该看的按钮。
验收标准只有一句话:拿一个最小权限的测试账号,绕过界面直接调接口,所有越权操作必须全部被拒。用管理员账号自测权限,等于没测。
八、改了不生效?noupdate 与升级参数
权限改完「没反应」,九成出在生效机制上,三种情况对号入座:
情况一:XML 里的规则改了,升级后没变化。记录带 noupdate="1" 标记时,模块升级不会覆盖已存在的记录。处理办法:界面上删掉旧规则再升级,或者把 noupdate 去掉让代码接管。
情况二:新加的 CSV 没导入。文件加了但没执行升级。Docker 部署下的标准动作:
docker exec -it odoo19-enterprise odoo -d 数据库名 -u 你的模块名 --stop-after-init
docker restart odoo19-enterprise
情况三:界面上手改过权限,下次升级被还原。这是设计如此——代码里定义的记录优先。结论也只有一句:权限规则只写代码、走 Git,界面上不做任何长期修改。
九、故障排查表与 8 个高频踩坑点
权限问题的表现集中且典型,先查表再动手:
| 现象 | 最可能的原因 | 处理动作 |
|---|---|---|
| 调用直接报 AccessError | 模型没有 ACL 记录 | 补 ir.model.access.csv,注意 group_id 别留空 |
| 列表里出现不该看到的数据 | 缺少 ir.rule 或组规则被 OR 放行 | 补全局收口规则,用最小权限账号复测 |
| 该看到的记录看不到 | 全局规则把组规则也收窄了 | 检查 global 规则的域是否过严 |
| 改了 XML 升级后没变化 | 记录带 noupdate=1 | 删旧记录再升级,或去掉 noupdate |
| 字段在表单上仍可见 | 视图里显式写了该字段 | 视图继承中给字段加同样的 groups |
| 导出能拿到隐藏字段 | 只在视图层做了隐藏 | 字段级权限必须落在字段 groups 上 |
除此之外,8 个高频踩坑点按出现频率排序:
坑 1:只写 ir.rule 不写 ACL。直接 AccessError。顺序永远是:先给模型级 CRUD,再收记录级。
坑 2:域表达式引用了不存在或改名的字段。规则静默失效,比报错更危险。字段改名必须同步过一遍规则。
坑 3:只写组规则,没有全局收口规则。组规则之间是 OR,多一个组就多一条放行路径,最终一定漏。
坑 4:业务代码里滥用 sudo()。整层记录权限被绕过。全局搜索一遍 sudo(),逐个确认,前端链路一律清零。
坑 5:字段加了 groups,表单上还能看见。因为视图里显式写了该字段——视图继承里也要加同样的 groups。
坑 6:多公司规则与自定义规则打架。全局规则之间是 AND,会把自己写的组规则也进一步收窄,出现「明明该看到的看不到」。多公司场景要单独画矩阵。
坑 7:在界面上调权限图省事。下次升级被代码覆盖,白改还引入「两边不一致」的隐患。
坑 8:只做视图隐藏就宣布完工。接口层裸奔,一次渗透测试就现原形。
十、三种做法对比:矩阵法赢在哪
同样是做权限,三种常见路线的长期成本差异很大:
| 对比项 | 界面勾选 | 矩阵法(本文) | 全塞管理员组 |
|---|---|---|---|
| 上手速度 | 最快 | 前期多半天 | 最快 |
| 字段级控制 | 做不到 | 天然支持 | 做不到 |
| 变更可追溯 | 无记录 | Git 全记录 | 无记录 |
| 新人接手成本 | 靠口口相传 | 看矩阵即懂 | 无人说得清 |
| 审计合规 | 弱 | 可出报告 | 几乎为零 |
| 适用规模 | 个人 / 演示 | 团队与企业 | 都不建议 |
我的建议很直接:两人以上的团队、模块数量超过三个,一律走矩阵法。它的前期投入比界面勾选多半天,但换来的是权限变更可评审、可回滚、可审计——这三件事在权限事故复盘时值回所有投入。不建议的做法是第三种「全塞管理员组」:短期最省事,长期等于没有权限体系,而且一旦出数据泄露事故,你连「谁在什么时候给了谁权限」都说不清楚。
本文涉及的版本差异(如用户组分类字段的变化)属于随版本演进的部分,落地前请以第十一节官方文档的当前版本为准,不要凭记忆硬写。
十一、官方参考 URL 清单(发布前请实时核对)
以下入口用于核对版本差异与最新参数,动手实施前建议逐个打开确认:
① 安全机制总览(访问权限 / 记录规则 / 字段权限):
https://www.odoo.com/documentation/19.0/developer/reference/backend/security.html
② ORM 与域表达式(domain 写法):
https://www.odoo.com/documentation/19.0/developer/reference/backend/orm.html
③ 视图架构与 xpath 继承:
https://www.odoo.com/documentation/19.0/developer/reference/user_interface/view_architectures.html
④ Odoo 19 开发者文档总目录:
https://www.odoo.com/documentation/19.0/developer.html
⑤ 用户与权限管理(应用侧):
https://www.odoo.com/documentation/19.0/applications/general/users.html
结语
权限是信息化工程里最难返工的一块。数据模型错了可以加字段,界面丑了可以换主题,而权限设计错了,你要回头修改每一处「默认所有人都能看到」的业务假设,成本是几何级上升的。
所以从第一天起就做三件事:把六维矩阵画出来、把规则写进代码、拿最小权限账号去测。做完这三步,后面加人、加部门、加公司,基本不会翻车。
如果你正在推进 Odoo 落地,或者手上的系统权限已经一团乱麻,欢迎在评论区说说你的场景,也可以加我微信 17712677007 一起探讨。
👉 延伸阅读:Odoo 数据备份
权限体系做好之后,下一步就是数据安全。Odoo 的数据库和上传文件是两套完全不同的存储,备份策略要分开设计——我们整理过一套含 Crontab 自动化与保留策略的完整方案,建议配合本文一起看。
✍️ 关于作者
IT 峰哥,互联网技术从业者。日常折腾服务器、虚拟化、数据库与企业级应用,关注开源系统的二次开发与基础设施落地。这里记录的是踩过的坑和验证过跑得通的方案,不是转述文档。
交流:微信 / 手机 17712677007(注明来意,看到会回)
文中方案如有疏漏,欢迎在评论区指正 · 技术问题欢迎交流