Open edX 证书显示设置重构深度解析:certificates_display_behavior 与 certificate_available_date 的设计与实现
【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform
本篇文章基于 Open edX 平台(openedx-platform)的架构决策记录 005-cert-display-settings.rst,系统讲解课程证书可见性相关设置的演进、三种展示行为(end/end_with_date/early_no_info)的语义、Mongo 数据翻译层的规则,以及这些设计在xmodule、CourseDetails与证书服务中的落地实现。读完本文,你将理解课程团队在 Studio 中选择"证书显示"选项后,底层数据如何被校验、翻译与消费,并掌握should_certificate_be_visible与validate_certificate_settings的核心判定逻辑。
背景:三个"各自为政"的证书可见性设置
在重构之前,课程中直接影响证书可见性的设置共有三个,它们分别针对不同的使用场景独立开发,彼此之间缺乏一致且易理解的联动关系:
| 设置项 | 类型与含义 | 历史问题 |
|---|---|---|
certificate_display_date | 一个日期,用于决定证书何时可以向学习者展示,默认值为课程结束日期之后两天 | 语义笼统,未与其它设置联动 |
certificates_display_behavior | 字符串,决定学习者何时能看到自己证书的详情;取值end、early_no_info、early_with_info | 没有任何取值校验,字符串散落硬编码在整个平台各处 |
certificates_show_before_end | 字符串布尔值,决定学习者是否能在课程结束前看到证书 | 已弃用(deprecated),但仍被用来判断是否应向学习者展示证书 |
这三个设置并存导致的直接后果是:课程团队难以预判"学习者到底什么时候能看到证书",平台侧也缺少统一的校验入口来保证数据的合法性。
决策:重新构想前两项设置,保留弃用项直至移除
ADR 的决定是重新设计前两个设置,第三个设置维持弃用状态直到未来移除。具体而言:
certificates_display_behavior被更新为使用三个常量(由枚举CertificatesDisplayBehaviors定义,见 xmodule/data.py):end(Studio 显示文案:"End date of course")end_with_date(Studio 显示文案:"A date after the course end date")early_no_info(Studio 显示文案:"Immediately upon passing")
- 这些选项通过 Studio 中措辞更友好的下拉框(dropdown)供课程团队选择。
- 这些设置仅对 instructor-paced(教师主导进度)课程生效,self-paced(自主进度)课程不适用。
三种行为的具体语义
end("End date of course")—— 默认行为课程证书将在课程结束日期(course end date)到达后对学习者可见。从源码看,这也是certificates_display_behavior字段的默认值,见 xmodule/course_block.py:certificates_display_behavior = String( display_name=_("Certificates Display Behavior"), help=_( "This field, together with certificate_available_date will determine when a " "user can see their certificate for the course" ), scope=Scope.settings, default=CertificatesDisplayBehaviors.END.value, )early_no_info("Immediately upon passing")一旦学习者在课程中达到及格成绩(passing grade),证书立即对学习者可见,无需等待课程结束。end_with_date("A date after the course end date")证书一直不展示,直到certificate_available_date中设置的日期到来。这里有一条关键的联动规则:如果没有选择此选项,certificate_available_date在 Studio 中不会显示,其值会被置为None,该日期也不会对任何学习者的证书产生影响。
字段定义:certificate_available_date 与旧布尔字段
重构后的核心日期字段certificate_available_date定义在 xmodule/course_block.py:
certificate_available_date = Date( help=_("Date that certificates become available to learners"), scope=Scope.content )值得注意的是,certificate_available_date的scope是Scope.content,这意味着它属于课程内容本身而非课程运行设置(settings),这一点与certificates_display_behavior(Scope.settings)不同。
同时,旧布尔字段certificates_show_before_end保留了其定义但被标记为deprecated=True,见 xmodule/course_block.py:
certificates_show_before_end = Boolean( display_name=_("Certificates Downloadable Before End"), help=_( "Enter true or false. If true, students can download certificates before the course ends, if they've " "met certificate requirements." ), scope=Scope.settings, default=False, deprecated=True )而历史设置certificate_display_date的"默认值 = 课程结束 + 两天"行为,在 CourseBlock 初始化逻辑中仍有迹可循(xmodule/course_block.py 附近存在self.certificate_available_date = self.end + timedelta(days=2)的默认逻辑),印证了 ADR 中对该默认值的描述。
可见性判定:should_certificate_be_visible
证书下载链接是否展示给学习者,最终由 lms/djangoapps/certificates/utils.py 中的should_certificate_be_visible函数统一裁决:
def should_certificate_be_visible( certificates_display_behavior, certificates_show_before_end, has_ended, certificate_available_date, self_paced ): show_early = ( certificates_display_behavior == CertificatesDisplayBehaviors.EARLY_NO_INFO or certificates_show_before_end ) past_available_date = ( certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE and certificate_available_date and certificate_available_date < datetime.now(utc) ) ended_without_available_date = ( certificates_display_behavior == CertificatesDisplayBehaviors.END and has_ended ) return any((self_paced, show_early, past_available_date, ended_without_available_date))该函数接受 5 个参数,判定逻辑可以拆解为四类"可见"场景(满足其一即返回True):
self_paced:自主进度课程证书始终可见(这也是文档强调该设置"仅用于教师主导课程"的原因——self-paced 课程直接短路放行);show_early:行为为early_no_info,或旧字段certificates_show_before_end为真(兼容已弃用设置);past_available_date:行为为end_with_date且certificate_available_date已设置且早于当前时刻;ended_without_available_date:行为为end且课程已结束。
可以清晰地看到:certificates_show_before_end作为一个"或"条件被保留在了show_early判定中——这正是 ADR 中所说的"它虽已弃用,但仍被用于决定是否应向学习者展示证书"。
Mongo 数据翻译层:把不可信的 modulestore 数据变成合法组合
为什么要翻译而不是迁移
由于 Mongo/modulestore 中的数据"难以信任"(课程团队可能通过 XML 直接上传数据),且平台无法像 Django/RDBMS 那样通过一次 migration 强制所有存量数据符合新范式,因此决策引入了一个新的翻译层(translation layer):在基于 modulestore 数据构建CourseOverview模型或CourseDetails对象时,对certificate_available_date与certificates_display_behavior两个字段进行校验并翻译为合法组合。
需要特别强调:该翻译层不会把更新后的数据写回 modulestore。它只负责把"可能有问题的 modulestore 数据"转换为代码库其余部分可以正常使用的功能化数据。
三条翻译规则
- 若
certificates_display_behavior为early_no_info,则certificate_available_date被置为None; - 若
certificate_available_date已设置且certificates_display_behavior不是early_no_info,则certificates_display_behavior被改为end_with_date; - 若以上两条都不成立,则
certificate_available_date置为None、certificates_display_behavior置为end。
这三条规则在源码中由CourseDetails.validate_certificate_settings精确实现,见 openedx/core/djangoapps/models/course_details.py:
@classmethod def validate_certificate_settings(cls, certificate_available_date, certificates_display_behavior): """ Takes the stored values for certificate_available_date and certificates_display_behavior and verifies they work together in tandem per ADR: lms/djangoapps/certificates/docs/decisions/005-cert-display-settings.rst """ # "early_no_info" will always show regardless of settings if certificates_display_behavior == CertificatesDisplayBehaviors.EARLY_NO_INFO: return (None, CertificatesDisplayBehaviors.EARLY_NO_INFO) # If the date is set and "early_no_info" isn't if certificate_available_date: return (certificate_available_date, CertificatesDisplayBehaviors.END_WITH_DATE) return (None, CertificatesDisplayBehaviors.END)该方法返回一个二元组(校验后的 certificate_available_date, 校验后的 certificates_display_behavior),且 docstring 中明确引用了本文所依据的 ADR 文件路径,是"文档 — 实现"一一对应的典型样本。
完整翻译表
为便于速查,ADR 给出了完整的翻译表(简写:CAD =certificate_available_date,CDB =certificates_display_behavior):
| CAD in modulestore | CDB in modulestore | 校验后的 CAD | 校验后的 CDB |
|---|---|---|---|
<date> | "end" | <date> | "end_with_date" |
<date> | "end_with_date" | <date> | "end_with_date" |
<date> | "early_no_info" | null | "early_no_info" |
<date> | <无效选项> | <date> | "end_with_date" |
null | "end" | null | "end" |
null | "end_with_date" | null | "end" |
null | "early_no_info" | null | "early_no_info" |
null | <无效选项> | null | "end" |
这张表揭示了一个重要事实:翻译层能够容忍"任何非法字符串"——只要日期为空,无论 CDB 存的是什么(包括完全无效的选项),最终都会被归一为"end";只要日期非空且行为不是early_no_info,最终都会被归一为"end_with_date"。非法取值通过CertificatesDisplayBehaviors.includes_value(见 xmodule/data.py)可被识别,但即便识别为非法,翻译层也能安全兜底。
翻译层在下游的消费:CourseOverview 信号与 Programs 任务
翻译后的字段并非只服务于证书展示,还被下游多个模块消费:
- CourseOverview 变更信号:openedx/core/djangoapps/content/course_overviews/signals.py 中,当
certificate_available_date、certificates_display_behavior或课程结束日期发生变化时,会通过transaction.on_commit发送course_cert_date_change信号,通知 Credentials 服务(IDA)同步修正证书可见性。其中还处理了一个边界情况:当展示行为为end("End date of course")且课程 end date 被修改时,同样要触发信号以修正由 Credentials IDA 管理的证书可见性。 - Programs 进度任务:openedx/core/djangoapps/programs/tasks.py 根据
CourseOverview.certificates_display_behavior的值(end_with_date/end/early_no_info)分别计算 Program 级证书的预计可得日期,说明这一设置同样影响 Program(微硕士等项目)的证书时间线。
测试验证:翻译表的单元测试
翻译逻辑的正确性由专门的单元测试覆盖,见 openedx/core/djangoapps/models/tests/test_course_details.py。测试用例逐行对应翻译表的 8 种组合,例如:("end", 有日期)→ 校验为end_with_date;("end_with_date", 无日期)→ 校验为end;("early_no_info", 有日期)→ 日期被清空为None且行为保持early_no_info。证书侧还有 lms/djangoapps/certificates/tests/test_utils.py 与 lms/djangoapps/certificates/tests/test_webview_views.py 等测试验证should_certificate_be_visible与 webview 渲染行为,确保证书展示逻辑在视图层与工具函数层保持一致。
迁移与兼容性注意事项
- 数据不回写:翻译层是"读取时校验",modulestore 中的原始脏数据会被原样保留,不会因为翻译而被动修改。
- Studio 交互:只有选择
end_with_date时,certificate_available_date输入框才会在 Studio 中显示;选择其它选项时该日期被强制归一为None。 - 弃用字段的过渡:
certificates_show_before_end仍以"或"条件参与show_early判定,保证存量课程在迁移前的体验不回退,但新课程应完全依赖certificates_display_behavior。 - 适用范围:三种展示行为仅对 instructor-paced 课程有意义;self-paced 课程在
should_certificate_be_visible中直接返回可见。
小结
Open edX 通过这份 ADR 将三个相互割裂的证书可见性设置收敛为"一个枚举 + 一个可选日期"的清晰模型:枚举CertificatesDisplayBehaviors定义合法取值,certificate_available_date仅在end_with_date场景生效,validate_certificate_settings作为翻译层兜底一切历史脏数据,should_certificate_be_visible统一裁决展示时机。这套"文档定义语义、源码落实规则、测试锁定行为"的闭环,是理解 Open edX 证书系统乃至其整体架构决策流程(ADR 机制)的绝佳入口。
【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考