先说结论
Odoo 官网搜索不是“输入关键字,然后全站 ilike 一把梭”。
website 模块真正做的是一条分层搜索链路:
- 前端请求
/website/snippet/autocomplete; - 网站对象根据
search_type组装多个模型的搜索细节; - 先做精确搜索,必要时再尝试 fuzzy term;
- 各模型把记录渲染成统一结构;
- 自动补全和
/website/search混合列表页复用这套结果。
也就是说,Odoo 的网站搜索核心不是“搜到什么”,而是“把不同模型的结果统一成同一种前台可渲染协议”。
这点非常重要。因为只有这样,页面、产品、博客、活动这些不同对象,才能共用一个搜索框和结果页。
一、入口路由为什么是 autocomplete,而不是单独做一个搜索 API
在 addons/website/controllers/main.py 里,公开入口是:
/website/snippet/autocomplete
这个接口返回的不是某个模型的原始记录,而是已经准备好给前端展示的数据:
resultsresults_countpartsfuzzy_search
这里的设计思路很清楚:
- 自动补全是第一消费方;
- 混合搜索页也是第二消费方;
- 所以前端真正需要的是“可展示数据”,不是 ORM 原始行。
换句话说,Odoo 把“搜索结果渲染”前移到了服务端。
这就是为什么返回里会带:
- 图标;
- 字段映射;
- 高亮后的文本;
- 截断后的描述;
- 已格式化的 monetary / html / text 值。
这样前端模板就不必知道每个模型该怎么展示。
二、搜索的关键不是 SQL,而是 _search_get_detail() 协议
真正决定一个模型能不能被网站搜索接入的,不是“有没有 name 字段”,而是它能不能提供搜索细节描述。
在 Odoo 这套机制里,模型通常提供类似 _search_get_detail() 的信息,包括:
modelbase_domainsearch_fieldsfetch_fieldsmappingiconorder
这意味着网站搜索不是硬编码“产品搜标题、页面搜 URL”。
而是让每个模型自己声明:
我允许被搜索哪些字段; 搜到后要取哪些字段; 这些字段在前台该如何映射成
name / description / website_url / image_url之类的统一槽位。
这套协议的好处有三个:
- 扩展性强:新模块只要实现协议,就能挂进搜索体系;
- 前端稳定:前台永远读统一的结果结构;
- 排序与过滤可控:每个模型可以保留自己的 domain 和 order。
所以如果你以后想给自定义网站模型接入搜索,正确做法通常不是改 controller,而是补齐模型侧的搜索细节。
三、为什么 autocomplete 返回前还要走一次 _search_render_results()
在 website.py 里,_search_render_results() 会遍历每种搜索明细,把记录集转成可展示数据。
这个阶段不是简单 read(),而是把:
- 取数字段
fetch_fields - 字段映射
mapping - 图标
icon - limit
一起交给记录集的 _search_render_results()。
结果里每一行都会带 _mapping,后续 controller 再根据 mapping 组装输出。
这说明 Odoo 的搜索体系故意分了两层:
- 模型层:知道记录本身该怎样变成展示数据;
- 控制器层:把多个模型结果拼成统一 JSON。
这个边界特别实用。
因为“产品该显示价格还是分类”“页面该显示摘要还是 URL”这种决策,最懂的一定是对应模型,不应该全塞到 controller 里。
四、fuzzy search 为什么不是无脑开启
website.py 里的 _search_find_fuzzy_term() 有几个很值得注意的保护条件:
- 少于 4 个字符,不做 fuzzy;
- 含空格的多词搜索,不做 fuzzy;
- 数字占比 80% 以上,不做 fuzzy。
这三个限制非常有经验。
1)太短的词不适合模糊匹配
例如用户输入 crm、api、erp 这种短词时,模糊匹配特别容易把结果带偏。
2)多词搜索更适合保留原意
multi website domain 这类词组,一旦拿去对单词做拼写近似,反而会把真正语义拆坏。
3)数字占比高时,通常是货号、编码、SKU
这类场景最怕“好心办坏事”。
比如用户搜 P79355,系统如果把它糊成别的词,结果体验会非常差。
所以 Odoo 的策略不是“尽可能模糊”,而是:
只在最有可能帮到用户、且误伤较低的场景下启用 fuzzy。
这是一种很产品化的保守设计。
五、结果高亮为什么放在服务端做
在 controller 的 autocomplete() 里,如果某个映射字段声明了 match=True,并且字段类型是文本,Odoo 会:
- 按搜索词切分文本;
- 渲染
website.search_text_with_highlight模板; - 返回带高亮片段的 HTML。
这说明 Odoo 不想把“高亮策略”下放到前端各处重复实现。
这样做有几个实际好处:
- 同一搜索结果在不同组件里表现一致;
- 服务端知道哪些字段允许高亮,哪些不允许;
- 截断、转义、HTML 类型格式化能放在一个管道里完成。
也因此,前端看到的并不只是纯文本,而是已经经过:
- 截断;
- escape;
- qweb field 格式化;
- 高亮模板渲染;
的一份“半成品 UI 数据”。
六、为什么 /website/search 结果页其实复用了 autocomplete
很多人会本能地以为:
- 输入框自动补全是一套接口;
- 搜索结果页是另一套完全不同的实现。
但 Odoo 在 hybrid_list() 里直接调用了 self.autocomplete(...),只是把 limit 放大到 500,然后分页展示。
这背后的含义很直接:
- 自动补全和结果页应该基于同一份搜索事实;
- 否则用户在下拉框里看到的内容,和点进搜索页看到的内容,会出现解释不一致。
所以 Odoo 的思路是:
先有统一搜索结果协议,再决定它是以下拉框还是列表页的形式消费。
这比“两个页面各写一套搜索逻辑”稳定得多。
七、这套设计对定制最重要的启发是什么
如果你想扩展 Odoo 网站搜索,最常见的误区有三个:
误区一:只改前端输入框
这样最多只是换 UI,真正搜什么、怎么渲染、能不能高亮,都没有解决。
误区二:在 controller 里硬塞自定义模型
短期能跑,但会把字段映射、图标、URL、摘要逻辑全写死,后面非常难维护。
误区三:盲目增强 fuzzy
搜索不是“召回越多越好”,而是错配越少越好。对 SKU、编码、短词来说,过强的 fuzzy 往往会适得其反。
正确方向通常是:
- 在模型层补
_search_get_detail(); - 明确
search_fields与fetch_fields; - 设计好
mapping; - 只在合适字段上启用
match=True; - 谨慎看待 fuzzy。
八、从产品体验角度,Odoo 网站搜索到底强在哪里
它不一定是“最聪明”的搜索引擎,但它非常像 Odoo 一贯的风格:
- 不追求花哨算法堆料;
- 重视统一协议;
- 重视多模型可复用;
- 重视前后台边界清晰;
- 重视展示结果的一致性。
所以它更像一个“可持续扩展的网站搜索框架”,而不是一个只服务某个页面的小功能。
对于内容型官网、产品型官网、混合型站点来说,这种架构比一时看起来聪明的前端小技巧更值钱。
最后一句
理解 Odoo 网站搜索,重点不在于“模糊匹配怎么拼”,而在于看懂这条链路:
搜索细节声明 → 搜索执行 → 结果映射 → 高亮与格式化 → autocomplete 与结果页复用。
把这个主链路看懂后,你再去做站内搜索优化、接自定义模型、调结果展示,才不会越改越乱。
DISCUSSION
评论区