先说结论
在 Odoo 里,“当前网站是谁”并不是一个随手取出来的配置值。
website.models.website 里的 get_current_website() 实际按一条很明确的优先级链路来判定:
- 先看 session 里有没有
force_website_id; - 再看 context 里有没有
website_id; - 前台请求时,再按当前请求 host 去匹配网站 domain;
- 如果还找不到,且允许 fallback,才退回数据库里第一条网站记录。
所以多网站的本质不是“一个域名对应一个页面主题”,而是:
Odoo 先决定当前请求属于哪个 website,再让模板、规则、语言、资源、搜索域和缓存都沿着这个 website 上下文继续走。
这就是为什么多网站问题一旦出错,表面看像域名问题,深层常常其实是上下文问题。
一、为什么 current website 是整条网站链路的起点
源码里最关键的方法就是:
get_current_website()_get_current_website_id(domain_name, fallback=True)
前者决定“现在是谁”,后者决定“用什么 host 去命中谁”。
一旦 current website 确认下来,后面的很多能力都会跟着变:
website_domain()会把查询限制到website_id in [False, current_website];- QWeb 模板缓存会把
website_id纳入 key; ir.rule的 domain cache 也会把website_id纳入 key;- 当前网站的语言、公司、logo、社交图、CDN 策略都会接管后续渲染。
所以它不是“前台首页该显示哪个主题色”的小变量,而是网站请求的根上下文。
二、优先级为什么先看 session,再看 context,再看域名
很多人直觉上会以为:
- 访问哪个域名,就一定是哪个网站。
但 Odoo 源码不是这么简单。
1. session 强制网站
如果 session 里有 force_website_id,get_current_website() 会优先直接返回它。
这说明 Odoo 允许“临时强制切站”。这很适合:
- 网站编辑器预览;
- 后台切换目标站点;
- 某些带上下文的管理动作。
也就是说:
域名不是永远最高优先级,用户会话可以临时盖过域名判断。
2. context 指定网站
如果没有 session 强制,再看 context['website_id']。
这个设计很重要,因为很多后台逻辑、模板渲染、RPC、批量查询并不一定处在完整前台 HTTP 请求里,但仍然需要明确“以哪个网站视角”来读数据。
3. 前台请求才按 host 匹配 domain
只有 session 和 context 都没给出答案时,Odoo 才去看 request host。
这意味着域名命中更像:
- 默认推断机制,
- 而不是唯一真理。
三、域名命中为什么不是简单字符串相等
_get_current_website_id() 里做得比表面细很多。
它会处理:
- host 中可能带端口;
- 网站 domain 可能带 scheme;
- 国际化域名可能出现 punycode / unicode 两种表示;
ilike搜到的结果还要再做一次精确过滤,避免子域名误命中。
源码里先搜索候选网站,再通过 _filter_domain() 做精确匹配;如果带端口匹配不到,还会再尝试忽略端口。
这背后的设计很务实:
- 先尽量把候选集找出来;
- 再做“真正属于这个 host 吗”的二次确认;
- 最后才考虑 fallback。
所以如果你在多网站环境里遇到:
- 同一个网站在
:80正常、:8069不正常; - Unicode 域名和 punycode 表现不一致;
问题往往就出在这一层,而不只是 Nginx 配置。
四、为什么 website_domain() 这么关键
website_domain() 返回的是:
website_id in [False, 当前网站]
这句很短,但它决定了 Odoo 多网站不是“绝对隔离”,而是“共享 + 定向覆盖”的模型。
什么意思?
website_id=False的内容,视为所有网站都能看见;website_id=当前网站的内容,只属于当前网站;- 当前网站查询时,会把这两类一起纳入范围。
这比“每条数据必须绑定一个网站”灵活得多。
因为真实场景里常常有两类内容:
- 全站共享内容;
- 某个站点定制内容。
Odoo 用一个 domain 就把这两类统一起来了。
五、为什么很多多网站 bug 看起来像缓存串站
源码里 ir.rule 的 _compute_domain_keys() 会追加 website_id,ir.qweb 的模板缓存 key 也会把 website_id 算进去。
这说明 Odoo 非常清楚:
如果缓存不按 website 维度分开,多网站就会出现最典型的串站问题。
比如:
- A 站模板被 B 站复用;
- A 站菜单跑到 B 站;
- A 站文章列表出现在 B 站;
- A 站语言/资源缓存污染 B 站。
所以当你排查“为什么同样模板在一个站正常、另一个站不正常”时,不要只看 XML 继承,也要想到:
- 当前请求到底命中了哪个 website;
- 当前渲染是否真的带上了
website_id上下文; - 某个自定义查询是否忘了补
website_domain()。
六、最容易误解的 4 件事
1. 以为多网站只靠域名字段驱动
不对。session 与 context 都可以抢在域名前面决定当前网站。
2. 以为 current website 只影响页面外观
不对。它会影响规则、模板缓存、语言、公司、资源、搜索域。
3. 以为数据隔离就是“按 website_id 严格相等”
不对。Odoo 默认允许 website_id=False 作为全站共享数据。
4. 以为 backend 下也一定能自动推断网站
不对。源码明确区分前台请求和非前台请求;backend 场景经常要靠 context 明确指定。
七、实战排错顺序
多网站串站、取错站、模板异常时,我更推荐按这个顺序查:
- 当前请求的 host 到底是什么;
- session 里有没有
force_website_id; - context 里有没有残留
website_id; - 目标数据查询是否用了
website_domain(); - 自定义缓存、模板或 snippet 是否把
website_id纳入隔离维度。
这个顺序比一上来怀疑 DNS、Nginx 或模板继承更高效。
一句话记忆
Odoo 多网站不是“域名 -> 页面”的薄映射,而是“session / context / host 先判定 current website,再让规则、缓存、模板和数据范围一起沿着 website 上下文继续走”。
DISCUSSION
评论区