先说结论
website_helpdesk_slides 不是简单把 website_slides 课程列表嵌进 Helpdesk 页面。
把 models/helpdesk.py、models/slide_channel.py、models/slide_slide.py、controllers/main.py、模板和测试一起看,Odoo 企业版真正实现的是一条 “帮助中心学习入口” 的受控链路,而不是“给访客几门课自己点着看”。
它至少同时守住了 5 条边界:
- 团队范围边界:Helpdesk 团队可以显式指定允许出现在帮助中心里的课程集合;
- 访问权限边界:即使课程被团队选中,访客也未必看得到,仍要经过
public / members / connected可见性裁切; - 搜索边界:帮助中心里的课程搜索不是全站乱搜,而会按 team、tag、发布时间、课件问题文本联合裁切;
- 深链边界:搜索结果命中某个 lesson,不代表用户就能直接进 lesson 页面;如果不能预览或不是成员,Odoo 会把链接降级到课程首页;
- 入口路由边界:团队没配课程、只配 1 门课、配了多门课,前台入口会走 3 条完全不同的路由策略。
所以更准确的理解是:
Odoo 企业版的 Helpdesk 课程中心,本质上是把 eLearning 变成“先筛范围、再判权限、最后按前台上下文决定跳转”的自助分流入口,而不是在帮助页上塞几个培训链接。
这也是为什么这个能力放在企业版 website_helpdesk_slides,而不是社区版简单做个菜单按钮就算了。
一、第一层边界:团队课程范围,不是“站里有课就都给客户看”
最先要看的不是 controller,而是 models/helpdesk.py 里 helpdesk.team 的两个字段:
website_slide_channel_idswebsite_top_channels
其中 website_slide_channel_ids 的 help 写得很直白:
- 客户在帮助中心里,只会看到这里选中的课程;
- 如果这个字段留空,才表示“允许访问所有课程”;
- 如果你想把课程限制给内部人,也可以把课程本身设成 private / members / connected 等可见性。
这句说明非常关键,因为它直接推翻了一个常见误解:
Helpdesk 接课程,不是把整站 eLearning 自动暴露给访客,而是每个团队都可以圈出自己的课程子集。
为什么 Odoo 要先做“团队范围”这一步
因为 Helpdesk 帮助中心不是一个纯学习站,而是一个服务入口。
不同团队面对的问题完全不同,比如:
- 售前团队要放 onboarding、试用说明;
- 售后团队要放故障排查、FAQ 视频;
- 技术支持团队要放更偏配置和调试的课程;
- 某个区域网站团队可能只希望展示本地化的课程集合。
如果没有团队范围这道边界,结果通常会变成:
- 搜得到一堆无关课程;
- 客户在错误课程里兜圈子;
- 原本应该减少 ticket 的课程入口,反而增加分流噪声。
所以 website_slide_channel_ids 的意义不是“多选几个课程标签”,而是:
先把这个 Helpdesk 团队要拿来做自助分流的学习资产集合圈出来。
二、第二层边界:即使团队选中了课程,也不代表访客一定看得到
真正有意思的是 show_knowledge_base_slide_channel 的计算逻辑。
_compute_show_knowledge_base_slide_channel() 并不是简单判断:
- 团队启用了
use_website_helpdesk_slides - 并且
website_slide_channel_ids非空
它还额外检查了“当前用户是否对这些课程有读取权限”。
源码里的逻辑可以概括成两种情况。
情况 1:团队明确选了课程
这时系统会取:
- 团队选中的课程 ids
- 当前用户实际可读的课程 ids
然后求交集。
只有当:
- 团队功能已启用;
- 且交集非空;
帮助中心页面上的课程卡片才会显示。
情况 2:团队没有选课程
这时它退化成“全站范围”,只要当前用户至少能访问到 1 门课程,课程卡片就能显示。
这条逻辑解决了什么问题
它解决的是前台最尴尬的一种体验:
- 卡片明明显示“Take courses”;
- 点进去却什么都看不了,或者整页空白。
Odoo 的做法更克制:
如果你对团队课程集合一个都读不到,入口卡片干脆就不出现。
这点非常像企业系统而不是营销网站:宁可少展示,也不把用户带进死胡同。
三、第三层边界:帮助中心里的“热门课程”并不是简单列前五条
website_top_channels 也值得单独拿出来讲。
很多人看到页面上的 “Most popular / Quick Links” 会以为:
- 就是从课程里随便取 5 条;
- 或者后端手工拖排序。
其实不是。
1)如果团队没指定课程
系统会在所有已发布课程里按 total_views desc 搜索,再经过 filtered_channels() 二次过滤,只保留:
website_published = Truevisibility = public- 或
visibility = members and channel.is_member - 或
visibility = connected and 当前用户不是 public user
最后取前 5 个。
2)如果团队指定了课程
系统不会去全站取热门,而是只在这个团队自己的课程子集里:
- 先做同样的可见性过滤;
- 再按
total_views倒序排序; - 最后取前 5 个。
测试 test_top_channels 也明确验证了这个行为:
- 课程 view 越多,越容易进
website_top_channels; - 返回顺序确实按热度降序,而不是创建时间顺序。
为什么这件事重要
因为帮助中心里的“推荐课程”如果只按后台配置顺序列,会有两个问题:
- 不反映真实使用价值:最常被客户看的课程,未必最早创建;
- 可能推荐不可访问课程:如果不再做一次权限过滤,前台推荐就会把用户引向不可见内容。
而 Odoo 的实现说明,它真正想做的是:
在当前团队可见课程集合里,优先推荐“已经被证明显著有用”的课程。
这和纯 CMS 式的手工推荐不是一个思路。
四、搜索为什么不是“搜课程名”这么简单,而是 team / tag / lesson / FAQ 联动
website_helpdesk_slides 最值钱的地方,其实在搜索。
它不是只把 slide.channel 暴露到帮助中心里,而是同时把:
slide.channelslide.slide
都接进了帮助中心搜索体系。
1)搜课程时:先受 team 限定,再受 tag 限定
slide.channel._search_get_detail() 里,如果请求参数里带了 helpdesk:
- 系统会先解出当前 helpdesk team;
- 如果该团队配置了
website_slide_channel_ids,domain 就会强行加上id in team_channels; - 如果请求还带了
tag,再叠加('tag_ids.name', 'ilike', tag)。
也就是说,帮助中心里的课程搜索不是:
- 全站课程模糊搜索
而是:
- 先在团队准入的课程池里搜,再看 tag 是否命中。
2)搜课件时:不仅搜标题,还搜问题文本
slide.slide._search_get_detail() 更有意思。
它额外做了几件事:
- 强制排除
is_category=True的目录型 slide; - 如果有
max_date,限制date_published >= max_date; - 如果有
tag,同时匹配: slide.tagchannel_id.tag_ids- 如果团队配置了课程范围,再加上
channel_id in team_channels - 最后把搜索字段扩展到:
channel_id.tag_ids.nametag_ids.namequestion_ids.question
最后这个 question_ids.question 非常值得注意。
它意味着帮助中心搜课件时,不只是搜:
- 课件标题
- 课件说明
还会搜课程中的问答题 / quiz question 文本。
这背后是什么产品思路
不是“让网站搜索更大”,而是:
用户往往不是记得课程名,而是记得自己遇到的问题表达。
比如用户搜:
- invoice policy
- serial number
- password reset
- portal access
命中的,可能不是课程标题,而是课程里的某个 quiz / FAQ 问题句子。
这就让 Helpdesk 课程中心不只是一个“被动浏览课程目录”的入口,而是更像:
- 一个带教学材料的自助检索层。
五、tag 搜索还有个容易忽略的细节:建议词并不严格按团队裁切
_helpcenter_filter_tags() 里,模块会在 slides 搜索类型下,把:
slide.tagslide.channel.tag
都搜出来,再统一转成小写标签词,提供给帮助中心过滤层使用。
这里一个很细的边界是:
- 它判断是否要启用 slides 标签过滤,会看
show_knowledge_base_slide_channel; - 但真正取 tag 数据时,并没有再按 team 的课程子集做 domain 裁切。
这意味着什么?
意味着帮助中心的 slides 标签建议,更像是“全局课程标签词典”,而不是“当前团队可见标签全集”。
这会带来什么实现含义
从产品上看,这么做大概率是为了:
- 保持标签过滤能力足够丰富;
- 避免为每个 team 动态重算 tag 索引,增加复杂度。
但从实施和二开的角度看,你要知道它不是一条严格 team-scoped 的标签链路。
也就是说:
- 用户点某个 tag 后,真正结果页仍会被 team domain 和权限 domain 再次裁切;
- 但标签候选词本身,不一定完全只来自这个 team 的课程集合。
这就是很典型的企业产品折中:
候选过滤词可以宽一点,但最终内容命中必须严一点。
六、最像“企业版味道”的地方:搜索结果命中 lesson,不一定允许你直达 lesson
controllers/main.py 里 _format_search_results() 这段,几乎是整模块最值得讲的设计。
当搜索类型是 slides 时,如果命中的是 slide.slide 课件,Odoo 返回的 url 不是一刀切:
- 如果
slide.is_preview为真,或者用户已经是slide.channel成员,链接就指向slide.website_absolute_url; - 否则,链接会退回到
slide.channel.website_absolute_url。
这是一个很高级的“深链降级”设计。
为什么不能一律跳课件详情页
因为搜索结果和访问权限不是一回事。
用户确实可能搜到了某个具体 lesson,但如果:
- 这个 lesson 不能预览;
- 用户又不是课程成员;
那直接把他送进 lesson 页面,要么打不开,要么体验很割裂。
Odoo 的处理更像一条引导漏斗
它承认:
- 你搜中的确实是这个 lesson;
但同时也承认:
- 你当前的访问层级,最多只能先到课程首页。
于是结果页里:
- 标题仍显示命中的课件;
- 但点击后先带你去课程页;
- 在课程页里,再由课程可见性、预览设置、加入状态来决定下一步。
这背后的产品逻辑很清楚:
搜索负责帮用户“找到方向”,但不能绕过课程本身的访问控制。
这和很多站内搜索“命中即硬跳详情页”的粗暴做法相比,明显更稳。
七、结果排序也不是默认顺序,而是按“可帮助性”打分
_format_search_results() 不只是生成模板,还给 slides 结果算了分数。
课件分数
对于 slide.slide:
score = total_views + comments_count + likes - dislikes
也就是说,一个课件在帮助中心里靠前,不只是因为看的人多,还因为:
- 有互动;
- 被点赞;
- 且差评会拉低权重。
课程分数
对于 slide.channel:
score = total_views + total_votes
这说明课程层面更看整体热度和投票反馈。
为什么这不是普通内容搜索的排序逻辑
因为 Helpdesk 里的课程搜索,目标不是找“语义最像”的内容,而是找:
- 最可能真能帮用户解决问题的内容。
从这个角度看:
- 阅读量说明它经常被需要;
- 评论和点赞说明它对用户有帮助;
- dislike 则提示它可能误导或质量一般。
这套排序非常符合“帮助中心”而不是“内容库”的定位。
八、入口路由为什么要区分“0 门 / 1 门 / 多门”三种情况
/helpdesk/<team>/slides 这条路由也不是简单 render 一个课程列表页。
helpdesk_slides_channel() 分了 3 个分支。
情况 1:没有 team 或 team 没配课程
直接重定向到:
/slides
也就是退回站点默认课程首页。
情况 2:团队只配了 1 门课程
直接 302 跳到这门课的详情页:
/slides/<slug(channel)>
情况 3:团队配了多门课程
这时才渲染专门的 helpdesk_courses 页面,并把 channels 替换为团队那组课程。
这条路由拆分的意义是什么
它对应三种完全不同的前台心智。
没配课程:不要装作自己有课程中心
既然团队没有独立课程配置,就退回全站课程入口,不额外制造一个“空的团队课程页”。
只配 1 门课:不要逼用户多点一步
如果帮助中心本来就是想把客户导向这唯一一门自助课程,那最省心的做法就是直接进去,不要再让用户先看一个只有一张卡片的列表页。
配了多门课:这时才需要一个真正的团队课程页
因为用户需要在几门受控课程里做选择。
所以这条路由本质上是在做一件很实用的事:
根据团队课程规模,动态选择“全站入口 / 单课直达 / 多课列表”三种不同的信息架构。
这不是简单的 URL 跳转优化,而是前台分流体验优化。
九、模板层也在强化“帮助中心入口”而不是“完整学习站替代品”
views/helpdesk_templates.xml 也泄露了产品意图。
1)帮助中心卡片只展示少量热门课
卡片区只列:
- Most popular / Quick Links
- 最多 5 个课程
- 再配一个
Take courses按钮
这说明它的目标不是把 Helpdesk 页面做成完整 LMS,而是给一个:
- 轻量自助入口
- 快速导流入口
- 在提交 ticket 前的先行分流入口
2)搜索结果模板会展示“课程 / lesson 元数据”
比如:
- views
- likes
- comments
- members
- lessons / hours
- tags
这意味着课程结果不只是一个标题列表,而是在搜索层就开始给用户“这是不是值得点进去”的判断依据。
3)按钮和链接默认 target="_blank"
帮助中心点课程时,官方很多链接直接新开页。
这说明 Odoo 不想让用户在学习内容和 Helpdesk 入口之间完全互相覆盖:
- 你可以去看课程;
- 但原来的帮助中心上下文还在。
这很符合真实支持场景:
- 用户可能先打开课程看一眼;
- 看完不行,再回来提交 ticket。
十、对实施与二开的启发
1. 不要把 Helpdesk 课程中心理解成“把 LMS 菜单嵌到帮助页”
真正的关键是:
- team 范围
- 权限过滤
- 搜索裁切
- 深链降级
- 单/多课程路由
2. 如果你的团队想做“售后自助化”,优先先设计课程子集
比起把几十门课程全塞给客户,圈出少量最有效的分诊课程,转单率通常更好。
3. 搜索命中 lesson 时,不要轻易绕过官方的 URL 降级逻辑
很多二开喜欢“命中哪一页就跳哪一页”,但对受限课程来说,这常常会制造打不开的死链体验。
4. 热门课程推荐最好保留“热度 + 可访问性”双重筛选
只看运营手工排序,往往会把真正帮得上忙的课程埋掉。
5. 如果你要做更严格的团队隔离,优先检查 tag 建议词逻辑
因为当前实现里,结果是强裁切的,但 tag 候选词并不是完全 team-scoped。
一句话记忆法
Odoo 企业版
website_helpdesk_slides不是“在 Helpdesk 上挂课程”,而是“先按团队圈课程、再按权限过滤、搜索时按可帮助性排序,并根据访问层级决定是直达 lesson 还是退回课程页”。
参考源码
enterprise/website_helpdesk_slides/models/helpdesk.pyenterprise/website_helpdesk_slides/models/slide_channel.pyenterprise/website_helpdesk_slides/models/slide_slide.pyenterprise/website_helpdesk_slides/controllers/main.pyenterprise/website_helpdesk_slides/views/helpdesk_templates.xmlenterprise/website_helpdesk_slides/views/helpdesk_views.xmlenterprise/website_helpdesk_slides/tests/test_helpdesk_slides.py
DISCUSSION
评论区