先说结论
compute_sudo 不是一个“顺手加一下更稳”的参数。
它真正决定的是:
这个计算字段在重算时,到底按当前用户权限执行,还是按超级用户权限执行。
这件事一旦理解错,就会出现两类常见问题:
- 你以为字段算不出来是 bug,其实是权限不够
- 你以为字段只是展示数据,结果把不该让用户看到的信息也算出来并展示了
所以 compute_sudo 的本质,不是性能,也不是语法细节,而是权限边界。
源码里已经把默认规则写得很直白
在 odoo/orm/fields.py 里,Odoo 对这个参数的说明很清楚:
compute_sudo=True/False决定计算字段是否以 superuser 身份重算- 默认规则是:
- 存储计算字段:默认
True - 非存储计算字段:默认
False
同一个文件里还有初始化逻辑:
- 只要字段是
compute,默认attrs['compute_sudo'] = store - 如果字段是
related,默认会走related_sudo,而它默认又通常是True
这意味着很多开发者根本没显式写 compute_sudo=True,但系统其实已经默默这么做了。
所以第一层认知要先纠正:
不是“只有你手写了 compute_sudo=True 才会提权”,而是很多 stored compute / related 字段本来就默认在 sudo 语境里工作。
为什么 stored compute 默认倾向 sudo
因为 Odoo 认为“存储型计算字段”更像一种系统维护的派生结果,而不是一次临时界面展示。
比如:
- 汇总数量
- 统计计数
- 关联状态
- 业务聚合值
这类字段如果完全依赖当前登录用户权限,系统就会碰到一个很尴尬的问题:
- A 用户改了数据,算出一个结果
- B 用户权限更少,再触发重算,结果又变了
- 同一条记录的存储值可能随着触发者不同而不一致
这显然不适合作为数据库里的“稳定派生值”。
所以 Odoo 默认让 stored compute 更偏向:
由系统用更完整的视角算出一个确定值,再存下来。
这就是默认 store=True -> compute_sudo=True 背后的设计逻辑。
非存储字段为什么默认不 sudo
因为非存储字段更接近“当前用户眼前这次读取时临时算一下”。
这种字段更像是:
- 当前页面展示
- 当前用户上下文下的即时信息
- 不必写入数据库的动态结果
如果这里默认 sudo,就很容易把“系统知道的全部信息”直接展示给权限较低的用户。
所以 Odoo 选择的默认策略是:
- 临时展示型字段,更尊重当前用户视角
- 系统派生型字段,更重视计算稳定性
这个分层其实很合理。
一个最危险的误区:把 compute_sudo 当成“算不出来就开 sudo”
很多人开发时会遇到这种情况:
- 计算方法里读到了别的模型
- 普通用户打开表单报空、报错、统计不对
- 然后第一反应就是:
compute_sudo=True
这确实可能“让字段正常显示”。
但你要先问一句:
它正常显示的是“业务上应该给这个用户看的值”,还是“系统内部其实不该暴露给他的值”?
比如:
- 某字段统计了其他部门的记录数
- 某字段拼接了用户本不该读取的对象名称
- 某字段暴露了多公司下别家公司的关联结果
一旦字段本身又没有额外做输出过滤,前端最终就会把 sudo 计算结果直接展示出来。
这就不是“修复显示”,而是制造越权泄露。
你可以把它理解成“厨房权限”和“上菜权限”是两回事
一个很好记的类比是:
compute_sudo决定的是:厨房里谁可以进仓库拿食材- 页面展示决定的是:最后哪道菜被端到谁桌上
问题在于,很多计算字段把这两件事混了。
如果厨房里用了 sudo,把全部食材都拿到了; 但上菜时你又没有二次过滤; 那用户最终吃到的,就是他本不该看到的数据。
所以正确姿势不是盲目提权,而是分两步想:
- 计算阶段需不需要更高权限才能得到稳定结果?
- 展示阶段要不要按当前用户再做裁剪?
源码里还有一个很容易被忽略的信号
在 odoo/orm/models.py 里,Odoo 在把某些 related 字段转换到 SQL 表达式时,会检查:
- 当前环境是否已经 sudo
- 字段是否
compute_sudo - 字段是否 inherited
否则会直接报:
- 不能把这个 related 字段转换到 SQL,因为它不是 sudoed related / inherited field
这背后说明一个很重要的事实:
compute_sudo不只是 UI 层一个显示参数,它会影响 ORM 如何认为这个字段是“可安全展开”的。
也就是说,它是 ORM 访问语义的一部分,不是装饰品。
官方测试也在提醒你:默认值不是拍脑袋
在 odoo/addons/test_orm/tests/test_fields.py 里,Odoo 专门测了:
- 普通非存储计算字段默认
compute_sudo=False - 存储计算字段默认
compute_sudo=True - 某些可编辑的 stored compute 字段虽然可复制、可编辑,但仍然默认
compute_sudo=True
这等于告诉开发者:
默认值本来就是框架明确设计过的行为,不是偶然。
所以你修改它时,要知道自己是在改框架默认的安全与一致性假设。
什么场景更适合保留 compute_sudo=True
通常是这些情况:
1. 你要维护一个稳定的系统级派生值
比如:
- 数量汇总
- 计数器
- 状态聚合
- 后台统计字段
2. 计算依赖的底层对象并不要求和最终阅读者权限一致
例如:
- 系统内部流程要先聚合,再只展示一个安全结果
3. 不同触发者不应该算出不同持久值
这是 stored compute 最常见的诉求。
什么场景更应该谨慎,甚至显式写 compute_sudo=False
1. 字段值天然就是“用户视角相关”的
比如:
- “我是否可见”
- “我是否可操作”
- “当前用户能读到几条”
- “根据当前用户身份展示不同提示”
2. 字段里会拼接敏感对象信息
比如名称、金额、联系人、内部记录摘要。
3. 字段结果会被直接展示给低权限用户
尤其是在 portal、website、公开页、跨部门共享界面里。
这种时候,默认 sudo 往往不是你真正想要的行为。
一个非常实用的判断问题
每次写计算字段前,先问自己:
这个字段是在表达“系统事实”,还是“当前用户看到的事实”?
- 如果是系统事实,更可能接受
compute_sudo=True - 如果是用户看到的事实,就要非常警惕 sudo
这句话基本能帮你避开一半以上的坑。
实战建议:别把安全和易用性交给默认碰运气
建议 1:stored compute 主动审视,不要因为默认值就忽略风险
很多 stored field 没写 compute_sudo,并不代表没有 sudo 风险。
建议 2:字段展示前,确认结果是否已经脱敏
如果是 sudo 算出来的,最好确认:
- 是否只是布尔结果
- 是否只是安全计数
- 是否只是抽象状态
建议 3:和 depends_context('uid') 放一起时尤其小心
用户维度依赖 + sudo 计算,语义很容易打架。
建议 4:多公司场景再多看一眼
sudo 之后,跨公司对象更容易被带进计算路径。
一句话总结
compute_sudo 讲的不是“算得快不快”,而是:
这个计算字段,到底是在当前用户的权限世界里求值,还是在系统超级用户的权限世界里求值。
一旦你把它当成权限边界问题来看,很多 stored compute、related 字段、多公司统计和前台泄露问题,都会一下子看清楚。
DISCUSSION
评论区