记录规则机制

Odoo 里的 domain_force 到底做了什么:ir.rule 记录规则的真实拼装链路

很多人把 domain_force 当成“写个 domain 就完了”,但 Odoo 真正执行记录规则时,还会区分全局规则、分组规则、_inherits 父模型规则与缓存。本文从 ir_rule.py 出发,讲清 domain_force 如何被 safe_eval、如何 AND/OR 拼装,以及为什么同一条规则有时看起来没生效。

Odoo 开发 框架
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 7 阅读

结论先行

domain_force 不是“用户搜索时额外附加的一段 domain”那么简单。

在 Odoo 里,它真正扮演的是:

  • 记录规则的原始表达式来源
  • 会先经过 safe_eval()
  • 再按 全局规则 AND、分组规则 OR、最后整体再 AND 的方式拼装
  • 还可能叠加 _inherits 父模型的规则
  • 并且结果会按用户、sudo 状态、模型、操作类型和公司上下文做缓存

所以很多人感觉“这条 rule 明明写了,怎么像没生效”,通常不是 domain_force 没执行,而是 被别的规则一起组合之后,结果已经不是你脑子里那条单独 domain 的语义了


先看源码:domain_force 在哪里被消费

/home/ubuntu/odoo-temp/odoo/addons/base/models/ir_rule.py 里,ir.rule 定义很直接:

domain_force = fields.Text(string='Domain')

关键不在字段定义,而在这几个方法:

  • _eval_context()
  • _get_rules()
  • _compute_domain()
  • _check_domain()

其中 _eval_context()safe_eval() 提供的上下文非常关键:

return {
    'user': self.env.user.with_context({}),
    'company_ids': self.env.companies.ids,
    'company_id': self.env.company.id,
}

这意味着你在 XML 或后台里写的:

[('company_id', 'in', company_ids)]
[('user_id', '=', user.id)]

并不是“魔法变量”,而是 Odoo 在求规则时专门喂进去的上下文对象。


第一步:先挑出“哪些规则有资格参与本次计算”

_get_rules(model_name, mode='read') 会先按模型和操作类型筛一遍:

  • 当前模型是否匹配
  • 规则是否激活
  • 当前操作是否命中 perm_read / perm_write / perm_create / perm_unlink
  • 规则是不是全局规则
  • 如果不是全局规则,当前用户是否在该规则的 group 里

这一步非常重要。

因为很多人误以为:

“我给某个组写了一条 rule,它就会和所有规则一起 AND。”

其实不是。Odoo 先筛出本次真的适用的规则,后面才谈组合逻辑。


第二步:全局规则 AND,分组规则 OR

真正的核心在 _compute_domain()

for rule in rules.sudo():
    dom = Domain(safe_eval(rule.domain_force, eval_context)) if rule.domain_force else Domain.TRUE
    if rule.groups:
        group_domains.append(dom)
    else:
        global_domains.append(dom)

if group_domains:
    global_domains.append(Domain.OR(group_domains))
return Domain.AND(global_domains).optimize(model)

翻译成人话就是:

  1. 没有 group 的规则,算全局规则,彼此之间是 AND
  2. 有 group 的规则,只把当前用户命中的那些拿出来,彼此之间是 OR
  3. 如果存在分组规则,先把它们 OR 成一大块,再跟全局规则整体 AND

这就是为什么:

  • 多条全局 rule 往往越加越“窄”
  • 多条组内 rule 往往是在“放行多个入口”
  • 但这些被放行的入口,仍然逃不过全局 rule 的总闸门

一个非常常见的误解是:

“我给销售组加了一条能看自己单据的规则,为什么还是看不到?”

答案常常是:还有一条全局规则先把范围锁死了


第三步:_inherits 父模型规则也会被带进来

_compute_domain() 前面还有一段很容易被忽略的逻辑:

for parent_model_name, parent_field_name in model._inherits.items():
    if domain := self._compute_domain(parent_model_name, mode):
        global_domains.append(Domain(parent_field_name, 'any', domain))

意思是:如果当前模型用了 _inherits,父模型上的记录规则也会被递归计算,再通过父字段折进来。

这会带来两个很实战的现象:

  1. 你明明只给子模型配了 rule,却还是被父模型卡住
  2. 你以为自己在调一个业务模型,实际命中的是底层父模型安全边界

所以,排查 _inherits 模型权限时,只盯当前模型的 ir.rule 往往不够。


第四步:domain_force 不是原样 SQL,而是 safe_eval + Domain.validate

_check_domain() 会先做校验:

domain = safe_eval(rule.domain_force, eval_context)
Domain(domain).validate(model)

这说明两件事:

1)domain_force 本质是 Python 表达的 domain 结构

它不是数据库 SQL 片段,也不是字符串拼接搜索条件。

2)就算 safe_eval 通过,也还要过 Domain 结构校验

也就是说,下面两种问题都可能报错:

  • 语法能跑,但字段路径不合法
  • 字段存在,但运算符或值结构不合法

因此写记录规则时,最稳的思路不是“先把表达式写复杂”,而是:

  • 先写最小可用 domain
  • 再一点点加条件
  • 每次都验证真实生效范围

第五步:为什么切公司后结果会变

_compute_domain()ormcache 缓存,但缓存 key 里专门包含:

'allowed_company_ids'

这意味着同一个用户、同一个模型、同一个操作,只要切换了激活公司集合,记录规则结果就可能不同。

这也解释了很多多公司环境下的“玄学”问题:

  • 同一个账号上午能看,下午切了公司就不能看
  • 不是 rule 被删了,而是 缓存命中条件和 eval context 里的公司集合变了

实战里最容易误解的 5 件事

1)把分组规则当成 AND

错。分组规则之间默认是 OR。

2)以为没写 group 就是“默认所有员工都能看”

不只是“所有员工都能看”,而是 所有命中此模型与操作的用户都要被它约束。这通常比想象中更强。

3)以为 sudo 还能继续测试规则

_get_rules() 里如果 self.env.su,直接返回空规则集。也就是说:

  • sudo() 经常不是“我想看看规则算出来啥”
  • 而是“我直接绕过这层规则了”

记录规则不是只管 read。你可能 read 没问题,但 write 被另一套 rule 卡住。

5)把前端 domain 和记录规则混为一谈

字段 domain、窗口 action domain、search view filter、record rule 都会影响“你最终看见什么”,但它们不在同一层。

其中 domain_force 属于 安全边界层,优先级和后果都比普通 UI 过滤重。


一套够用的排查顺序

如果你怀疑 domain_force 没生效,建议按这个顺序查:

  1. 当前操作是什么:read、write、create 还是 unlink
  2. 当前用户命中了哪些 group rule
  3. 是否还有全局 rule 在更外层做 AND
  4. 模型是否带 _inherits,父模型 rule 是否一起进来了
  5. 当前激活公司集合是不是变了
  6. 是不是用了 sudo,导致你根本没在测记录规则

总结

真正理解 domain_force,关键不是背“它是记录规则的 domain”。

关键是记住这张心智图:

  • domain_force 提供原始规则表达式
  • safe_eval()user / company_id / company_ids 求值
  • 全局规则 AND
  • 分组规则 OR
  • _inherits 父模型规则继续叠加
  • 结果按用户与公司上下文缓存

一旦把这套链路看清,很多权限问题就不再玄学,而会变成很具体的组合逻辑问题。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。