前端

Odoo Domain Selector 为什么能把复杂筛选条件改回可视树:TreeEditor、操作符归一化与 archived 边界讲透

Odoo 后台里很多高级筛选、自动化条件和规则配置,最后都会落到 domain 字符串。但前端界面又必须把这些字符串重新还原成可编辑树。`domain_selector.js`、`domain_selector_operator_editor.js` 与 `utils.js` 这一组源码,讲的正是这件事:如何从 domain 反解出树、如何为不同字段类型收缩合法操作符、以及如何把 archived 这类通用边界条件折叠回可视开关。本文把这条链路讲透。

前端
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

Odoo 里很多“看起来是图形化配置”的界面,底层其实都要落成 domain:

  • 高级筛选;
  • 自动化规则;
  • 智能按钮条件;
  • 某些视图里的动态过滤器。

问题在于,domain 本身是程序员友好的表达,不是业务用户友好的表达。

所以前端必须解决一个很棘手的任务:

把一段 domain 字符串重新还原成可以点击、改字段、换操作符、改布尔连接词的可视树。

这就是 DomainSelector 的职责。

一、DomainSelector 的第一步不是渲染 UI,而是先确认“这段 domain 支不支持被还原”

onPropsUpdated() 一开头就会尝试:

domain = new Domain(p.domain)

如果这里直接失败,组件不会硬着头皮渲染一个半残 UI,而是:

  • tree = null
  • showArchivedCheckbox = false
  • includeArchived = false

然后模板层显示“This domain is not supported.”。

这一步看似保守,实际上很重要。

因为 domain 不是任何时候都能无损映射回树编辑器。某些复杂写法、调试态表达或暂时不支持的语法,如果前端强行解析,用户只会在可视界面里得到一个被静默改写的结果。

官方宁愿直说“我不支持”,也不偷偷给你改语义,这是对的。

二、真正的还原工作不在组件本身,而是交给 tree_processor service

当 domain 可解析后,DomainSelector 会并行做两件事:

  • this.treeProcessor.treeFromDomain(...)
  • this.fieldService.loadFieldInfo(p.resModel, "active")

前者把 domain 还原成树,后者则判断模型上有没有 active 字段。

这两步组合起来,就形成了 DomainSelector 的真实输入:

  • 一棵可编辑条件树;
  • 一份模型字段信息;
  • 一条额外的业务边界:是否支持 archived 切换。

因此 DomainSelector 并不是“自己懂 domain 的全部细节”,它更像一个装配层:

  • 解析交给 domain / tree processor;
  • 字段元数据交给 field service;
  • 最终 UI 组合和交互边界由自己控制。

三、getDomainDisplayedOperators() 解释了为什么不同字段类型看到的操作符不一样

这是这组源码里最有价值的部分之一。

getDomainDisplayedOperators(fieldDef) 没有把所有操作符都暴露给用户,而是按字段类型收缩:

  • boolean 只有 set / not set
  • date / datetimein range, =, <, >, set, not set
  • char / text 偏向 ilike, starts with
  • many2one / many2many 会给 in / not in / ilike

这背后是很明确的产品判断:

Domain 的语法能力很强,但图形化编辑器不该把所有可能性都原样暴露。

为什么?因为“语法允许”不等于“交互上合适”。

如果一个布尔字段也给出 like>in range 之类操作符,用户虽然 technically 能看到更多选项,但多数只会带来误解。

所以 DomainSelector 真正做的是操作符归一化

  • 对前端展示层,缩成合理集合;
  • 对不同字段类型,给出最能解释业务意义的候选。

四、默认条件不是拍脑袋生成的,而是沿着“默认字段 -> 默认操作符 -> 默认值”三段走完

utils.js 里的 getDefaultCondition(fieldDefs) 很短,但很有代表性。

它会依次调用:

  • getDefaultPath(fieldDefs)
  • getDomainDisplayedOperators(fieldDef)[0]
  • getDefaultValue(fieldDef, operator)

最终拼成 condition(fieldDef.name, operator, value)

也就是说,新增一条叶子条件时,前端并不是随便塞个空壳,而是明确遵守下面这条链路:

  1. 先找到一个最适合起步的字段;
  2. 再为这个字段挑最默认、最安全的操作符;
  3. 最后给出与字段类型和操作符匹配的默认值。

这就是为什么 DomainSelector 的“新增条件”体验通常不会太突兀——它给你的不是裸空白,而是一个能继续编辑的最小合法条件。

五、archived 不是普通条件,而是被折叠成一个专用业务开关

源码里对 archived 的处理非常值得学习。

它预定义了:

  • ARCHIVED_CONDITION = condition("active", "in", [true, false])
  • ARCHIVED_DOMAIN = '[("active", "in", [True, False])]'

然后在 onPropsUpdated() 中主动把这条条件从 & 连接树里剥离出来,映射为 includeArchived 布尔开关。

这相当于做了一次“语义提升”:

  • 底层仍然是 domain 条件;
  • 但 UI 层不让用户每次都手写 active in [true, false]
  • 而是给一个更符合认知的复选框:Include archived

这件事说明一个很重要的前端原则:

不是所有 domain 叶子都应该原样暴露,有些通用业务语义值得被提炼成独立控件。

六、update(tree) 把树重新压回 domain 字符串时,顺手补回 archived 边界

update(tree) 的逻辑也很干净:

  • includeArchived 为真,先构造 archiveDomain
  • 若有树,就 Domain.and([domainFromTree(tree), archiveDomain]).toString()
  • 若没有树,就直接退回 archived 边界本身。

这说明 archived 开关不是 UI 装饰,而是序列化阶段的一等公民。

也就是说,DomainSelector 并不是“编辑完树再额外拼个 checkbox 状态”,而是从一开始就把它当成完整 domain 的组成部分。

七、模板层暴露了双轨编辑模式:可视树 + 调试代码框

domain_selector.xml 很值得一看,因为它把两条编辑路径同时留着:

  • 普通模式看 TreeEditor
  • debug 模式下还能直接看到 code editor textarea

这种双轨设计特别符合 Odoo 的受众结构:

  • 普通实施或业务用户可以用图形化树;
  • 开发者 / 顾问可以直接看 domain 文本;
  • 只读和可编辑模式都能共存。

这不是重复造轮子,而是在承认:domain 这种对象天然就有“业务可视化”和“技术原文”两种查看需求。

八、二开最容易踩的坑

误区 1:把所有操作符都一股脑扔给用户

这通常不是更强,而是更乱。

误区 2:只会从 tree 生成 domain,不会从 domain 还原 tree

这样用户一旦回到编辑界面,就只能看到原始字符串,体验会断层。

误区 3:把 archived 当普通条件叶子处理

用户真正想表达的是“包含归档记录”,不是“我会写 active in [true, false]”。

误区 4:不区分 debug 与普通交互模式

开发者需要原文,业务用户不一定需要。把两者混成一个界面,往往两边都不满意。

九、结论

DomainSelector 的价值,不在于它把 domain “显示出来”,而在于它做了三层关键翻译:

  • 把字符串 domain 还原成可编辑树;
  • 把过宽的语法能力收缩成类型友好的操作符集合;
  • 把通用业务语义(如 archived)提升成独立控件。

所以更准确地说,Odoo 的 DomainSelector 是一个domain 语义适配器

它连接的是底层表达能力与上层可视交互,而不只是一个筛选条件编辑框。

DISCUSSION

评论区

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