先说结论
Odoo eLearning 的课程页看起来像内容站,但底层并不是“文章列表 + 视频列表”这么简单。
从 /home/ubuntu/odoo-temp/addons/website_slides/models/slide_channel.py 和 addons/website_slides/models/slide_channel_partner.py 来看,真正支撑课程运营的是两层对象:
slide.channel:课程本身,负责可见性、入课策略、内容统计和规则slide.channel.partner:课程成员关系,负责邀请、完成率、成员状态和下一课
再加上 Karma 字段、完成邮件和 prerequisite 逻辑,Odoo 实际上做的是一套轻量的学习增长引擎。
所以它真正解决的问题不是“用户能不能打开课程页”,而是:
一个人如何被邀请进课程、如何逐步推进、何时算完成,以及完成后系统如何继续激励他。
第一层:为什么课程不是内容容器,而是规则容器
slide.channel 上有很多人第一次不会在意,但其实很关键的字段:
visibilityenrollenroll_group_idspartner_idscompletedcompletionprerequisite_channel_idskarma_gen_channel_finishcompleted_template_id
如果它只是一个内容容器,这么多“成员和规则”字段本来没必要放在课程对象上。
这说明 Odoo 的产品判断很明确:
课程不只是内容集合,而是一个有准入机制、有推进逻辑、有激励结果的学习空间。
这和很多“把知识库页包装成课程页”的做法差别很大。
在 Odoo 里,你不仅要管:
- 课里放了什么内容
还要管:
- 谁能看
- 谁算成员
- 谁是被邀请未加入
- 谁已经进行中
- 谁已经完成
- 完成后系统要不要给奖励或发邮件
第二层:为什么课程成员必须有独立模型
slide.channel.partner 是这套设计的核心之一。
它不是一张普通中间表,而是一张带业务状态的成员关系表。
上面直接挂着:
member_statusinvitedjoinedongoingcompletedcompletioncompleted_slides_countlast_invitation_dateinvitation_linknext_slide_id
并且有唯一约束:
- 同一个
channel_id + partner_id只能有一条成员关系
这套设计说明官方不是把“是否在课里”看成一个布尔值,而是看成一条持续演化的成员生命周期。
这很重要,因为课程运营最怕的就是以下状态全挤在一起:
- 被邀请但还没进来的人
- 进来了但没开始的人
- 开始了但没学完的人
- 学完了的人
如果没有独立成员模型,你只能用一堆临时字段拼凑,最后连转化漏斗都看不清。
第三层:邀请链接为什么要按人和课程算哈希
_compute_invitation_link() 会基于:
partner_idchannel_id
生成邀请链接,并用 _get_invitation_hash() 做 HMAC。
这意味着 Odoo 的邀请不是泛用分享链接,而是面向具体成员的入课凭证。
它带来的好处很现实:
1. 邀请可以追踪到人
谁被邀请、谁还没进来,不会因为一个公共链接被无限转发而失真。
2. 课程可做 invitation-only 模式
当 visibility = members 且 enroll = invite 时,课程可以真正变成“仅成员可见”。
3. 后续状态变迁有基础
只有先有成员记录,系统才能知道这个人是 invited、joined 还是 ongoing。
这就是为什么课程邀请在 Odoo 里不只是“发一封邮件”,而是成员关系建模的一部分。
第四层:完成率为什么不只是前端进度条
_recompute_completion() 是最值得研究的方法之一。
它会基于 slide.slide.partner 的完成记录,去统计当前成员在本课程下:
- 已完成内容数
- completion 百分比
- 是否达到 total_slides
- 对应应该落在哪个
member_status
状态切换逻辑也很清晰:
- 0%:
joined - 0~100%:
ongoing - 100%:
completed
而且一旦从未完成变成完成,会触发:
_post_completion_update_hook(completed=True)_send_completed_mail()
如果后续因内容变化或回退导致不再完成,还能反向撤销完成态与相关 Karma。
这说明 Odoo 的“完成率”不是 UI 装饰,而是驱动成员生命周期和激励逻辑的核心指标。
第五层:为什么“下一课”推荐要在 SQL 层精确计算
_compute_next_slide_id() 用了一段非常直接的 SQL:
- 只看当前课程已发布、激活、非分类的 slide
- 排除当前成员已经完成的内容
- 按 sequence、id 取第一条未完成内容
这段逻辑看上去简单,但很有产品味道。
它表达的是:
课程推进不应该让学员自己在内容堆里找方向,系统应该永远知道他下一步最合理的内容是什么。
很多在线学习产品表面上有“继续学习”按钮,底层却只是在前端缓存最近打开项。 Odoo 这里则是用成员完成记录严格算出“下一课”。
这就让课程具备了真正的引导性,而不只是内容目录。
第六层:Karma 机制为什么对学习产品很重要
slide.channel 和 slide.channel.partner 里有两类 Karma 字段:
互动门槛
karma_reviewkarma_slide_commentkarma_slide_vote
完成奖励
karma_gen_channel_rankkarma_gen_channel_finish
这说明 Odoo 想要的不是被动看课,而是社区式学习参与。
尤其 _post_completion_update_hook() 会在成员完成课程时给对应用户加 Karma,若完成状态被撤销则反向扣回。
这套机制很值钱,因为它把学习行为和社区声誉打通了:
- 学完课程的人更有资格评论、投票、参与互动
- 高价值用户不是靠人工授予,而是靠行为累计
对于企业培训、伙伴学院、客户教育平台来说,这比单纯统计观看次数要有运营价值得多。
第七层:课程可见性和入课策略为什么要配合看
visibility 和 enroll 是最容易被误配的一组字段。
源码里甚至有约束:
- 当
visibility = members时,enroll必须是invite
这条规则的意思很明确:
如果你把课程设置成“只有成员可见”,那入课方式就不能还是完全开放。
这类约束看似严格,实际上是在帮你防止配置自相矛盾。
因为学习业务里最常见的翻车场景就是:
- 想做内训课程,却留着公开入课
- 想做合作伙伴认证,却让任何登录用户都能直接加入
- 想靠邀请管理学习路径,却没有成员准入边界
Odoo 在模型层提前把这些矛盾压住了。
和“会员学习进度”那篇最不一样的地方
如果说之前那篇更偏“成员与 next slide 的主链”,这次更值得看的,是课程为什么会长成一套状态机 + 激励器。
重点不只是“你学到哪里了”,而是:
- 你怎么被纳入课程
- 课程是否允许你看
- 你现在是哪种成员状态
- 系统接下来要推你去哪一课
- 你完成后是否会收到邮件、获得 Karma、解锁更多互动权限
这才是 Odoo eLearning 和普通内容站之间真正的鸿沟。
最后一句话
Odoo 在线课程最容易被低估的地方,不是视频播放,不是文章管理,而是它把成员关系、学习推进和社区激励做成了同一套引擎。
所以理解 website_slides 最好的方式不是“课件系统”,而是:
它是一套以课程成员为中心的学习运营系统。
DISCUSSION
评论区