Open edX 课程证书模块(Certificates App)全解析:从数据模型、生成流程到证书管理实战
【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform
导读
Open edX 平台的课程证书功能并非只存在于单一目录,而是横跨 LMS、Studio(CMS)与 Credentials 等多个子系统。本文以lms/djangoapps/certificates为核心,系统讲解该应用的职责边界、核心数据模型、证书状态机、证书生成与撤销的完整调用链,并结合cms/djangoapps/contentstore/views/certificates.py与openedx/core/djangoapps/credentials等关联代码,帮助你理解"证书配置 → 证书生成 → 证书展示 → 证书撤销"的端到端实现,掌握通过源码定位证书问题、通过管理命令批量发证的能力。
仓库定位:本应用当前状态为Maintenance(维护中),即功能已基本稳定,新功能开发优先考虑 Credentials 等外部服务,本模块以维护为主。
Open edX 课程证书生成流程架构图
一、应用职责:证书的"创建与管理中枢"
根据 lms/djangoapps/certificates/README.rst,Certificates 应用负责创建和管理课程证书(course certificates),其职责覆盖三个层面:
- 证书设置(certificate settings)——课程的证书启用开关、显示行为、生效日期等配置;
- 课程证书模板(course certificate templates)——Web 端自定义证书的 Django 模板及其配套资源;
- 已生成的学习者课程证书(generated learner course certificates)——每位学习者在某次课程运行(course run)下实际获得的证书记录。
同时,该应用提供了两类关键数据模型能力:证书无效化(invalidating certificates)与白名单管理(managing the allowlist)。
1.1 功能散布在多个子系统
证书相关功能分散在多处,理解"哪里改什么"是排查问题的第一步:
| 位置 | 职责 |
|---|---|
lms/djangoapps/certificates | 核心:证书数据模型、生成逻辑、任务、信号、API 与 Web 展示 |
openedx/core/djangoapps/credentials | 学习者的项目证书(Program Certificates)记录系统(system of record),与课程证书联动 |
cms/djangoapps/contentstore/views/certificates.py | Studio 侧证书配置的 CRUD API(创建/编辑/删除证书及签名人) |
| 各前端静态模板 | 证书列表页(Backbone 应用)、证书 Web 视图等 UI |
值得特别说明的是Credentials 服务:它是学习者项目证书的权威记录(system of record)系统。在本仓库中对应openedx/core/djangoapps/credentials;课程证书在生成或状态变化时会通过COURSE_CERT_CHANGED、COURSE_CERT_AWARDED等信号通知 Credentials,由其判断是否可以授予项目级证书。
二、核心数据模型(models.py)
证书模块的数据模型全部定义在lms/djangoapps/certificates/models.py,下面按"学习者证书 → 例外与撤销 → 配置 → 模板"四类介绍。
2.1 GeneratedCertificate:学习者证书主模型
这是全模块最核心的模型,一条记录代表"一个学生在一次课程运行中获得的证书"。关键字段:
| 字段 | 说明 |
|---|---|
user | 关联用户 |
course_id | 课程运行 key(CourseKeyField) |
verify_uuid | 证书唯一标识(32 位 UUID),用于证书校验 URL |
grade | 生成证书时的成绩快照(注意:该成绩不会随课程成绩变化更新,权威成绩应使用 PersistentCourseGrade) |
key | 证书标识符(PDF 证书时代使用) |
distinction | 是否"荣誉"通过(当前未使用) |
status | 证书状态,见下文状态机 |
mode | 课程运行模式(verified、honor、audit、masters 等) |
name | 证书上显示的用户姓名(PII 字段) |
download_uuid/download_url/error_reason | 遗留的 PDF 证书字段,已不再使用(见 ADR 008-certificate-model-remnants.rst) |
模型内置了三个 Manager:
objects:默认管理器,包含不合格证书(如 audit 模式的新证书);eligible_certificates:自动排除audit_passing、audit_notpassing状态,防止意外给未注册证书模式的学生发证;eligible_available_certificates:在 eligible 基础上,仅返回存在对应CourseOverview的证书。
# 使用示例:查询某学生在某课程的证书 cert = GeneratedCertificate.certificate_for_student(student, course_id)2.2 例外与撤销类模型
- CertificateAllowlist(证书白名单):记录某个课程运行下被"特批"发证的学生,
allowlist=True时生效;get_certificate_allowlist(course_id, student)会返回包含id/user_id/user_name/user_email/course_id/created/notes/certificate_generated的字典列表。 - CertificateInvalidation(证书无效化):记录对某张证书的无效化操作,
active=True表示当前生效;提供get_certificate_invalidations()与has_certificate_invalidation(student, course_key)查询接口。 - CertificateDateOverride(证书日期覆盖):手动将某张证书的展示日期覆盖为指定日期(
date字段,时间建议设为 00:00:00),并记录reason与overridden_by;保存/删除时会通过COURSE_CERT_CHANGED信号通知 Credentials 刷新。
2.3 配置类模型
- CertificateGenerationConfiguration:全局开关,控制"自助生成证书"功能(关闭时进度页隐藏"生成证书"按钮);
- CertificateGenerationCourseSetting:按课程配置,包括
self_generation_enabled(允许学生自助生成)、language_specific_templates_enabled(多语言模板)、include_hours_of_effort(展示学时投入,需 Discovery 提供weeks_to_complete与max_effort); - CertificateHtmlViewConfiguration:HTML 证书视图的静态上下文参数(JSON),支持
default与按 mode 覆盖; - CertificateGenerationCommandConfiguration / ModifiedCertificateTemplateCommandConfiguration / PurgeReferencestoPDFCertificatesCommandConfiguration:分别对应三条管理命令的参数存储(可配合
--args-from-database使用); - ExampleCertificateSet / ExampleCertificate:示例证书,用于在启用自助发证前验证模板与生成链路是否正常(示例证书不关联真实用户,姓名固定为
John Doë)。
2.4 模板类模型
- CertificateTemplate:自定义 Web 证书模板,字段包括
template(Django 模板 HTML)、organization_id、course_key、mode、language、is_active。唯一性约束为(organization_id, course_key, mode, language); - CertificateTemplateAsset:模板配套资源(图片、CSS 等),上传至
MEDIA_ROOT/certificate_template_assets/<id>/,通过asset_slug在模板中引用。
三、证书状态机(data.py)
所有状态常量定义在CertificateStatuses类中。当前代码实际会写入的状态有四种:
| 状态 | 含义 | 设置入口 |
|---|---|---|
downloadable | 已授予且可下载 | generation.py中的生成流程 |
notpassing | 未达到通过成绩 | GeneratedCertificate.mark_notpassing() |
unavailable | 证书已被无效化 | GeneratedCertificate.invalidate() |
unverified | 身份验证未通过 | GeneratedCertificate.mark_unverified() |
其余状态(generating、error、requesting、deleted、audit_passing等)为 PDF 证书时代的遗留值,仅用于兼容历史数据。
两个派生常量值得注意:
PASSED_STATUSES = (downloadable, generating)——判断学生是否通过课程;NON_REFUNDABLE_STATUSES = (downloadable, generating, unavailable)——用于退款资格判断(is_refundable_status)。
GeneratedCertificate.invalidate()/mark_notpassing()/mark_unverified()最终都汇聚到私有方法_revoke_certificate(),它会清空download_uuid与download_url、更新状态,随后依次:
- 发送
COURSE_CERT_REVOKED信号(用于联动撤销项目证书); - 发送 Open edX 事件
org.openedx.learning.certificate.revoked.v1(CERTIFICATE_REVOKED); - 若撤销前状态为
downloadable,额外发出edx.certificate.revoked埋点事件。
四、证书生成流程:从信号到 Celery 的完整链路
证书生成采用"信号触发 → 资格校验 → 异步任务"的架构,相关代码集中在 generation_handler.py 与 tasks.py。
4.1 触发信号(signals.py)
证书生成的触发源包括:
COURSE_GRADE_NOW_PASSED:学生成绩变为通过(自动发证模式下);COURSE_GRADE_NOW_FAILED:成绩变为不通过(标记notpassing,白名单学生除外);LEARNER_SSO_VERIFIED/PHOTO_VERIFICATION_APPROVED/IDV_ATTEMPT_APPROVED:身份验证通过后补发证书;ENROLLMENT_TRACK_UPDATED:选课模式变为可发证模式(如升级到 verified);post_save(CertificateAllowlist):学生被加入白名单后自动尝试发证。
4.2 资格校验(generation_handler.py)
入口函数generate_certificate_task(user, course_key, generation_mode, delay_seconds)会先判断学生是否在白名单上,分流到两条路径:
- 白名单路径
generate_allowlist_certificate_task():不要求通过成绩,但仍需满足公共校验; - 常规路径
_generate_regular_certificate_task():额外要求非 CCX 课程、非 Beta 测试者、成绩通过。
两条路径共享公共校验_can_generate_certificate_common(),其检查项依次为:
- 是否在证书无效化列表(
CertificateInvalidation.has_certificate_invalidation)——是则拒绝; - 是否存在选课记录(enrollment_mode 为空则拒绝);
- 选课模式是否可发证(
modes_api.is_eligible_for_certificate); - 若课程强制身份验证(
ENABLE_CERTIFICATES_IDV_REQUIREMENT)且用户未验证,且模式不属于NON_VERIFIED_MODES(honor、no-id-professional 等)——则拒绝; - 已有证书状态是否允许生成(
downloadable且当前模式未升级为可发证模式则拒绝); - 课程是否存在
CourseOverview; - 是否已启用 HTML 证书(
has_html_certificates_enabled)。
校验通过后调用_generate_certificate_task(),其中会触发 Open edX 过滤器CertificateCreationRequested(类型org.openedx.learning.certificate.creation.requested.v1),允许插件在生成前拦截(抛出PreventCertificateCreation则中止)。
4.3 异步任务执行(tasks.py / generation.py)
# tasks.py 中的任务定义(节选) @shared_task(base=LoggedPersistOnFailureTask, bind=True, default_retry_delay=30, max_retries=2) def generate_certificate(self, **kwargs): student = User.objects.get(id=kwargs.pop("student")) course_key = CourseKey.from_string(kwargs.pop("course_key")) status = kwargs.pop("status", CertificateStatuses.downloadable) enrollment_mode = kwargs.pop("enrollment_mode") course_grade = kwargs.pop("course_grade", "") generation_mode = kwargs.pop("generation_mode", "batch") generate_course_certificate(user=student, course_key=course_key, status=status, enrollment_mode=enrollment_mode, course_grade=course_grade, generation_mode=generation_mode)任务参数说明:
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
student | 是 | — | 学生用户 ID |
course_key | 是 | — | 课程运行 key |
status | 否 | downloadable | 目标证书状态 |
enrollment_mode | 是 | — | 选课模式 |
course_grade | 否 | '' | 课程成绩 |
generation_mode | 否 | batch | 事件模式:self(用户自助)/batch(批量等) |
任务默认延迟CERTIFICATE_DELAY_SECONDS = 2秒执行(防止调用方在 post-save 阶段仍有未提交改动),失败时最多重试 2 次、间隔 30 秒。
真正落库的逻辑在 generation.py 的generate_course_certificate():
- 若已存在证书则复用其
verify_uuid(保证学习者证书 URL 不变),否则新生成 UUID; - 通过
GeneratedCertificate.objects.update_or_create(user, course_id, ...)创建或更新记录; - 若状态为通过类状态(
downloadable/generating),发出edx.certificate.created埋点事件; - 若状态为
unverified,调用mark_unverified()落库。
同时,GeneratedCertificate.save()的重写会在保存后发出COURSE_CERT_CHANGED、CERTIFICATE_CHANGED事件,以及(通过状态时)COURSE_CERT_AWARDED、CERTIFICATE_CREATED事件,供 Credentials 与事件总线消费——这正是架构图中"信号 → Celery → 数据库 → Credentials/Event Bus"的实现对应。
五、Studio 侧的证书配置 API
证书的配置(而非具体某张证书)存储在课程 Modulestore 的course.certificates字段中,由 Studio 管理,对应 cms/djangoapps/contentstore/views/certificates.py。其数据契约如下:
course.certificates: { 'certificates': [ { 'version': 1, // 数据契约版本 'id': 12345, // 自动生成的标识符 'name': 'Certificate 1', 'description': 'Certificate 1 Description', 'course_title': 'course title', 'signatories': [ { 'id': 24680, // 自动生成的标识符 'name': 'Dr. Bob Smith', 'title': 'Dean of the College', 'organization': 'Awesome College' } ] } ] }提供的接口能力:
- 证书列表/创建(
certificates_list_handler,GET/POST):返回证书列表页(Backbone 应用)或 JSON 列表;POST 时通过CertificateManager.deserialize_certificate()校验并追加新证书; - 证书详情(
CertificateDetailAPIView,POST/PUT/DELETE):创建、更新、删除单条证书配置;删除处于激活状态的证书需要 GlobalStaff 权限; - 签名人管理(
signatory_detail_handler):删除证书签名人; - 证书激活/停用(
CertificateActivationAPIView,POST{ "is_active": true/false }):切换证书配置的激活状态(假设每个课程只有一条激活证书配置)。
所有接口都先通过has_studio_write_access(user, course_key)做权限校验,因此仅具备 Studio 写权限的用户可管理证书配置。
六、HTML 证书的展示(views/webview.py)
现代 Open edX 使用Web 证书(HTML 证书)替代 PDF 证书。渲染入口为render_html_view(),其判断链为:
- 全局开关
settings.CERTIFICATES_HTML_VIEW关闭 → 返回 "invalid" 页; - 课程
cert_html_view_enabled为假 → 返回 "invalid" 页; - 学生无
downloadable状态证书 → 返回 "invalid" 页; - 课程无激活证书配置(
get_active_web_certificate)→ 返回 "invalid" 页。
渲染时支持两种模板路径:
- 标准模板:
certificates/valid.html,上下文由CertificateHtmlViewConfiguration.get_config()提供默认值并按user_certificate.mode覆盖; - 自定义模板:启用
CUSTOM_CERTIFICATE_TEMPLATES_ENABLED后,按"组织+课程+模式+语言"四级匹配CertificateTemplate(get_certificate_template()),并使用模板自带语言渲染。
此外,渲染前会触发过滤器CertificateRenderStarted(org.openedx.learning.certificate.render.started.v1),插件可借此替换为自定义响应;视图还支持?preview=verified等参数实现 CMS 侧的证书预览。
证书的显示日期由display_date_for_certificate()(api.py)决定:优先使用CertificateDateOverride覆盖日期;否则按课程的certificates_display_behavior(END_WITH_DATE/END/EARLY_NO_INFO)选择"证书可用日期 / 课程结束日期 / 证书修改日期"。
七、证书的撤销与无效化
证书撤销的核心入口:
- 单张无效化:
GeneratedCertificate.invalidate(mode, source),将状态置为unavailable; - API 级无效化:
api.invalidate_certificate(user_id, course_key, source),白名单学生豁免; - 事件驱动撤销:
EXAM_ATTEMPT_REJECTED事件(考试作弊被拒)会触发invalidate_certificate(source='exam_event')(见 signals.py); - 白名单移除联动:
remove_allowlist_entry()在把学生移出白名单前会先无效化其证书(source='allowlist_removal')。
Open edX 课程证书撤销流程架构图
八、管理命令:批量发证与模板维护
lms/djangoapps/certificates/management/commands/下提供多条运维命令:
8.1 cert_generation:为指定用户批量生成证书
./manage.py lms cert_generation -u 123 456 -c course-v1:edX+DemoX+Demo_Course参数说明:
| 参数 | 说明 |
|---|---|
-u, --user | 用户 ID(可传多个,空格分隔) |
-c, --course-key | 课程运行 key |
--args-from-database | 从CertificateGenerationCommandConfiguration配置读取参数(如-u <user_id> -c <course_run_key>) |
命令内部对每个用户调用generate_certificate_task(),不存在的用户或不允许生成的情况仅记录日志。
8.2 其余命令
- modify_cert_template:批量修改
CertificateTemplate中的文本(--old-text/--new-text/--template_ids/--dry-run),支持ModifiedCertificateTemplateCommandConfiguration存储参数; - purge_references_to_pdf_certificates:清理 PDF 证书遗留引用(
--certificate_ids); - purge_pii_from_generatedcertificates:清理已生成证书中的个人隐私信息(PII)。
九、测试与设计决策参考
- 单元测试位于 lms/djangoapps/certificates/tests/,覆盖生成(
test_generation.py)、处理器(test_generation_handler.py)、信号(test_signals.py)、任务(test_tasks.py)、模型(test_models.py)、Web 视图(test_webview_views.py)等; - 管理命令测试位于 management/commands/tests/;
- 设计决策(ADR)位于 docs/decisions/,推荐重点阅读:
003-web-certs.rst(Web 证书)、004-cert-status.rst(状态机)、006-cert-date-override.rst(日期覆盖)、008-certificate-model-remnants.rst(PDF 遗留字段去留); - 流程图源码(PlantUML DSL)位于 docs/diagrams/,可在仓库内直接查看生成与撤销两条流程的完整组件交互。
十、排查指引小结
| 症状 | 排查入口 |
|---|---|
| 学生未自动获证 | 检查CertificateGenerationConfiguration全局开关、课程self_generation_enabled、CourseOverview、cert_html_view_enabled、ENABLE_CERTIFICATES_IDV_REQUIREMENT及信号日志 |
| 证书显示 "invalid" | 依次核对CERTIFICATES_HTML_VIEW、cert_html_view_enabled、证书状态是否downloadable、get_active_web_certificate是否有激活配置 |
| 需要给未通过学生发证 | 使用CertificateAllowlist白名单(API:create_or_update_certificate_allowlist_entry),加入后自动触发发证 |
| 需批量补发证书 | ./manage.py lms cert_generation -u ... -c <course_key> |
| 某学生证书需作废 | invalidate_certificate()/CertificateInvalidation记录 +GeneratedCertificate.invalidate() |
| 证书日期需修正 | Django Admin 中维护CertificateDateOverride,保存后自动广播COURSE_CERT_CHANGED |
综上,Open edX 的证书体系以GeneratedCertificate为核心、以信号和 Celery 为纽带、以 Credentials 为下游权威记录,将"配置(Studio)→ 生成(LMS)→ 展示(Web 证书)→ 撤销"串联为完整闭环。理解这条链路,即可从容应对证书相关的绝大多数开发与运维场景。
【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考