Open edX Platform Django 配置体系解析:从 openedx/envs/common.py 到 LMS/CMS 环境设置的三层架构
【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform
Open edX 平台(edx-platform)通过三层 Django 配置模块组织全站配置:平台级公共配置 openedx/envs/common.py(约 3062 行)、LMS 配置 lms/envs/common.py(约 3306 行)、CMS/Studio 配置 cms/envs/common.py(约 1359 行)。本文以官方设置参考文档 docs/references/settings.rst 为骨架,讲清这套配置的分层继承关系、Derived派生设置的计算机制、FEATURES特性开关代理,以及运维人员如何安全地覆盖设置项——读完后你可以独立完成"查看最终生效配置、编写自定义 DJANGO_SETTINGS_MODULE、排查配置覆盖链"三类操作。
一、设置参考文档是如何自动生成的
官方设置参考页 settings.rst 本身只有 25 行,其内容不是手写的,而是通过 Sphinx 指令自动从源码抽取:
Settings ======== This is the list of (non-toggle) Django settings defined in the ``common.py`` modules of edx-platform. .. note:: Toggle settings, which enable or disable a specific feature, are documented in the :ref:`feature toggles <featuretoggles>` section. Platform-Wide Settings ---------------------- .. settings:: :folder_path: openedx/envs/common.py LMS Settings ------------ .. settings:: :folder_path: lms/envs/common.py CMS Settings ------------ .. settings:: :folder_path: cms/envs/common.py三个关键机制值得注意:
.. settings::指令:由 Sphinx 扩展code_annotations.contrib.sphinx.extensions.settings提供,在 docs/conf.py 中注册。构建文档时,它扫描指定文件里所有大写顶层变量(即 Django 设置名),并结合源码中的注释生成条目化的设置列表,同时把每个设置链接到对应源码行。- 设置与功能开关分而治之:文档明确说明 toggle 类设置(形如
# .. toggle_name: settings.XXX注释块)不在此页,而是收录在 featuretoggles.rst 中。以 lms/envs/common.py 为例,其中包含 69 处toggle_name注释(如settings.ENABLE_MASQUERADE、settings.DISPLAY_HISTOGRAMS_TO_STAFF),CMS 侧有 16 处。这种"非开关设置看 settings 页、开关设置看 toggles 页"的划分是查阅配置时的第一条经验法则。 - 文档构建即代码检查:docs/conf.py 在构建前设置
DJANGO_SETTINGS_MODULE = 'docs.docs_settings'并执行django.setup(),确保 LMS 与 Studio 全部代码可被成功导入。因此设置文件里任何导入错误都会直接暴露为文档构建失败。
二、三层设置的分层继承关系
整个配置体系是一棵"星号导入 + 逐项覆盖"的树:
openedx/envs/common.py 平台级:LMS 与 CMS 共享的基础默认值(路径、Django 内建项、缓存、DB、模板、Celery、JWT……) ├── lms/envs/common.py LMS:`from openedx.envs.common import *` 后叠加 LMS 专属设置与 FEATURES │ ├── lms/envs/test.py 单元测试环境,末尾调用 derive_settings(__name__) │ └── lms/envs/production.py 生产环境,加载 LMS_CFG 指向的 YAML 覆盖 └── cms/envs/common.py CMS/Studio:同样 `from openedx.envs.common import *` 后叠加 CMS 专属设置 ├── cms/envs/test.py └── cms/envs/production.py继承方式的证据直接写在源码里:lms/envs/common.py 与 cms/envs/common.py 都执行from openedx.envs.common import *(附带pylint: disable=wildcard-import),随后各自定义FEATURES = FeaturesProxy(globals())特性开关代理(lms/envs/common.py#L72、cms/envs/common.py#L58)。
openedx/envs/common.py 的模块 docstring 还特别警告了一个实操陷阱:
WARNING: Mutable values defined in this file may be unintentionally modified downstream…… if an LMS settings module modifies a mutable value defined here, the final value of the corresponding CMS setting may also be affected. To avoid this risk, create a deep copy of the value in the module that modifies it.
即:因为 CMS 会导入 LMS 会导入的共享可变值,若你的下游设置文件直接就地修改(mutate)某个列表/字典,可能污染其他服务的最终配置——正确做法是深拷贝后再修改。
按 ADR 0022-settings-simplification.rst 的目标结构,这套分层的定位是:
openedx/envs/common.py:尽可能集中 LMS/CMS 共享配置,给出合理的、生产可用的默认值(production-ready defaults);对密钥类设置给出明显错误的占位默认值(obviously-wrong defaults),确保不会被误用于生产;lms/envs/common.py/cms/envs/common.py:在此之上扩展出各服务的生产可用配置,并作为运行管理命令的默认设置文件(可用DJANGO_SETTINGS_MODULE覆盖);- 该 ADR 还明确了当前配置体系的演进方向:逐步把
production.py中的默认值"上浮"到common.py,让运维通过自定义DJANGO_SETTINGS_MODULE派生自common.py来完成覆盖。
三、平台级核心设置逐项解读(openedx/envs/common.py)
以下按 openedx/envs/common.py 源码中的分区(##### Section Name #####头注释)梳理最重要的设置项。完整列表请在文档构建产物中通过.. settings::指令生成的页面查阅,此处给出高频项与源码依据。
3.1 路径与运行基础(Paths / Django Built-Ins)
| 设置 | 默认值 | 说明 | 源码位置 |
|---|---|---|---|
REPO_ROOT | 仓库根目录 | 由path(__file__).abspath()推导,其余路径常量都基于它 | openedx/envs/common.py#L98-L104 |
COURSES_ROOT/DATA_DIR | ENV_ROOT / "data" | 课程数据根目录,ENV_ROOT为存放 edx-platform 的上级目录(venv 同级) | 同上 |
DEBUG | False | 生产默认关闭调试 | L108 |
USE_TZ/TIME_ZONE | True/'UTC' | 统一 UTC 时区 | L110-L111 |
SECRET_KEY | 'dev key' | 典型"明显错误"占位值,生产环境必须覆盖 | L118 |
SECURE_PROXY_SSL_HEADER | ('HTTP_X_FORWARDED_PROTO', 'https') | 源码注释强调:启用后服务器必须位于会剥离该头的代理之后,否则用户可伪造 https 状态 | L120-L123 |
SESSION_ENGINE | django.contrib.sessions.backends.cache | 会话存缓存;序列化器为openedx.core.lib.session_serializers.PickleSerializer | L129-L131 |
CSRF_COOKIE_AGE | 60*60*24*7*52(一年) | 源码同时警告CSRF_COOKIE_SECURE = False的默认值"强烈建议在面向终端用户的环境中覆盖" | L133-L136 |
ALLOWED_HOSTS | ['*'] | 仅适合开发/测试 | L138 |
ROOT_URLCONF | Derived(lambda settings: f'{settings.SERVICE_VARIANT}.urls') | 派生设置的典型用法:LMS 得到lms.urls、CMS 得到cms.urls | L143 |
X_FRAME_OPTIONS | 'DENY' | 防点击劫持,设为'ALLOW'可关闭 | L141 |
3.2 缓存与数据库
CACHES 定义了 7 个缓存别名:default、general、configuration、staticfiles、course_structure_cache(超时 1 周)、celery(超时 7200 秒)、mongo_metadata_inheritance(超时 300 秒),全部默认使用PyMemcacheCache指向localhost:11211,并统一使用common.djangoapps.util.memcache.safe_key作为 KEY_FUNCTION、ignore_exc: True(缓存故障不阻断业务)。
DATABASES 默认声明三个 MySQL 连接:default(库名edxapp,开启ATOMIC_REQUESTS)、read_replica(读副本)、student_module_history(库名edxapp_csmh,存放学习进度历史)。配套的DATABASE_ROUTERS使用 StudentModuleHistoryExtendedRouter 把StudentModuleHistory相关表路由到独立库。注释中说明edxapp-migrate脚本保证除read_replica外的库会同时为 LMS 和 CMS 执行迁移。
3.3 模板系统:Django 与 Mako 双引擎
TEMPLATES 同时注册了两个后端:
django后端:APP_DIRS: False,模板目录为PROJECT_ROOT/templates、common/templates等;loader 链中特意加入 Mako 感知的 loader(ThemeTemplateLoader→MakoFilesystemLoader→MakoAppDirectoriesLoader),以便在 Django 模板中 include Mako 模板(如main_django.html)。mako后端:BACKEND为common.djangoapps.edxmako.backend.Mako,其DIRS是一个Derived(make_mako_template_dirs)——该函数 会在启用综合主题(ENABLE_COMPREHENSIVE_THEMING)时,把各主题的模板目录插入MAKO_TEMPLATE_DIRS_BASE头部。
相关配套设置:MAKO_MODULE_DIR(Mako 编译产物目录,L504 也是Derived,按SERVICE_VARIANT区分 LMS/CMS 的临时目录)、CONTEXT_PROCESSORS(L520-L530,包含帮助系统的help_tokens.context_processor与站点配置的configuration_context)。
3.4 其他主要分区
openedx/envs/common.py 后半部分按主题划分的分区(源码中的分区头注释)包括:Optional Apps(可选项INSTALLED_APPS扩展)、Django Rest Framework、Celery、RedirectMiddleware、Django Debug Toolbar、JWT、Features、CAPA External Code Evaluation、CSRF、Cross-domain Requests、Social Media、Google Analytics、Block Structures、Bulk Email、Video(含图片/字幕存储与视频管线)、Parental Controls、Instructor Downloads、Registration、Course Enrollment Modes、Enterprise Api Client、ModuleStore、Micro-frontends、Swift、SAML、django-fernet-fields、django-simple-history、Django OAuth Toolkit、Profile Image、XBlock、Built-in Blocks Extraction 等。这些分区的设置都会出现在文档构建产物中;开发调试时可直接在源码中按分区名定位。
四、核心机制一:Derived派生设置
Derived是理解整套配置体系的钥匙。定义在 openedx/core/lib/derived.py:
class Derived(t.Generic[T]): """ A temporary Django setting value, defined with a function which generates the setting's eventual value. Said function (`calculate_value`) should accept a Django settings module, and return a calculated value. To ensure that application code does not encounter an instance of this class in your settings, be sure to call `derive_settings` somewhere in your terminal settings file. """ def __init__(self, calculate_value: t.Callable[[Settings], T]): self.calculate_value = calculate_value工作方式是"延迟求值":
- 设置先被赋值为一个
Derived(calculate_value)占位对象,而不是具体值; - 所有设置模块(shared → service → 下游覆盖)全部加载完成后,终端设置文件调用
derive_settings(module_name); - derive_settings 遍历该模块所有大写字母开头的顶层变量(正则
^[A-Z][A-Z0-9_]*$),递归评估其中嵌套在 dict/list/tuple/set 里的Derived对象(见 _derive_recursively),用calculate_value(settings)的返回值就地替换。
这样设计解决的问题是:某个设置的合理默认值依赖另一个设置的最终值,而后者可能被你下游覆盖。典型实例:
ROOT_URLCONF = Derived(lambda settings: f'{settings.SERVICE_VARIANT}.urls')(openedx/envs/common.py#L143):只有等SERVICE_VARIANT被最终确定后才计算;LOCALE_PATHS = Derived(_make_locale_paths)(L222):依赖PREPEND_LOCALE_PATHS与主题设置;LOCALE_PATHS的推导函数 _make_locale_paths 会先取PREPEND_LOCALE_PATHS,再追加conf/locale,启用主题时继续追加主题 locale 路径。
两条硬约束(源码 docstring 明确):
- 你的终端设置文件必须调用
derive_settings(__name__),否则应用代码会拿到Derived实例而非真实值。仓库内现有调用点见 lms/envs/test.py#L339 与 cms/envs/test.py#L193; Derived值可以被直接赋成一个普通值来覆盖——此时它不再参与推导,等价于"手工指定最终值"。
五、核心机制二:FEATURES特性开关代理
LMS 与 CMS 的common.py都不再用裸字典管理开关,而是FEATURES = FeaturesProxy(globals())(lms/envs/common.py#L71-L72)。FeaturesProxy是 openedx/core/lib/features_setting_proxy.py 中实现的MutableMapping:它以所在设置模块的globals()为数据源,把settings.FEATURES['ENABLE_MASQUERADE']这样的字典访问映射回模块级变量ENABLE_MASQUERADE的读写。
这带来两个实际后果:
- 覆盖开关的标准写法仍是"直接重赋模块级变量"(如
ENABLE_MASQUERADE = False),settings.FEATURES[...]只是读取语法糖; - 由于代理绑定在
globals()上,LMS 与 CMS 各自维护独立的FEATURES命名空间,修改互不干扰。
带# .. toggle_name:注释块的开关(如 lms/envs/common.py#L129-L138 的ENABLE_DJANGO_ADMIN_SITE)会被文档管线收集到 featuretoggles.rst 页面,注释块中的toggle_default、toggle_description、toggle_warning字段即页面展示内容。
六、LMS 与 CMS 服务级设置要点
6.1 lms/envs/common.py
除继承自平台级的全部设置外,LMS 层还定义了:PLATFORM_NAME、COURSE_IDT_REGEX、课程/使用键的正则常量、COURSES_API_*、PAYMENT_PROCESSOR_*、邮件/SMTP、ENTERPRISE_*角色常量导入(lms/envs/common.py#L48-L62)、CACHES扩展(如bulk_email别名)等。文件头部 lms/envs/common.py#L14-L32 还写明了一条重要惯例——扩展列表类设置优先使用EXTRA后缀的新变量(如CELERY_EXTRA_IMPORTS、XBLOCK_EXTRA_MIXINS),而不是就地替换整个列表,以便平台后续合并时不丢失你的条目。
6.2 cms/envs/common.py
CMS 层聚焦 Studio 侧能力,代表性设置(cms/envs/common.py#L62-L160):
| 设置 | 默认 | 说明 |
|---|---|---|
STUDIO_NAME/STUDIO_SHORT_NAME | "Your Platform Studio"/"Studio" | 品牌名 |
SECRET_KEY | 'dev key' | 再次覆盖为占位值,生产必须替换 |
ENABLE_CREATOR_GROUP | True | 开启后仅课程创建者组可建课 |
ENABLE_CONTENT_LIBRARIES | True | 内容库(仅 split mongo 课程支持) |
ALLOW_COURSE_RERUNS | True | 控制 Studio 首页 Re-run Course 入口 |
ENABLE_SEPARATE_ARCHIVED_COURSES | True | 归档课程单独列表展示 |
ENABLE_GRADE_DOWNLOADS | True | 支持成绩下载 |
GITHUB_PUSH/STUDIO_REQUEST_EMAIL | False/'' | Git 推送、开课申请邮箱 |
ENABLE_MAX_FAILED_LOGIN_ATTEMPTS | False | 失败登录锁定 |
七、实操指南:查看最终配置与自定义覆盖
7.1 查看某设置模块的完整生效值
ADR 计划引入的dump_settings管理命令已落地:openedx/core/djangoapps/util/management/commands/dump_settings.py,对应测试在 test_dump_settings.py。用法示例(在 venv 中,以 LMS 测试设置为前提):
DJANGO_SETTINGS_MODULE=lms.envs.test ./manage.py lms dump_settings它会以 JSON 输出当前DJANGO_SETTINGS_MODULE加载后的全部顶层设置,适合在调整覆盖链后验证"最终值到底是什么"。
7.2 编写自定义生产设置模块
按 ADR 的目标结构,自定义设置模块(例如部署工具生成的lms_prod.py)应遵循三步:
# <your>/lms_prod.py(示例骨架) from lms.envs.common import * # 1. 继承服务级公共默认 # 2. 用生产值替换所有"明显错误"占位(SECRET_KEY、ALLOWED_HOSTS、 # CSRF_COOKIE_SECURE、SECURE_PROXY_SSL_HEADER 前提条件……) from openedx.core.lib.derived import derive_settings derive_settings(__name__) # 3. 渲染所有 Derived 值要点回顾:
- 必须覆盖"明显错误"默认值,否则会带着
SECRET_KEY='dev key'这类占位值上线; - 必须调用
derive_settings(__name__),否则Derived占位不会计算; - 修改共享可变值前先深拷贝(见 openedx/envs/common.py 的 WARNING);
- 对列表类设置优先使用
XXX_EXTRA_YYY变量扩展而非整表替换。
7.3 测试环境配置
单测使用的 openedx/envs/test.py 集中了 LMS/CMS 共享的测试值(如把ENABLE_DISCUSSION_SERVICE置为False加速测试),服务级测试模块 lms/envs/test.py 与 cms/envs/test.py 分别在其末尾执行derive_settings(__name__)完成派生设置渲染——这也是"终端设置文件负责调用derive_settings"这一约束在仓库内的直接范例。
八、延伸阅读与限制说明
- 配置简化工程的背景、动机与完整行动路线(含对 Tutor 间接层问题的剖析)见 ADR:docs/decisions/0022-settings-simplification.rst;
- 功能开关完整列表见 docs/references/featuretoggles.rst;
- 各环境模块的职责分工:
common.py(默认值)→production.py(加载LMS_CFG/CMS_CFG指向的 YAML 覆盖)→test.py(单测)。当前仓库中 YAML 覆盖路径仍在演进,具体取舍以 ADR 所述两条路线(保留简化版 YAML schema,或完全转向自定义DJANGO_SETTINGS_MODULE)为准; - 本文所有行号与取值均基于当前仓库快照(
openedx/envs/common.py3062 行、lms/envs/common.py3306 行、cms/envs/common.py1359 行),升级平台后请以对应版本的源码为准。
【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考