From ffd0cec34b84894514b488e6ab8396f3ccf26edc Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Fri, 29 Aug 2025 11:11:07 -0400 Subject: [PATCH 01/27] build: Unpin pip and pip-tools. Test with unpinning both pip and pip-tools. The latest versions have fixed the pathing issues with `make upgrade` and the dependency resolution issues reported in https://github.com/openedx/edx-lint/issues/458 --- Makefile | 2 ++ requirements/constraints.txt | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index b76479fbda..6c525a57b6 100644 --- a/Makefile +++ b/Makefile @@ -123,6 +123,8 @@ compile-requirements: pre-requirements $(COMMON_CONSTRAINTS_TXT) ## Re-compile * @# time someone tries to use the outputs. sed 's/Django<5.0//g' requirements/common_constraints.txt > requirements/common_constraints.tmp mv requirements/common_constraints.tmp requirements/common_constraints.txt + sed 's/pip<24.3//g' requirements/common_constraints.txt > requirements/common_constraints.tmp + mv requirements/common_constraints.tmp requirements/common_constraints.txt pip-compile -v --allow-unsafe ${COMPILE_OPTS} -o requirements/pip.txt requirements/pip.in pip install -r requirements/pip.txt diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 772321fe52..6271881301 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -79,11 +79,6 @@ openai<=0.28.1 # Issue for unpinning: https://github.com/openedx/edx-platform/issues/35267 path<16.12.0 -# Date: 2025-05-11 -# Broke lxml[html_clean] extra dependency declaration -# Issue for unpinning: https://github.com/openedx/edx-platform/issues/37168 -pip-tools<7.5.0 - # Date: 2022-08-03 # pycodestyle==2.9.0 generates false positive error E275. # Constraint can be removed once the issue https://github.com/PyCQA/pycodestyle/issues/1090 is fixed. From 66fdf4092ca88d97a12096a59be33c0864fa99ac Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Tue, 2 Sep 2025 14:05:10 -0400 Subject: [PATCH 02/27] chore: Run `make upgrade` --- requirements/common_constraints.txt | 2 +- requirements/edx-sandbox/base.txt | 6 +- requirements/edx/assets.txt | 2 +- requirements/edx/base.txt | 34 +++++------ requirements/edx/development.txt | 56 +++++++++---------- requirements/edx/doc.txt | 32 +++++------ requirements/edx/testing.txt | 34 +++++------ requirements/pip-tools.txt | 6 +- requirements/pip.txt | 6 +- .../structures_pruning/requirements/base.txt | 2 +- scripts/user_retirement/requirements/base.txt | 8 +-- .../user_retirement/requirements/testing.txt | 4 +- 12 files changed, 93 insertions(+), 99 deletions(-) diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 071d0a6ecf..af5c9e04c6 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -25,4 +25,4 @@ elasticsearch<7.14.0 # Cause: https://github.com/openedx/edx-lint/issues/458 # This can be unpinned once https://github.com/openedx/edx-lint/issues/459 has been resolved. -pip<24.3 + diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt index e41030c76d..6f68e648ed 100644 --- a/requirements/edx-sandbox/base.txt +++ b/requirements/edx-sandbox/base.txt @@ -24,9 +24,9 @@ joblib==1.5.2 # via nltk kiwisolver==1.4.9 # via matplotlib -lxml[html-clean,html_clean]==5.3.2 +lxml[html-clean]==5.3.2 # via - # -c requirements/edx-sandbox/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx-sandbox/base.in # lxml-html-clean # openedx-calc @@ -48,7 +48,7 @@ nltk==3.9.1 # chem numpy==1.26.4 # via - # -c requirements/edx-sandbox/../constraints.txt + # -c requirements/constraints.txt # chem # contourpy # matplotlib diff --git a/requirements/edx/assets.txt b/requirements/edx/assets.txt index 6288377f63..bb6693f4dc 100644 --- a/requirements/edx/assets.txt +++ b/requirements/edx/assets.txt @@ -8,7 +8,7 @@ click==8.2.1 # via -r requirements/edx/assets.in libsass==0.10.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/assets.in nodeenv==1.9.1 # via -r requirements/edx/assets.in diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 6000ff82b3..1f9643a048 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -95,7 +95,7 @@ camel-converter[pydantic]==4.0.1 # via meilisearch celery==5.5.3 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # django-celery-results # django-user-tasks @@ -171,7 +171,7 @@ defusedxml==0.7.1 # social-auth-core django==4.2.23 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # django-appconf # django-autocomplete-light @@ -322,7 +322,7 @@ django-mysql==4.17.0 # via -r requirements/edx/kernel.in django-oauth-toolkit==1.7.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # edx-enterprise # enterprise-integrated-channels @@ -374,7 +374,7 @@ django-waffle==5.0.0 # edx-toggles django-webpack-loader==0.7.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # edx-proctoring djangorestframework==3.16.1 @@ -478,7 +478,7 @@ edx-drf-extensions==10.6.0 # openedx-learning edx-enterprise==6.3.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in edx-event-bus-kafka==6.1.0 # via -r requirements/edx/kernel.in @@ -562,8 +562,8 @@ edxval==3.0.0 # via -r requirements/edx/kernel.in elasticsearch==7.9.1 # via - # -c requirements/edx/../common_constraints.txt - # -c requirements/edx/../constraints.txt + # -c requirements/common_constraints.txt + # -c requirements/constraints.txt # edx-search # openedx-forum enmerkar==0.7.1 @@ -726,9 +726,9 @@ loremipsum==1.0.5 # via ora2 lti-consumer-xblock==9.14.2 # via -r requirements/edx/kernel.in -lxml[html-clean,html_clean]==5.3.2 +lxml[html-clean]==5.3.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # edx-i18n-tools # edxval @@ -774,7 +774,7 @@ mongoengine==0.29.1 # via -r requirements/edx/kernel.in monotonic==1.6 # via analytics-python -more-itertools==10.7.0 +more-itertools==10.8.0 # via cssutils mpmath==1.3.0 # via sympy @@ -798,7 +798,7 @@ nodeenv==1.9.1 # via -r requirements/edx/kernel.in numpy==1.26.4 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # chem # openedx-calc # scipy @@ -815,7 +815,7 @@ olxcleaner==0.3.0 # via -r requirements/edx/kernel.in openai==0.28.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # edx-enterprise openedx-atlas==0.7.0 # via @@ -851,7 +851,7 @@ openedx-forum==0.3.4 # via -r requirements/edx/kernel.in openedx-learning==0.27.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in optimizely-sdk==5.2.0 # via -r requirements/edx/bundled.in @@ -867,7 +867,7 @@ paramiko==4.0.0 # via edx-enterprise path==16.11.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # edx-i18n-tools # path-py @@ -955,7 +955,7 @@ pymemcache==4.0.0 # via -r requirements/edx/kernel.in pymongo==4.4.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # edx-opaque-keys # event-tracking @@ -1135,7 +1135,7 @@ snowflake-connector-python==3.17.2 # via edx-enterprise social-auth-app-django==5.4.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # edx-auth-backends social-auth-core==4.7.0 @@ -1284,7 +1284,7 @@ xblocks-contrib==0.6.0 # via -r requirements/edx/bundled.in xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # python3-saml xss-utils==0.8.0 # via -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 74d1565505..696bafd459 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -161,7 +161,7 @@ bridgekeeper==0.9 # -r requirements/edx/testing.txt build==1.3.0 # via - # -r requirements/edx/../pip-tools.txt + # -r requirements/pip-tools.txt # pip-tools cachecontrol==0.14.3 # via @@ -182,7 +182,7 @@ camel-converter[pydantic]==4.0.1 # meilisearch celery==5.5.3 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # django-celery-results @@ -228,11 +228,11 @@ chem==2.0.0 # -r requirements/edx/testing.txt click==8.2.1 # via - # -r requirements/edx/../pip-tools.txt # -r requirements/edx/assets.txt # -r requirements/edx/development.in # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt + # -r requirements/pip-tools.txt # celery # click-didyoumean # click-log @@ -337,7 +337,7 @@ distlib==0.4.0 # virtualenv django==4.2.23 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # django-appconf @@ -465,7 +465,7 @@ django-crum==0.7.9 # super-csv django-debug-toolbar==5.2.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/development.in django-fernet-fields-v2==0.9 # via @@ -530,7 +530,7 @@ django-mysql==4.17.0 # -r requirements/edx/testing.txt django-oauth-toolkit==1.7.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-enterprise @@ -588,7 +588,7 @@ django-storages==1.14.6 # edxval django-stubs[compatible-mypy]==5.2.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/development.in # djangorestframework-stubs django-stubs-ext==5.2.2 @@ -608,7 +608,7 @@ django-waffle==5.0.0 # edx-toggles django-webpack-loader==0.7.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-proctoring @@ -754,7 +754,7 @@ edx-drf-extensions==10.6.0 # openedx-learning edx-enterprise==6.3.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt edx-event-bus-kafka==6.1.0 @@ -867,8 +867,8 @@ edxval==3.0.0 # -r requirements/edx/testing.txt elasticsearch==7.9.1 # via - # -c requirements/edx/../common_constraints.txt - # -c requirements/edx/../constraints.txt + # -c requirements/common_constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-search @@ -1197,7 +1197,7 @@ lazy==1.6 # xblock libsass==0.10.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/assets.txt loremipsum==1.0.5 # via @@ -1210,7 +1210,7 @@ lti-consumer-xblock==9.14.2 # -r requirements/edx/testing.txt lxml[html-clean]==5.3.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-i18n-tools @@ -1286,7 +1286,7 @@ monotonic==1.6 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # analytics-python -more-itertools==10.7.0 +more-itertools==10.8.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1335,7 +1335,7 @@ nodeenv==1.9.1 # -r requirements/edx/testing.txt numpy==1.26.4 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # chem @@ -1357,7 +1357,7 @@ olxcleaner==0.3.0 # -r requirements/edx/testing.txt openai==0.28.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-enterprise @@ -1408,7 +1408,7 @@ openedx-forum==0.3.4 # -r requirements/edx/testing.txt openedx-learning==0.27.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt optimizely-sdk==5.2.0 @@ -1421,9 +1421,9 @@ ora2==6.16.4 # -r requirements/edx/testing.txt packaging==25.0 # via - # -r requirements/edx/../pip-tools.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt + # -r requirements/pip-tools.txt # build # drf-yasg # gunicorn @@ -1443,7 +1443,7 @@ paramiko==4.0.0 # edx-enterprise path==16.11.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-i18n-tools @@ -1477,10 +1477,8 @@ pillow==11.3.0 # edx-enterprise # edx-organizations # edxval -pip-tools==7.4.1 - # via - # -c requirements/edx/../constraints.txt - # -r requirements/edx/../pip-tools.txt +pip-tools==7.5.0 + # via -r requirements/pip-tools.txt platformdirs==4.4.0 # via # -r requirements/edx/doc.txt @@ -1550,7 +1548,7 @@ pyasn1-modules==0.4.2 # google-auth pycodestyle==2.8.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/testing.txt pycountry==24.6.1 # via @@ -1648,7 +1646,7 @@ pymemcache==4.0.0 # -r requirements/edx/testing.txt pymongo==4.4.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-opaque-keys @@ -1682,7 +1680,7 @@ pyproject-api==1.9.1 # tox pyproject-hooks==1.2.0 # via - # -r requirements/edx/../pip-tools.txt + # -r requirements/pip-tools.txt # build # pip-tools pyquery==2.0.1 @@ -1966,7 +1964,7 @@ snowflake-connector-python==3.17.2 # edx-enterprise social-auth-app-django==5.4.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-auth-backends @@ -2241,9 +2239,9 @@ webob==1.8.9 # xblock wheel==0.45.1 # via - # -r requirements/edx/../pip-tools.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt + # -r requirements/pip-tools.txt # django-pipeline # pip-tools wrapt==1.17.3 @@ -2291,7 +2289,7 @@ xblocks-contrib==0.6.0 # -r requirements/edx/testing.txt xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # python3-saml diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 9de665c477..8d734f6bcc 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -135,7 +135,7 @@ camel-converter[pydantic]==4.0.1 # meilisearch celery==5.5.3 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # django-celery-results # django-user-tasks @@ -229,7 +229,7 @@ defusedxml==0.7.1 # social-auth-core django==4.2.23 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # django-appconf # django-autocomplete-light @@ -391,7 +391,7 @@ django-mysql==4.17.0 # via -r requirements/edx/base.txt django-oauth-toolkit==1.7.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-enterprise # enterprise-integrated-channels @@ -446,7 +446,7 @@ django-waffle==5.0.0 # edx-toggles django-webpack-loader==0.7.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-proctoring djangorestframework==3.16.1 @@ -562,7 +562,7 @@ edx-drf-extensions==10.6.0 # openedx-learning edx-enterprise==6.3.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt edx-event-bus-kafka==6.1.0 # via -r requirements/edx/base.txt @@ -648,8 +648,8 @@ edxval==3.0.0 # via -r requirements/edx/base.txt elasticsearch==7.9.1 # via - # -c requirements/edx/../common_constraints.txt - # -c requirements/edx/../constraints.txt + # -c requirements/common_constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-search # openedx-forum @@ -883,7 +883,7 @@ lti-consumer-xblock==9.14.2 # via -r requirements/edx/base.txt lxml[html-clean]==5.3.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-i18n-tools # edxval @@ -938,7 +938,7 @@ monotonic==1.6 # via # -r requirements/edx/base.txt # analytics-python -more-itertools==10.7.0 +more-itertools==10.8.0 # via # -r requirements/edx/base.txt # cssutils @@ -971,7 +971,7 @@ nodeenv==1.9.1 # via -r requirements/edx/base.txt numpy==1.26.4 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # chem # openedx-calc @@ -989,7 +989,7 @@ olxcleaner==0.3.0 # via -r requirements/edx/base.txt openai==0.28.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-enterprise openedx-atlas==0.7.0 @@ -1027,7 +1027,7 @@ openedx-forum==0.3.4 # via -r requirements/edx/base.txt openedx-learning==0.27.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt optimizely-sdk==5.2.0 # via -r requirements/edx/base.txt @@ -1048,7 +1048,7 @@ paramiko==4.0.0 # edx-enterprise path==16.11.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-i18n-tools # path-py @@ -1169,7 +1169,7 @@ pymemcache==4.0.0 # via -r requirements/edx/base.txt pymongo==4.4.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-opaque-keys # event-tracking @@ -1390,7 +1390,7 @@ snowflake-connector-python==3.17.2 # edx-enterprise social-auth-app-django==5.4.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-auth-backends social-auth-core==4.7.0 @@ -1613,7 +1613,7 @@ xblocks-contrib==0.6.0 # via -r requirements/edx/base.txt xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # python3-saml xss-utils==0.8.0 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 03dea1d943..224a521f14 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -133,7 +133,7 @@ camel-converter[pydantic]==4.0.1 # meilisearch celery==5.5.3 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # django-celery-results # django-user-tasks @@ -255,7 +255,7 @@ distlib==0.4.0 # via virtualenv django==4.2.23 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # django-appconf # django-autocomplete-light @@ -417,7 +417,7 @@ django-mysql==4.17.0 # via -r requirements/edx/base.txt django-oauth-toolkit==1.7.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-enterprise # enterprise-integrated-channels @@ -472,7 +472,7 @@ django-waffle==5.0.0 # edx-toggles django-webpack-loader==0.7.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-proctoring djangorestframework==3.16.1 @@ -583,7 +583,7 @@ edx-drf-extensions==10.6.0 # openedx-learning edx-enterprise==6.3.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt edx-event-bus-kafka==6.1.0 # via -r requirements/edx/base.txt @@ -671,8 +671,8 @@ edxval==3.0.0 # via -r requirements/edx/base.txt elasticsearch==7.9.1 # via - # -c requirements/edx/../common_constraints.txt - # -c requirements/edx/../constraints.txt + # -c requirements/common_constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-search # openedx-forum @@ -924,7 +924,7 @@ lti-consumer-xblock==9.14.2 # via -r requirements/edx/base.txt lxml[html-clean]==5.3.2 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-i18n-tools # edxval @@ -983,7 +983,7 @@ monotonic==1.6 # via # -r requirements/edx/base.txt # analytics-python -more-itertools==10.7.0 +more-itertools==10.8.0 # via # -r requirements/edx/base.txt # cssutils @@ -1016,7 +1016,7 @@ nodeenv==1.9.1 # via -r requirements/edx/base.txt numpy==1.26.4 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # chem # openedx-calc @@ -1034,7 +1034,7 @@ olxcleaner==0.3.0 # via -r requirements/edx/base.txt openai==0.28.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-enterprise openedx-atlas==0.7.0 @@ -1072,7 +1072,7 @@ openedx-forum==0.3.4 # via -r requirements/edx/base.txt openedx-learning==0.27.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt optimizely-sdk==5.2.0 # via -r requirements/edx/base.txt @@ -1096,7 +1096,7 @@ paramiko==4.0.0 # edx-enterprise path==16.11.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-i18n-tools # path-py @@ -1179,7 +1179,7 @@ pyasn1-modules==0.4.2 # google-auth pycodestyle==2.8.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/testing.in pycountry==24.6.1 # via -r requirements/edx/base.txt @@ -1248,7 +1248,7 @@ pymemcache==4.0.0 # via -r requirements/edx/base.txt pymongo==4.4.0 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-opaque-keys # event-tracking @@ -1496,7 +1496,7 @@ snowflake-connector-python==3.17.2 # edx-enterprise social-auth-app-django==5.4.1 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-auth-backends social-auth-core==4.7.0 @@ -1695,7 +1695,7 @@ xblocks-contrib==0.6.0 # via -r requirements/edx/base.txt xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt + # -c requirements/constraints.txt # -r requirements/edx/base.txt # python3-saml xss-utils==0.8.0 diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt index 21e3389916..b19a4faaa0 100644 --- a/requirements/pip-tools.txt +++ b/requirements/pip-tools.txt @@ -10,10 +10,8 @@ click==8.2.1 # via pip-tools packaging==25.0 # via build -pip-tools==7.4.1 - # via - # -c requirements/constraints.txt - # -r requirements/pip-tools.in +pip-tools==7.5.0 + # via -r requirements/pip-tools.in pyproject-hooks==1.2.0 # via # build diff --git a/requirements/pip.txt b/requirements/pip.txt index dabfa8f0eb..dec15874f7 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -8,9 +8,7 @@ wheel==0.45.1 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==24.2 - # via - # -c requirements/common_constraints.txt - # -r requirements/pip.in +pip==25.2 + # via -r requirements/pip.in setuptools==80.9.0 # via -r requirements/pip.in diff --git a/scripts/structures_pruning/requirements/base.txt b/scripts/structures_pruning/requirements/base.txt index 9a0bf10bf3..7afeac7d9d 100644 --- a/scripts/structures_pruning/requirements/base.txt +++ b/scripts/structures_pruning/requirements/base.txt @@ -16,7 +16,7 @@ edx-opaque-keys==3.0.0 # via -r scripts/structures_pruning/requirements/base.in pymongo==4.4.0 # via - # -c scripts/structures_pruning/requirements/../../../requirements/constraints.txt + # -c requirements/constraints.txt # -r scripts/structures_pruning/requirements/base.in # edx-opaque-keys stevedore==5.5.0 diff --git a/scripts/user_retirement/requirements/base.txt b/scripts/user_retirement/requirements/base.txt index 11aeade054..8fe3ef5ca6 100644 --- a/scripts/user_retirement/requirements/base.txt +++ b/scripts/user_retirement/requirements/base.txt @@ -34,7 +34,7 @@ cryptography==45.0.7 # via pyjwt django==4.2.23 # via - # -c scripts/user_retirement/requirements/../../../requirements/constraints.txt + # -c requirements/constraints.txt # django-crum # django-waffle # edx-django-utils @@ -48,7 +48,7 @@ edx-rest-api-client==6.2.0 # via -r scripts/user_retirement/requirements/base.in google-api-core==2.25.1 # via google-api-python-client -google-api-python-client==2.179.0 +google-api-python-client==2.181.0 # via -r scripts/user_retirement/requirements/base.in google-auth==2.40.3 # via @@ -75,9 +75,9 @@ jmespath==1.0.1 # botocore lxml==5.3.2 # via - # -c scripts/user_retirement/requirements/../../../requirements/constraints.txt + # -c requirements/constraints.txt # zeep -more-itertools==10.7.0 +more-itertools==10.8.0 # via simple-salesforce platformdirs==4.4.0 # via zeep diff --git a/scripts/user_retirement/requirements/testing.txt b/scripts/user_retirement/requirements/testing.txt index d6c4c10150..997681c328 100644 --- a/scripts/user_retirement/requirements/testing.txt +++ b/scripts/user_retirement/requirements/testing.txt @@ -76,7 +76,7 @@ google-api-core==2.25.1 # via # -r scripts/user_retirement/requirements/base.txt # google-api-python-client -google-api-python-client==2.179.0 +google-api-python-client==2.181.0 # via -r scripts/user_retirement/requirements/base.txt google-auth==2.40.3 # via @@ -126,7 +126,7 @@ markupsafe==3.0.2 # werkzeug mock==5.2.0 # via -r scripts/user_retirement/requirements/testing.in -more-itertools==10.7.0 +more-itertools==10.8.0 # via # -r scripts/user_retirement/requirements/base.txt # simple-salesforce From 790a67a0d13229dc76261b6e1bce423d7a65589f Mon Sep 17 00:00:00 2001 From: coder1918 Date: Tue, 2 Sep 2025 23:54:05 -0600 Subject: [PATCH 03/27] refactor: flatten FEATURES dictionary with backward compatible proxy --- cms/envs/common.py | 652 +++--- cms/envs/devstack.py | 28 +- cms/envs/production.py | 11 +- cms/envs/test.py | 37 +- lms/djangoapps/discussion/settings/common.py | 2 +- lms/djangoapps/instructor/settings/common.py | 180 +- .../instructor/settings/production.py | 4 +- lms/djangoapps/instructor/settings/test.py | 4 +- lms/envs/common.py | 2007 +++++++++-------- lms/envs/devstack.py | 66 +- lms/envs/production.py | 25 +- lms/envs/test.py | 55 +- .../djangoapps/ace_common/settings/common.py | 2 +- .../core/djangoapps/models/course_details.py | 5 +- openedx/core/lib/features_setting_proxy.py | 106 + openedx/envs/common.py | 2 + .../features/announcements/settings/common.py | 4 +- .../features/announcements/settings/test.py | 2 +- 18 files changed, 1657 insertions(+), 1535 deletions(-) create mode 100644 openedx/core/lib/features_setting_proxy.py diff --git a/cms/envs/common.py b/cms/envs/common.py index 13811d2a13..273eb9f191 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -66,6 +66,10 @@ from openedx.core.djangoapps.theming.helpers_dirs import ( from openedx.core.lib.license import LicenseMixin from openedx.core.lib.derived import Derived from openedx.core.release import doc_version +from openedx.core.lib.features_setting_proxy import FeaturesProxy + +# A proxy for feature flags stored in the settings namespace +FEATURES = FeaturesProxy(globals()) # pylint: enable=useless-suppression @@ -110,387 +114,385 @@ FAVICON_PATH = 'images/favicon.ico' # templates. STUDIO_NAME = _("Your Platform Studio") STUDIO_SHORT_NAME = _("Studio") -FEATURES = { - 'GITHUB_PUSH': False, - # See annotations in lms/envs/common.py for details. - 'ENABLE_DISCUSSION_SERVICE': True, - # See annotations in lms/envs/common.py for details. - 'ENABLE_TEXTBOOK': True, +# FEATURES - # When True, all courses will be active, regardless of start date - # DO NOT SET TO True IN THIS FILE - # Doing so will cause all courses to be released on production - 'DISABLE_START_DATES': False, +GITHUB_PUSH = False - # email address for studio staff (eg to request course creation) - 'STUDIO_REQUEST_EMAIL': '', +# See annotations in lms/envs/common.py for details. +ENABLE_DISCUSSION_SERVICE = True +# See annotations in lms/envs/common.py for details. +ENABLE_TEXTBOOK = True - # Segment - must explicitly turn it on for production - 'CMS_SEGMENT_KEY': None, +# When True, all courses will be active, regardless of start date +# DO NOT SET TO True IN THIS FILE +# Doing so will cause all courses to be released on production +DISABLE_START_DATES = False - # Enable URL that shows information about the status of various services - 'ENABLE_SERVICE_STATUS': False, +# email address for studio staff (eg to request course creation) +STUDIO_REQUEST_EMAIL = '' - # Don't autoplay videos for course authors - 'AUTOPLAY_VIDEOS': False, +# Segment - must explicitly turn it on for production +CMS_SEGMENT_KEY = None - # Move the course author to next page when a video finishes. Set to True to - # show an auto-advance button in videos. If False, videos never auto-advance. - 'ENABLE_AUTOADVANCE_VIDEOS': False, +# Enable URL that shows information about the status of various services +ENABLE_SERVICE_STATUS = False - # If set to True, new Studio users won't be able to author courses unless - # an Open edX admin has added them to the course creator group. - 'ENABLE_CREATOR_GROUP': True, +# Don't autoplay videos for course authors +AUTOPLAY_VIDEOS = False - # If set to True, organization staff members can create libraries for their specific - # organization and no other organizations. They do not need to be course creators, - # even when ENABLE_CREATOR_GROUP is True. - 'ENABLE_ORGANIZATION_STAFF_ACCESS_FOR_CONTENT_LIBRARIES': True, +# Move the course author to next page when a video finishes. Set to True to +# show an auto-advance button in videos. If False, videos never auto-advance. +ENABLE_AUTOADVANCE_VIDEOS = False - # Turn off account locking if failed login attempts exceeds a limit - 'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False, +# If set to True, new Studio users won't be able to author courses unless +# an Open edX admin has added them to the course creator group. +ENABLE_CREATOR_GROUP = True - # .. toggle_name: FEATURES['EDITABLE_SHORT_DESCRIPTION'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: This feature flag allows editing of short descriptions on the Schedule & Details page in - # Open edX Studio. Set to False if you want to disable the editing of the course short description. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-02-13 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2334 - 'EDITABLE_SHORT_DESCRIPTION': True, +# If set to True, organization staff members can create libraries for their specific +# organization and no other organizations. They do not need to be course creators, +# even when ENABLE_CREATOR_GROUP is True. +ENABLE_ORGANIZATION_STAFF_ACCESS_FOR_CONTENT_LIBRARIES = True - # Hide any Personally Identifiable Information from application logs - 'SQUELCH_PII_IN_LOGS': False, +# Turn off account locking if failed login attempts exceeds a limit +ENABLE_MAX_FAILED_LOGIN_ATTEMPTS = False - # Toggles the embargo functionality, which blocks users - # based on their location. - 'EMBARGO': False, +# .. toggle_name: settings.EDITABLE_SHORT_DESCRIPTION +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: This feature flag allows editing of short descriptions on the Schedule & Details page in +# Open edX Studio. Set to False if you want to disable the editing of the course short description. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-02-13 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2334 +EDITABLE_SHORT_DESCRIPTION = True - # Allow creating courses with non-ascii characters in the course id - 'ALLOW_UNICODE_COURSE_ID': False, +# Hide any Personally Identifiable Information from application logs +SQUELCH_PII_IN_LOGS = False - # Prevent concurrent logins per user - 'PREVENT_CONCURRENT_LOGINS': False, +# Toggles the embargo functionality, which blocks users +# based on their location. +EMBARGO = False - # Turn off Video Upload Pipeline through Studio, by default - 'ENABLE_VIDEO_UPLOAD_PIPELINE': False, +# Allow creating courses with non-ascii characters in the course id +ALLOW_UNICODE_COURSE_ID = False - # See annotations in lms/envs/common.py for details. - 'ENABLE_EDXNOTES': False, +# Prevent concurrent logins per user +PREVENT_CONCURRENT_LOGINS = False - # Toggle to enable coordination with the Publisher tool (keep in sync with lms/envs/common.py) - 'ENABLE_PUBLISHER': False, +# Turn off Video Upload Pipeline through Studio, by default +ENABLE_VIDEO_UPLOAD_PIPELINE = False - # Show a new field in "Advanced settings" that can store custom data about a - # course and that can be read from themes - 'ENABLE_OTHER_COURSE_SETTINGS': False, +# See annotations in lms/envs/common.py for details. +ENABLE_EDXNOTES = False - # Write new CSM history to the extended table. - # This will eventually default to True and may be - # removed since all installs should have the separate - # extended history table. This is needed in the LMS and CMS - # for migration consistency. - 'ENABLE_CSMH_EXTENDED': True, +# Toggle to enable coordination with the Publisher tool (keep in sync with lms/envs/common.py) +ENABLE_PUBLISHER = False - # Enable support for content libraries. Note that content libraries are - # only supported in courses using split mongo. - 'ENABLE_CONTENT_LIBRARIES': True, +# Show a new field in "Advanced settings" that can store custom data about a +# course and that can be read from themes +ENABLE_OTHER_COURSE_SETTINGS = False - # .. toggle_name: FEATURES['ENABLE_CONTENT_LIBRARIES_LTI_TOOL'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When set to True, Content Libraries in - # Studio can be used as an LTI 1.3 tool by external LTI platforms. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2021-08-17 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/27411 - 'ENABLE_CONTENT_LIBRARIES_LTI_TOOL': False, +# Write new CSM history to the extended table. +# This will eventually default to True and may be +# removed since all installs should have the separate +# extended history table. This is needed in the LMS and CMS +# for migration consistency. +ENABLE_CSMH_EXTENDED = True - # Milestones application flag - 'MILESTONES_APP': False, +# Enable support for content libraries. Note that content libraries are +# only supported in courses using split mongo. +ENABLE_CONTENT_LIBRARIES = True - # Prerequisite courses feature flag - 'ENABLE_PREREQUISITE_COURSES': False, +# .. toggle_name: settings.ENABLE_CONTENT_LIBRARIES_LTI_TOOL +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When set to True, Content Libraries in +# Studio can be used as an LTI 1.3 tool by external LTI platforms. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2021-08-17 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/27411 +ENABLE_CONTENT_LIBRARIES_LTI_TOOL = False - # Toggle course entrance exams feature - 'ENTRANCE_EXAMS': False, +# Milestones application flag +MILESTONES_APP = False - # Toggle platform-wide course licensing - 'LICENSING': False, +# Prerequisite courses feature flag +ENABLE_PREREQUISITE_COURSES = False - # Enable the courseware search functionality - 'ENABLE_COURSEWARE_INDEX': False, +# Toggle course entrance exams feature +ENTRANCE_EXAMS = False - # Enable content libraries (modulestore) search functionality - 'ENABLE_LIBRARY_INDEX': False, +# Toggle platform-wide course licensing +LICENSING = False - # .. toggle_name: FEATURES['ALLOW_COURSE_RERUNS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: This will allow staff member to re-run the course from the studio home page and will - # always use the split modulestore. When this is set to False, the Re-run Course link will not be available on - # the studio home page. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-02-13 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6965 - 'ALLOW_COURSE_RERUNS': True, +# Enable the courseware search functionality +ENABLE_COURSEWARE_INDEX = False - # Certificates Web/HTML Views - 'CERTIFICATES_HTML_VIEW': False, +# Enable content libraries (modulestore) search functionality +ENABLE_LIBRARY_INDEX = False - # Teams feature - 'ENABLE_TEAMS': True, +# .. toggle_name: settings.ALLOW_COURSE_RERUNS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: This will allow staff member to re-run the course from the studio home page and will +# always use the split modulestore. When this is set to False, the Re-run Course link will not be available on +# the studio home page. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-02-13 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6965 +ALLOW_COURSE_RERUNS = True - # Show video bumper in Studio - 'ENABLE_VIDEO_BUMPER': False, +# Certificates Web/HTML Views +CERTIFICATES_HTML_VIEW = False - # How many seconds to show the bumper again, default is 7 days: - 'SHOW_BUMPER_PERIODICITY': 7 * 24 * 3600, +# Teams feature +ENABLE_TEAMS = True - # Enable credit eligibility feature - 'ENABLE_CREDIT_ELIGIBILITY': ENABLE_CREDIT_ELIGIBILITY, +# Show video bumper in Studio +ENABLE_VIDEO_BUMPER = False - # Special Exams, aka Timed and Proctored Exams - 'ENABLE_SPECIAL_EXAMS': False, +# How many seconds to show the bumper again, default is 7 days: +SHOW_BUMPER_PERIODICITY = 7 * 24 * 3600 - # Show the language selector in the header - 'SHOW_HEADER_LANGUAGE_SELECTOR': False, +# Special Exams, aka Timed and Proctored Exams +ENABLE_SPECIAL_EXAMS = False - # At edX it's safe to assume that English transcripts are always available - # This is not the case for all installations. - # The default value in {lms,cms}/envs/common.py and xmodule/tests/test_video.py should be consistent. - 'FALLBACK_TO_ENGLISH_TRANSCRIPTS': True, +# Show the language selector in the header +SHOW_HEADER_LANGUAGE_SELECTOR = False - # Set this to False to facilitate cleaning up invalid xml from your modulestore. - 'ENABLE_XBLOCK_XML_VALIDATION': True, +# At edX it's safe to assume that English transcripts are always available +# This is not the case for all installations. +# The default value in {lms,cms}/envs/common.py and xmodule/tests/test_video.py should be consistent. +FALLBACK_TO_ENGLISH_TRANSCRIPTS = True - # Allow public account creation - 'ALLOW_PUBLIC_ACCOUNT_CREATION': True, +# Set this to False to facilitate cleaning up invalid xml from your modulestore. +ENABLE_XBLOCK_XML_VALIDATION = True - # Allow showing the registration links - 'SHOW_REGISTRATION_LINKS': True, +# Allow public account creation +ALLOW_PUBLIC_ACCOUNT_CREATION = True - # Whether or not the dynamic EnrollmentTrackUserPartition should be registered. - 'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': True, +# Allow showing the registration links +SHOW_REGISTRATION_LINKS = True - 'ENABLE_PASSWORD_RESET_FAILURE_EMAIL': False, +# Whether or not the dynamic EnrollmentTrackUserPartition should be registered. +ENABLE_ENROLLMENT_TRACK_USER_PARTITION = True - # Whether archived courses (courses with end dates in the past) should be - # shown in Studio in a separate list. - 'ENABLE_SEPARATE_ARCHIVED_COURSES': True, +ENABLE_PASSWORD_RESET_FAILURE_EMAIL = False - # For acceptance and load testing - 'AUTOMATIC_AUTH_FOR_TESTING': False, +# Whether archived courses (courses with end dates in the past) should be +# shown in Studio in a separate list. +ENABLE_SEPARATE_ARCHIVED_COURSES = True - # Prevent auto auth from creating superusers or modifying existing users - 'RESTRICT_AUTOMATIC_AUTH': True, +# For acceptance and load testing +AUTOMATIC_AUTH_FOR_TESTING = False - 'ENABLE_GRADE_DOWNLOADS': True, - 'ENABLE_MKTG_SITE': False, - 'ENABLE_DISCUSSION_HOME_PANEL': True, - 'ENABLE_CORS_HEADERS': False, - 'ENABLE_CROSS_DOMAIN_CSRF_COOKIE': False, - 'ENABLE_COUNTRY_ACCESS': False, - 'ENABLE_CREDIT_API': False, - 'ENABLE_OAUTH2_PROVIDER': False, - 'ENABLE_MOBILE_REST_API': False, - 'CUSTOM_COURSES_EDX': False, - 'ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES': True, - 'SHOW_FOOTER_LANGUAGE_SELECTOR': False, - 'ENABLE_ENROLLMENT_RESET': False, - # Settings for course import olx validation - 'ENABLE_COURSE_OLX_VALIDATION': False, - # .. toggle_name: FEATURES['DISABLE_MOBILE_COURSE_AVAILABLE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to remove Mobile Course Available UI Flag from Studio's Advanced Settings - # page else Mobile Course Available UI Flag will be available on Studio side. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2020-02-14 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/23073 - 'DISABLE_MOBILE_COURSE_AVAILABLE': False, +# Prevent auto auth from creating superusers or modifying existing users +RESTRICT_AUTOMATIC_AUTH = True - # .. toggle_name: FEATURES['ENABLE_CHANGE_USER_PASSWORD_ADMIN'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable changing a user password through django admin. This is disabled by - # default because enabling allows a method to bypass password policy. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2020-02-21 - # .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/21616' - 'ENABLE_CHANGE_USER_PASSWORD_ADMIN': False, +ENABLE_GRADE_DOWNLOADS = True +ENABLE_MKTG_SITE = False +ENABLE_DISCUSSION_HOME_PANEL = True +ENABLE_CORS_HEADERS = False +ENABLE_CROSS_DOMAIN_CSRF_COOKIE = False +ENABLE_COUNTRY_ACCESS = False +ENABLE_CREDIT_API = False +ENABLE_OAUTH2_PROVIDER = False +ENABLE_MOBILE_REST_API = False +CUSTOM_COURSES_EDX = False +ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES = True +SHOW_FOOTER_LANGUAGE_SELECTOR = False +ENABLE_ENROLLMENT_RESET = False +# Settings for course import olx validation +ENABLE_COURSE_OLX_VALIDATION = False +# .. toggle_name: settings.DISABLE_MOBILE_COURSE_AVAILABLE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to remove Mobile Course Available UI Flag from Studio's Advanced Settings +# page else Mobile Course Available UI Flag will be available on Studio side. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2020-02-14 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/23073 +DISABLE_MOBILE_COURSE_AVAILABLE = False - ### ORA Feature Flags ### - # .. toggle_name: FEATURES['ENABLE_ORA_ALL_FILE_URLS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: A "work-around" feature toggle meant to help in cases where some file uploads are not - # discoverable. If enabled, will iterate through all possible file key suffixes up to the max for displaying file - # metadata in staff assessments. - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2020-03-03 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://openedx.atlassian.net/browse/EDUCATOR-4951 - # .. toggle_warning: This temporary feature toggle does not have a target removal date. - 'ENABLE_ORA_ALL_FILE_URLS': False, +# .. toggle_name: settings.ENABLE_CHANGE_USER_PASSWORD_ADMIN +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable changing a user password through django admin. This is disabled by +# default because enabling allows a method to bypass password policy. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2020-02-21 +# .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/21616' +ENABLE_CHANGE_USER_PASSWORD_ADMIN = False - # .. toggle_name: FEATURES['ENABLE_ORA_USER_STATE_UPLOAD_DATA'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: A "work-around" feature toggle meant to help in cases where some file uploads are not - # discoverable. If enabled, will pull file metadata from StudentModule.state for display in staff assessments. - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2020-03-03 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://openedx.atlassian.net/browse/EDUCATOR-4951 - # .. toggle_warning: This temporary feature toggle does not have a target removal date. - 'ENABLE_ORA_USER_STATE_UPLOAD_DATA': False, +### ORA Feature Flags ### +# .. toggle_name: settings.ENABLE_ORA_ALL_FILE_URLS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: A "work-around" feature toggle meant to help in cases where some file uploads are not +# discoverable. If enabled, will iterate through all possible file key suffixes up to the max for displaying file +# metadata in staff assessments. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2020-03-03 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://openedx.atlassian.net/browse/EDUCATOR-4951 +# .. toggle_warning: This temporary feature toggle does not have a target removal date. +ENABLE_ORA_ALL_FILE_URLS = False - # .. toggle_name: FEATURES['DEPRECATE_OLD_COURSE_KEYS_IN_STUDIO'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Warn about removing support for deprecated course keys. - # To enable, set to True. - # To disable, set to False. - # To enable with a custom support deadline, set to an ISO-8601 date string: - # eg: '2020-09-01' - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2020-06-12 - # .. toggle_target_removal_date: 2021-04-01 - # .. toggle_warning: This can be removed once support is removed for deprecated - # course keys. - # .. toggle_tickets: https://openedx.atlassian.net/browse/DEPR-58 - 'DEPRECATE_OLD_COURSE_KEYS_IN_STUDIO': True, +# .. toggle_name: settings.ENABLE_ORA_USER_STATE_UPLOAD_DATA +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: A "work-around" feature toggle meant to help in cases where some file uploads are not +# discoverable. If enabled, will pull file metadata from StudentModule.state for display in staff assessments. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2020-03-03 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://openedx.atlassian.net/browse/EDUCATOR-4951 +# .. toggle_warning: This temporary feature toggle does not have a target removal date. +ENABLE_ORA_USER_STATE_UPLOAD_DATA = False - # .. toggle_name: FEATURES['DISABLE_COURSE_CREATION'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: If set to True, it disables the course creation functionality and hides the "New Course" - # button in studio. - # It is important to note that the value of this flag only affects if the user doesn't have a staff role, - # otherwise the course creation functionality will work as it should. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-12-02 - # .. toggle_warning: Another toggle DISABLE_LIBRARY_CREATION overrides DISABLE_COURSE_CREATION, if present. - 'DISABLE_COURSE_CREATION': False, +# .. toggle_name: settings.DEPRECATE_OLD_COURSE_KEYS_IN_STUDIO +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Warn about removing support for deprecated course keys. +# To enable, set to True. +# To disable, set to False. +# To enable with a custom support deadline, set to an ISO-8601 date string: +# eg: '2020-09-01' +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2020-06-12 +# .. toggle_target_removal_date: 2021-04-01 +# .. toggle_warning: This can be removed once support is removed for deprecated +# course keys. +# .. toggle_tickets: https://openedx.atlassian.net/browse/DEPR-58 +DEPRECATE_OLD_COURSE_KEYS_IN_STUDIO = True - # Can be turned off to disable the help link in the navbar - # .. toggle_name: FEATURES['ENABLE_HELP_LINK'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: When True, a help link is displayed on the main navbar. Set False to hide it. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2021-03-05 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/26106 - 'ENABLE_HELP_LINK': True, +# .. toggle_name: settings.DISABLE_COURSE_CREATION +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: If set to True, it disables the course creation functionality and hides the "New Course" +# button in studio. +# It is important to note that the value of this flag only affects if the user doesn't have a staff role, +# otherwise the course creation functionality will work as it should. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-12-02 +# .. toggle_warning: Another toggle DISABLE_LIBRARY_CREATION overrides DISABLE_COURSE_CREATION, if present. +DISABLE_COURSE_CREATION = False - # .. toggle_name: FEATURES['ENABLE_INTEGRITY_SIGNATURE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Whether to replace ID verification course/certificate requirement - # with an in-course Honor Code agreement - # (https://github.com/edx/edx-name-affirmation) - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2022-02-15 - # .. toggle_target_removal_date: None - # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1348' - 'ENABLE_INTEGRITY_SIGNATURE': False, +# Can be turned off to disable the help link in the navbar +# .. toggle_name: settings.ENABLE_HELP_LINK +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: When True, a help link is displayed on the main navbar. Set False to hide it. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2021-03-05 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/26106 +ENABLE_HELP_LINK = True - # .. toggle_name: FEATURES['ENABLE_LTI_PII_ACKNOWLEDGEMENT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enables the lti pii acknowledgement feature for a course - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2023-10 - # .. toggle_target_removal_date: None - # .. toggle_tickets: 'https://2u-internal.atlassian.net/browse/MST-2055' - 'ENABLE_LTI_PII_ACKNOWLEDGEMENT': False, +# .. toggle_name: settings.ENABLE_INTEGRITY_SIGNATURE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Whether to replace ID verification course/certificate requirement +# with an in-course Honor Code agreement +# (https://github.com/edx/edx-name-affirmation) +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2022-02-15 +# .. toggle_target_removal_date: None +# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1348' +ENABLE_INTEGRITY_SIGNATURE = False - # .. toggle_name: MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: If enabled, the Library Content Block is marked as complete when users view it. - # Otherwise (by default), all children of this block must be completed. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2022-03-22 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/28268 - # .. toggle_warning: For consistency in user-experience, keep the value in sync with the setting of the same name - # in the LMS and CMS. - 'MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW': False, +# .. toggle_name: settings.ENABLE_LTI_PII_ACKNOWLEDGEMENT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enables the lti pii acknowledgement feature for a course +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2023-10 +# .. toggle_target_removal_date: None +# .. toggle_tickets: 'https://2u-internal.atlassian.net/browse/MST-2055' +ENABLE_LTI_PII_ACKNOWLEDGEMENT = False - # .. toggle_name: FEATURES['DISABLE_UNENROLLMENT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to disable self-unenrollments via REST API. - # This also hides the "Unenroll" button on the Learner Dashboard. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2021-10-11 - # .. toggle_warning: For consistency in user experience, keep the value in sync with the setting of the same name - # in the LMS and CMS. - # .. toggle_tickets: 'https://github.com/open-craft/edx-platform/pull/429' - 'DISABLE_UNENROLLMENT': False, +# .. toggle_name: MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: If enabled, the Library Content Block is marked as complete when users view it. +# Otherwise (by default), all children of this block must be completed. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2022-03-22 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/28268 +# .. toggle_warning: For consistency in user-experience, keep the value in sync with the setting of the same name +# in the LMS and CMS. +MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW = False - # .. toggle_name: FEATURES['DISABLE_ADVANCED_SETTINGS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to `True` to disable the advanced settings page in Studio for all users except those - # having `is_superuser` or `is_staff` set to `True`. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2023-03-31 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/32015 - 'DISABLE_ADVANCED_SETTINGS': False, +# .. toggle_name: settings.DISABLE_UNENROLLMENT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to disable self-unenrollments via REST API. +# This also hides the "Unenroll" button on the Learner Dashboard. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2021-10-11 +# .. toggle_warning: For consistency in user experience, keep the value in sync with the setting of the same name +# in the LMS and CMS. +# .. toggle_tickets: 'https://github.com/open-craft/edx-platform/pull/429' +DISABLE_UNENROLLMENT = False - # .. toggle_name: FEATURES['ENABLE_SEND_XBLOCK_LIFECYCLE_EVENTS_OVER_BUS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enables sending xblock lifecycle events over the event bus. Used to create the - # EVENT_BUS_PRODUCER_CONFIG setting - # .. toggle_use_cases: opt_in - # .. toggle_creation_date: 2023-10-10 - # .. toggle_target_removal_date: 2023-10-12 - # .. toggle_warning: The default may be changed in a later release. See - # https://github.com/openedx/openedx-events/issues/265 - # .. toggle_tickets: https://github.com/edx/edx-arch-experiments/issues/381 - 'ENABLE_SEND_XBLOCK_LIFECYCLE_EVENTS_OVER_BUS': False, +# .. toggle_name: settings.DISABLE_ADVANCED_SETTINGS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to `True` to disable the advanced settings page in Studio for all users except those +# having `is_superuser` or `is_staff` set to `True`. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2023-03-31 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/32015 +DISABLE_ADVANCED_SETTINGS = False - # .. toggle_name: FEATURES['ENABLE_HIDE_FROM_TOC_UI'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When enabled, exposes hide_from_toc xblock attribute so course authors can configure it as - # a section visibility option in Studio. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2024-02-29 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/33952 - 'ENABLE_HIDE_FROM_TOC_UI': False, +# .. toggle_name: settings.ENABLE_SEND_XBLOCK_LIFECYCLE_EVENTS_OVER_BUS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enables sending xblock lifecycle events over the event bus. Used to create the +# EVENT_BUS_PRODUCER_CONFIG setting +# .. toggle_use_cases: opt_in +# .. toggle_creation_date: 2023-10-10 +# .. toggle_target_removal_date: 2023-10-12 +# .. toggle_warning: The default may be changed in a later release. See +# https://github.com/openedx/openedx-events/issues/265 +# .. toggle_tickets: https://github.com/edx/edx-arch-experiments/issues/381 +ENABLE_SEND_XBLOCK_LIFECYCLE_EVENTS_OVER_BUS = False - # .. toggle_name: FEATURES['ENABLE_GRADING_METHOD_IN_PROBLEMS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enables the grading method feature in capa problems. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2024-03-22 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/33911 - 'ENABLE_GRADING_METHOD_IN_PROBLEMS': False, +# .. toggle_name: settings.ENABLE_HIDE_FROM_TOC_UI +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When enabled, exposes hide_from_toc xblock attribute so course authors can configure it as +# a section visibility option in Studio. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-02-29 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/33952 +ENABLE_HIDE_FROM_TOC_UI = False - # .. toggle_name: FEATURES['BADGES_ENABLED'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable the Badges feature. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2024-04-10 - 'BADGES_ENABLED': False, +# .. toggle_name: settings.ENABLE_GRADING_METHOD_IN_PROBLEMS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enables the grading method feature in capa problems. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-03-22 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/33911 +ENABLE_GRADING_METHOD_IN_PROBLEMS = False - # .. toggle_name: FEATURES['IN_CONTEXT_DISCUSSION_ENABLED_DEFAULT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Set to False to disable in-context discussion for units by default. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2024-09-02 - 'IN_CONTEXT_DISCUSSION_ENABLED_DEFAULT': True, -} +# .. toggle_name: settings.BADGES_ENABLED +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable the Badges feature. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-04-10 +BADGES_ENABLED = False + +# .. toggle_name: settings.IN_CONTEXT_DISCUSSION_ENABLED_DEFAULT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Set to False to disable in-context discussion for units by default. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-09-02 +IN_CONTEXT_DISCUSSION_ENABLED_DEFAULT = True # .. toggle_name: ENABLE_COPPA_COMPLIANCE # .. toggle_implementation: DjangoSetting @@ -1072,7 +1074,7 @@ X_FRAME_OPTIONS = 'DENY' # .. setting_name: GIT_REPO_EXPORT_DIR # .. setting_default: '/edx/var/edxapp/export_course_repos' # .. setting_description: When courses are exported to git, either with the export_git management command or the git -# export view from the studio (when FEATURES['ENABLE_EXPORT_GIT'] is True), they are stored in this directory, which +# export view from the studio (when settings.ENABLE_EXPORT_GIT is True), they are stored in this directory, which # must exist at the time of the export. GIT_REPO_EXPORT_DIR = '/edx/var/edxapp/export_course_repos' # .. setting_name: GIT_EXPORT_DEFAULT_IDENT @@ -1716,7 +1718,6 @@ LOGIN_ISSUE_SUPPORT_LINK = '' ############################## EVENT TRACKING ################################# -CMS_SEGMENT_KEY = None TRACK_MAX_EVENT = 50000 TRACKING_BACKENDS = { @@ -1854,9 +1855,6 @@ MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB = 20 # a file that exceeds the above size MAX_ASSET_UPLOAD_FILE_SIZE_URL = "" -### Default value for entrance exam minimum score -ENTRANCE_EXAM_MIN_SCORE_PCT = 50 - ### Default language for a new course DEFAULT_COURSE_LANGUAGE = "en" @@ -2316,7 +2314,7 @@ FINANCIAL_REPORTS = { } ############# CORS headers for cross-domain requests ################# -if FEATURES.get('ENABLE_CORS_HEADERS'): +if ENABLE_CORS_HEADERS: CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_WHITELIST = () CORS_ORIGIN_ALLOW_ALL = False @@ -2614,11 +2612,11 @@ SIMPLE_HISTORY_DATE_INDEX = False def _should_send_xblock_events(settings): - return settings.FEATURES['ENABLE_SEND_XBLOCK_LIFECYCLE_EVENTS_OVER_BUS'] + return settings.ENABLE_SEND_XBLOCK_LIFECYCLE_EVENTS_OVER_BUS def _should_send_learning_badge_events(settings): - return settings.FEATURES['BADGES_ENABLED'] + return settings.BADGES_ENABLED # .. setting_name: EVENT_BUS_PRODUCER_CONFIG diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py index bda53a366c..d683f48708 100644 --- a/cms/envs/devstack.py +++ b/cms/envs/devstack.py @@ -50,7 +50,7 @@ FRONTEND_REGISTER_URL = LMS_ROOT_URL + '/register' ################################## Video Pipeline Settings ######################### -FEATURES['ENABLE_VIDEO_UPLOAD_PIPELINE'] = True +ENABLE_VIDEO_UPLOAD_PIPELINE = True ########################### PIPELINE ################################# @@ -126,7 +126,7 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing ################################ MILESTONES ################################ -FEATURES['MILESTONES_APP'] = True +MILESTONES_APP = True ########################### ORGANIZATIONS ################################# # Although production studio.edx.org disables `ORGANIZATIONS_AUTOCREATE`, @@ -136,16 +136,16 @@ FEATURES['MILESTONES_APP'] = True ORGANIZATIONS_AUTOCREATE = True ################################ ENTRANCE EXAMS ################################ -FEATURES['ENTRANCE_EXAMS'] = True +ENTRANCE_EXAMS = True ################################ COURSE LICENSES ################################ -FEATURES['LICENSING'] = True +LICENSING = True # Needed to enable licensing on video blocks XBLOCK_SETTINGS.update({'VideoBlock': {'licensing_enabled': True}}) ################################ SEARCH INDEX ################################ -FEATURES['ENABLE_COURSEWARE_INDEX'] = True -FEATURES['ENABLE_LIBRARY_INDEX'] = False +ENABLE_COURSEWARE_INDEX = True +ENABLE_LIBRARY_INDEX = False SEARCH_ENGINE = "search.elastic.ElasticSearchEngine" ELASTIC_SEARCH_CONFIG = [ @@ -157,22 +157,22 @@ ELASTIC_SEARCH_CONFIG = [ ] ################################ COURSE DISCUSSIONS ########################### -FEATURES['ENABLE_DISCUSSION_SERVICE'] = True +ENABLE_DISCUSSION_SERVICE = True ################################ CREDENTIALS ########################### CREDENTIALS_SERVICE_USERNAME = 'credentials_worker' ########################## Certificates Web/HTML View ####################### -FEATURES['CERTIFICATES_HTML_VIEW'] = True +CERTIFICATES_HTML_VIEW = True ########################## AUTHOR PERMISSION ####################### -FEATURES['ENABLE_CREATOR_GROUP'] = True +ENABLE_CREATOR_GROUP = True ########################## Library creation organizations restriction ####################### -FEATURES['ENABLE_ORGANIZATION_STAFF_ACCESS_FOR_CONTENT_LIBRARIES'] = True +ENABLE_ORGANIZATION_STAFF_ACCESS_FOR_CONTENT_LIBRARIES = True ################### FRONTEND APPLICATION PUBLISHER URL ################### -FEATURES['FRONTEND_APP_PUBLISHER_URL'] = 'http://localhost:18400' +FRONTEND_APP_PUBLISHER_URL = 'http://localhost:18400' ################### FRONTEND APPLICATION COURSE AUTHORING ################### COURSE_AUTHORING_MICROFRONTEND_URL = 'http://localhost:2001' @@ -252,13 +252,13 @@ MODULESTORE = convert_module_store_setting_if_needed(MODULESTORE) SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' ############# CORS headers for cross-domain requests ################# -FEATURES['ENABLE_CORS_HEADERS'] = True +ENABLE_CORS_HEADERS = True CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_ALLOW_ALL = True ################### Special Exams (Proctoring) and Prereqs ################### -FEATURES['ENABLE_SPECIAL_EXAMS'] = True -FEATURES['ENABLE_PREREQUISITE_COURSES'] = True +ENABLE_SPECIAL_EXAMS = True +ENABLE_PREREQUISITE_COURSES = True # Used in edx-proctoring for ID generation in lieu of SECRET_KEY - dummy value # (ref MST-637) diff --git a/cms/envs/production.py b/cms/envs/production.py index 346a60da66..f2d7ab88e4 100644 --- a/cms/envs/production.py +++ b/cms/envs/production.py @@ -29,6 +29,11 @@ from openedx.core.lib.derived import derive_settings # lint-amnesty, pylint: di from openedx.core.lib.logsettings import get_logger_config # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.modulestore_settings import convert_module_store_setting_if_needed # lint-amnesty, pylint: disable=wrong-import-order +from openedx.core.lib.features_setting_proxy import FeaturesProxy + +# A proxy for feature flags stored in the settings namespace +FEATURES = FeaturesProxy(globals()) + def get_env_setting(setting): """ Get the environment setting or return exception """ @@ -286,7 +291,7 @@ EVENT_TRACKING_BACKENDS['segmentio']['OPTIONS']['processors'][0]['OPTIONS']['whi ) -if FEATURES['ENABLE_COURSEWARE_INDEX'] or FEATURES['ENABLE_LIBRARY_INDEX']: +if ENABLE_COURSEWARE_INDEX or ENABLE_LIBRARY_INDEX: # Use ElasticSearch for the search engine SEARCH_ENGINE = "search.elastic.ElasticSearchEngine" @@ -302,7 +307,7 @@ XBLOCK_SETTINGS.setdefault("VideoBlock", {})['YOUTUBE_API_KEY'] = YOUTUBE_API_KE JWT_AUTH.update(_YAML_TOKENS.get('JWT_AUTH', {})) ######################## CUSTOM COURSES for EDX CONNECTOR ###################### -if FEATURES['CUSTOM_COURSES_EDX']: +if CUSTOM_COURSES_EDX: INSTALLED_APPS.append('openedx.core.djangoapps.ccxcon.apps.CCXConnectorConfig') ########################## Extra middleware classes ####################### @@ -347,7 +352,7 @@ add_plugins(__name__, ProjectType.CMS, SettingsType.PRODUCTION) #### ############# CORS headers for cross-domain requests ################# -if FEATURES['ENABLE_CORS_HEADERS']: +if ENABLE_CORS_HEADERS: CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_WHITELIST = _YAML_TOKENS.get('CORS_ORIGIN_WHITELIST', ()) CORS_ORIGIN_ALLOW_ALL = _YAML_TOKENS.get('CORS_ORIGIN_ALLOW_ALL', False) diff --git a/cms/envs/test.py b/cms/envs/test.py index 23131c699f..1e249f18d0 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -23,6 +23,8 @@ from openedx.core.lib.derived import derive_settings from xmodule.modulestore.modulestore_settings import update_module_store_settings # pylint: disable=wrong-import-order +from openedx.core.lib.features_setting_proxy import FeaturesProxy + from .common import * # import settings from LMS for consistent behavior with CMS @@ -46,6 +48,8 @@ from lms.envs.test import ( # pylint: disable=wrong-import-order, disable=unuse XBLOCK_RUNTIME_V2_EPHEMERAL_DATA_CACHE, ) +# A proxy for feature flags stored in the settings namespace +FEATURES = FeaturesProxy(globals()) # Include a non-ascii character in STUDIO_NAME and STUDIO_SHORT_NAME to uncover possible # UnicodeEncodeErrors in tests. Also use lazy text to reveal possible json dumps errors @@ -72,7 +76,7 @@ DATA_DIR = TEST_ROOT / "data" COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data" # For testing "push to lms" -FEATURES["ENABLE_EXPORT_GIT"] = True +ENABLE_EXPORT_GIT = True GIT_REPO_EXPORT_DIR = TEST_ROOT / "export_course_repos" # TODO (cpennington): We need to figure out how envs/test.py can inject things into common.py so that we don't have to repeat this sort of thing # lint-amnesty, pylint: disable=line-too-long @@ -203,51 +207,50 @@ PASSWORD_HASHERS = [ # No segment key CMS_SEGMENT_KEY = None -FEATURES["DISABLE_SET_JWT_COOKIES_FOR_TESTS"] = True +DISABLE_SET_JWT_COOKIES_FOR_TESTS = True -FEATURES["ENABLE_SERVICE_STATUS"] = True +ENABLE_SERVICE_STATUS = True # Toggles embargo on for testing -FEATURES["EMBARGO"] = True +EMBARGO = True TEST_THEME = COMMON_ROOT / "test" / "test-theme" # For consistency in user-experience, keep the value of this setting in sync with # the one in lms/envs/test.py -FEATURES["ENABLE_DISCUSSION_SERVICE"] = False +ENABLE_DISCUSSION_SERVICE = False # Enable a parental consent age limit for testing PARENTAL_CONSENT_AGE_LIMIT = 13 # Enable certificates for the tests -FEATURES["CERTIFICATES_HTML_VIEW"] = True +CERTIFICATES_HTML_VIEW = True # Enable content libraries code for the tests -FEATURES["ENABLE_CONTENT_LIBRARIES"] = True +ENABLE_CONTENT_LIBRARIES = True -FEATURES["ENABLE_EDXNOTES"] = True +ENABLE_EDXNOTES = True # MILESTONES -FEATURES["MILESTONES_APP"] = True +MILESTONES_APP = True # ENTRANCE EXAMS -FEATURES["ENTRANCE_EXAMS"] = True -ENTRANCE_EXAM_MIN_SCORE_PCT = 50 +ENTRANCE_EXAMS = True VIDEO_CDN_URL = {"CN": "http://api.xuetangx.com/edx/video?s3_url="} # Courseware Search Index -FEATURES["ENABLE_COURSEWARE_INDEX"] = True -FEATURES["ENABLE_LIBRARY_INDEX"] = True +ENABLE_COURSEWARE_INDEX = True +ENABLE_LIBRARY_INDEX = True SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine" -FEATURES["ENABLE_ENROLLMENT_TRACK_USER_PARTITION"] = True +ENABLE_ENROLLMENT_TRACK_USER_PARTITION = True ########################## AUTHOR PERMISSION ####################### -FEATURES["ENABLE_CREATOR_GROUP"] = False +ENABLE_CREATOR_GROUP = False # teams feature -FEATURES["ENABLE_TEAMS"] = True +ENABLE_TEAMS = True # Dummy secret key for dev/test SECRET_KEY = "85920908f28904ed733fe576320db18cabd7b6cd" @@ -257,7 +260,7 @@ INSTALLED_APPS += [ "openedx.core.djangoapps.ccxcon.apps.CCXConnectorConfig", "common.djangoapps.third_party_auth.apps.ThirdPartyAuthConfig", ] -FEATURES["CUSTOM_COURSES_EDX"] = True +CUSTOM_COURSES_EDX = True ########################## VIDEO IMAGE STORAGE ############################ VIDEO_IMAGE_SETTINGS = dict( diff --git a/lms/djangoapps/discussion/settings/common.py b/lms/djangoapps/discussion/settings/common.py index 732bc24fd5..882687a2d6 100644 --- a/lms/djangoapps/discussion/settings/common.py +++ b/lms/djangoapps/discussion/settings/common.py @@ -10,7 +10,7 @@ def plugin_settings(settings): # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2015-06-15 # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/8474 - settings.FEATURES['ALLOW_HIDING_DISCUSSION_TAB'] = False + settings.ALLOW_HIDING_DISCUSSION_TAB = False settings.DISCUSSION_SETTINGS = { 'MAX_COMMENT_DEPTH': 2, 'COURSE_PUBLISH_TASK_DELAY': 30, diff --git a/lms/djangoapps/instructor/settings/common.py b/lms/djangoapps/instructor/settings/common.py index 052405ab32..c8609d8750 100644 --- a/lms/djangoapps/instructor/settings/common.py +++ b/lms/djangoapps/instructor/settings/common.py @@ -9,103 +9,101 @@ def plugin_settings(settings): ### Analytics Dashboard (Insights) settings settings.ANALYTICS_DASHBOARD_URL = "" settings.ANALYTICS_DASHBOARD_NAME = _('Your Platform Insights') - settings.FEATURES.update({ - # .. toggle_name: FEATURES['DISPLAY_ANALYTICS_ENROLLMENTS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Enable display of enrollment counts in instructor dashboard and - # analytics section. - # .. toggle_use_cases: opt_out - # .. toggle_creation_date: 2014-11-12 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/5838 - 'DISPLAY_ANALYTICS_ENROLLMENTS': True, + # .. toggle_name: FEATURES['DISPLAY_ANALYTICS_ENROLLMENTS'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: True + # .. toggle_description: Enable display of enrollment counts in instructor dashboard and + # analytics section. + # .. toggle_use_cases: opt_out + # .. toggle_creation_date: 2014-11-12 + # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/5838 + settings.DISPLAY_ANALYTICS_ENROLLMENTS = True - # .. toggle_name: FEATURES['ENABLE_CCX_ANALYTICS_DASHBOARD_URL'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Display the 'Analytics' tab in the instructor dashboard for CCX courses. - # Note: This has no effect unless ANALYTICS_DASHBOARD_URL is already set, because without that - # setting, the tab does not show up for any courses. - # .. toggle_use_cases: opt_in - # .. toggle_creation_date: 2016-10-07 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/13196 - 'ENABLE_CCX_ANALYTICS_DASHBOARD_URL': False, + # .. toggle_name: FEATURES['ENABLE_CCX_ANALYTICS_DASHBOARD_URL'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Display the 'Analytics' tab in the instructor dashboard for CCX courses. + # Note: This has no effect unless ANALYTICS_DASHBOARD_URL is already set, because without that + # setting, the tab does not show up for any courses. + # .. toggle_use_cases: opt_in + # .. toggle_creation_date: 2016-10-07 + # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/13196 + settings.ENABLE_CCX_ANALYTICS_DASHBOARD_URL = False - # .. setting_name: FEATURES['MAX_ENROLLMENT_INSTR_BUTTONS'] - # .. setting_default: 200 - # .. setting_description: Disable instructor dashboard buttons for downloading course data - # when enrollment exceeds this number. The number indicates the maximum allowed enrollments - # for the course to be considered "small". Courses exceeding the upper limit of "small" - # courses will have disabled buttons at the instructor dashboard. - 'MAX_ENROLLMENT_INSTR_BUTTONS': 200, + # .. setting_name: FEATURES['MAX_ENROLLMENT_INSTR_BUTTONS'] + # .. setting_default: 200 + # .. setting_description: Disable instructor dashboard buttons for downloading course data + # when enrollment exceeds this number. The number indicates the maximum allowed enrollments + # for the course to be considered "small". Courses exceeding the upper limit of "small" + # courses will have disabled buttons at the instructor dashboard. + settings.MAX_ENROLLMENT_INSTR_BUTTONS = 200 - # .. toggle_name: FEATURES['ENABLE_GRADE_DOWNLOADS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable grade CSV downloads from the instructor dashboard. Grade - # calculation started from the instructor dashboard will write grades CSV files to the - # configured storage backend and give links for downloads. - # .. toggle_use_cases: opt_in - # .. toggle_creation_date: 2016-07-06 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/11286 - 'ENABLE_GRADE_DOWNLOADS': False, + # .. toggle_name: FEATURES['ENABLE_GRADE_DOWNLOADS'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Enable grade CSV downloads from the instructor dashboard. Grade + # calculation started from the instructor dashboard will write grades CSV files to the + # configured storage backend and give links for downloads. + # .. toggle_use_cases: opt_in + # .. toggle_creation_date: 2016-07-06 + # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/11286 + settings.ENABLE_GRADE_DOWNLOADS = False - # .. toggle_name: FEATURES['ALLOW_COURSE_STAFF_GRADE_DOWNLOADS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable to give course staff unrestricted access to grade downloads; - # if set to False, only edX superusers can perform the downloads. - # .. toggle_use_cases: opt_in - # .. toggle_creation_date: 2018-03-26 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/1750 - 'ALLOW_COURSE_STAFF_GRADE_DOWNLOADS': False, + # .. toggle_name: FEATURES['ALLOW_COURSE_STAFF_GRADE_DOWNLOADS'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Enable to give course staff unrestricted access to grade downloads; + # if set to False, only edX superusers can perform the downloads. + # .. toggle_use_cases: opt_in + # .. toggle_creation_date: 2018-03-26 + # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/1750 + settings.ALLOW_COURSE_STAFF_GRADE_DOWNLOADS = False - # .. toggle_name: FEATURES['ALLOW_AUTOMATED_SIGNUPS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable to show a section in the membership tab of the instructor - # dashboard to allow an upload of a CSV file that contains a list of new accounts to create - # and register for course. - # .. toggle_use_cases: opt_in - # .. toggle_creation_date: 2014-10-21 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/5670 - 'ALLOW_AUTOMATED_SIGNUPS': False, + # .. toggle_name: FEATURES['ALLOW_AUTOMATED_SIGNUPS'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Enable to show a section in the membership tab of the instructor + # dashboard to allow an upload of a CSV file that contains a list of new accounts to create + # and register for course. + # .. toggle_use_cases: opt_in + # .. toggle_creation_date: 2014-10-21 + # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/5670 + settings.ALLOW_AUTOMATED_SIGNUPS = False - # .. toggle_name: FEATURES['ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When True, the CSV file that contains a list of - # new accounts to create and register for a course in the membership - # tab of the instructor dashboard will accept the cohort name to - # assign the new user and the enrollment course mode. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2021-10-26 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/21260 - 'ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS': False, + # .. toggle_name: FEATURES['ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: When True, the CSV file that contains a list of + # new accounts to create and register for a course in the membership + # tab of the instructor dashboard will accept the cohort name to + # assign the new user and the enrollment course mode. + # .. toggle_use_cases: open_edx + # .. toggle_creation_date: 2021-10-26 + # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/21260 + settings.ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS = False - # .. toggle_name: FEATURES['CERTIFICATES_INSTRUCTOR_GENERATION'] # lint-amnesty, pylint: disable=annotation-missing-token - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable to allow batch generation of certificates from the instructor dashboard. - # In case of self-paced courses, the certificate generation button is hidden if certificate - # generation is not explicitly enabled globally or for the specific course. - # .. toggle_use_cases: opt_in - 'CERTIFICATES_INSTRUCTOR_GENERATION': False, + # .. toggle_name: FEATURES['CERTIFICATES_INSTRUCTOR_GENERATION'] # lint-amnesty, pylint: disable=annotation-missing-token + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Enable to allow batch generation of certificates from the instructor dashboard. + # In case of self-paced courses, the certificate generation button is hidden if certificate + # generation is not explicitly enabled globally or for the specific course. + # .. toggle_use_cases: opt_in + settings.CERTIFICATES_INSTRUCTOR_GENERATION = False - # .. toggle_name: FEATURES['ENABLE_CERTIFICATES_INSTRUCTOR_MANAGE] # lint-amnesty, pylint: disable=annotation-missing-token - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Allow course instructors to manage certificates from the instructor dashboard. - # .. toggle_use_cases: opt_in - 'ENABLE_CERTIFICATES_INSTRUCTOR_MANAGE': False, + # .. toggle_name: FEATURES['ENABLE_CERTIFICATES_INSTRUCTOR_MANAGE] # lint-amnesty, pylint: disable=annotation-missing-token + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Allow course instructors to manage certificates from the instructor dashboard. + # .. toggle_use_cases: opt_in + settings.ENABLE_CERTIFICATES_INSTRUCTOR_MANAGE = False - # .. toggle_name: FEATURES['BATCH_ENROLLMENT_NOTIFY_USERS_DEFAULT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Controls if the "Notify users by email" checkbox in the batch - # enrollment form on the instructor dashboard is already checked on page load or not. - # .. toggle_use_cases: opt_out - # .. toggle_creation_date: 2017-07-05 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/15392 - 'BATCH_ENROLLMENT_NOTIFY_USERS_DEFAULT': True, - }) + # .. toggle_name: FEATURES['BATCH_ENROLLMENT_NOTIFY_USERS_DEFAULT'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: True + # .. toggle_description: Controls if the "Notify users by email" checkbox in the batch + # enrollment form on the instructor dashboard is already checked on page load or not. + # .. toggle_use_cases: opt_out + # .. toggle_creation_date: 2017-07-05 + # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/15392 + settings.BATCH_ENROLLMENT_NOTIFY_USERS_DEFAULT = True diff --git a/lms/djangoapps/instructor/settings/production.py b/lms/djangoapps/instructor/settings/production.py index 7f91625587..86ec1d6d78 100644 --- a/lms/djangoapps/instructor/settings/production.py +++ b/lms/djangoapps/instructor/settings/production.py @@ -14,9 +14,9 @@ def plugin_settings(settings): "ANALYTICS_DASHBOARD_NAME", settings.ANALYTICS_DASHBOARD_NAME ) # Backward compatibility for deprecated feature names - if 'ENABLE_S3_GRADE_DOWNLOADS' in settings.FEATURES: + if hasattr(settings, 'ENABLE_S3_GRADE_DOWNLOADS'): warnings.warn( "'ENABLE_S3_GRADE_DOWNLOADS' is deprecated. Please use 'ENABLE_GRADE_DOWNLOADS' instead", DeprecationWarning, ) - settings.FEATURES['ENABLE_GRADE_DOWNLOADS'] = settings.FEATURES['ENABLE_S3_GRADE_DOWNLOADS'] + settings.ENABLE_GRADE_DOWNLOADS = settings.ENABLE_S3_GRADE_DOWNLOADS diff --git a/lms/djangoapps/instructor/settings/test.py b/lms/djangoapps/instructor/settings/test.py index b779b55352..92223867be 100644 --- a/lms/djangoapps/instructor/settings/test.py +++ b/lms/djangoapps/instructor/settings/test.py @@ -4,5 +4,5 @@ def plugin_settings(settings): """Settings for the instructor plugin.""" # Enable this feature for course staff grade downloads, to enable acceptance tests - settings.FEATURES['ENABLE_GRADE_DOWNLOADS'] = True - settings.FEATURES['ALLOW_COURSE_STAFF_GRADE_DOWNLOADS'] = True + settings.ENABLE_GRADE_DOWNLOADS = True + settings.ALLOW_COURSE_STAFF_GRADE_DOWNLOADS = True diff --git a/lms/envs/common.py b/lms/envs/common.py index 2075f3e70f..4740cf59fb 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -69,6 +69,10 @@ from openedx.core.release import doc_version from openedx.envs.common import * # pylint: disable=wildcard-import from lms.djangoapps.lms_xblock.mixin import LmsBlockMixin +from openedx.core.lib.features_setting_proxy import FeaturesProxy + +# A proxy for feature flags stored in the settings namespace +FEATURES = FeaturesProxy(globals()) ################################### FEATURES ################################### # .. setting_name: PLATFORM_NAME @@ -84,988 +88,1000 @@ PLATFORM_TWITTER_ACCOUNT = "@YourPlatformTwitterAccount" ENABLE_JASMINE = False -# Features -FEATURES = { - # .. toggle_name: FEATURES['DISPLAY_DEBUG_INFO_TO_STAFF'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Add a "Staff Debug" button to course blocks for debugging - # by course staff. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-09-04 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2425 - 'DISPLAY_DEBUG_INFO_TO_STAFF': True, - - # .. toggle_name: FEATURES['DISPLAY_HISTOGRAMS_TO_STAFF'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: This displays histograms in the Staff Debug Info panel to course staff. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-02-13 - # .. toggle_warning: Generating histograms requires scanning the courseware_studentmodule table on each view. This - # can make staff access to courseware very slow on large courses. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2425 - 'DISPLAY_HISTOGRAMS_TO_STAFF': False, # For large courses this slows down courseware access for staff. - - 'REROUTE_ACTIVATION_EMAIL': False, # nonempty string = address for all activation emails - - # .. toggle_name: FEATURES['DISABLE_START_DATES'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When True, all courses will be active, regardless of start - # date. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2012-07-24 - # .. toggle_warning: This will cause ALL courses to be immediately visible. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/17913 - ## DO NOT SET TO True IN THIS FILE - ## Doing so will cause all courses to be released on production - 'DISABLE_START_DATES': False, - - # .. toggle_name: FEATURES['ENABLE_DISCUSSION_SERVICE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: When True, it will enable the Discussion tab in courseware for all courses. Setting this - # to False will not contain inline discussion components and discussion tab in any courses. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2012-08-14 - # .. toggle_warning: If the discussion panel is present in the course and the value for this flag is False then, - # attempting to expand those components will cause errors. So, this should only be set to False with an LMS that - # is running courses that do not contain discussion components. - # For consistency in user-experience, keep the value in sync with the setting of the same name in the CMS. - 'ENABLE_DISCUSSION_SERVICE': True, - - # .. toggle_name: FEATURES['ENABLE_TEXTBOOK'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Add PDF and HTML textbook tabs to the courseware. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-03-27 - # .. toggle_warning: For consistency in user-experience, keep the value in sync with the setting of the same name - # in the CMS. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/3064 - 'ENABLE_TEXTBOOK': True, - - # .. toggle_name: FEATURES['ENABLE_DISCUSSION_HOME_PANEL'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Hides or displays a welcome panel under the Discussion tab, which includes a subscription - # on/off setting for discussion digest emails. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-07-30 - # .. toggle_warning: This should remain off in production until digest notifications are online. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/520 - 'ENABLE_DISCUSSION_HOME_PANEL': False, - - # .. toggle_name: FEATURES['ENABLE_DISCUSSION_EMAIL_DIGEST'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set this to True if you want the discussion digest emails - # enabled automatically for new users. This will be set on all new account - # registrations. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-08-19 - # .. toggle_target_removal_date: None - # .. toggle_warning: It is not recommended to enable this feature if ENABLE_DISCUSSION_HOME_PANEL is not enabled, - # since subscribers who receive digests in that case will only be able to unsubscribe via links embedded in - # their emails, and they will have no way to resubscribe. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/4891 - 'ENABLE_DISCUSSION_EMAIL_DIGEST': False, - - # .. toggle_name: FEATURES['ENABLE_UNICODE_USERNAME'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set this to True to allow unicode characters in username. Enabling this will also - # automatically enable SOCIAL_AUTH_CLEAN_USERNAMES. When this is enabled, usernames will have to match the - # regular expression defined by USERNAME_REGEX_PARTIAL. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-06-27 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/14729 - 'ENABLE_UNICODE_USERNAME': False, - - # .. toggle_name: FEATURES['ENABLE_DJANGO_ADMIN_SITE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Set to False if you want to disable Django's admin site. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-09-26 - # .. toggle_warning: It is not recommended to disable this feature as there are many settings available on - # Django's admin site and will be inaccessible to the superuser. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/829 - 'ENABLE_DJANGO_ADMIN_SITE': True, - 'ENABLE_LMS_MIGRATION': False, - - # .. toggle_name: FEATURES['ENABLE_MASQUERADE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: None - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-04-13 - 'ENABLE_MASQUERADE': True, - - # .. toggle_name: FEATURES['DISABLE_LOGIN_BUTTON'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Removes the display of the login button in the navigation bar. - # Change is only at the UI level. Used in systems where login is automatic, eg MIT SSL - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-12-03 - 'DISABLE_LOGIN_BUTTON': False, - - # .. toggle_name: FEATURES['ENABLE_OAUTH2_PROVIDER'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable this feature to allow this Open edX platform to be an OAuth2 authentication - # provider. This is necessary to enable some other features, such as the REST API for the mobile application. - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2014-09-09 - # .. toggle_target_removal_date: None - # .. toggle_warning: This temporary feature toggle does not have a target removal date. - 'ENABLE_OAUTH2_PROVIDER': False, - - # .. toggle_name: FEATURES['ENABLE_XBLOCK_VIEW_ENDPOINT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable an API endpoint, named "xblock_view", to serve rendered XBlock views. This might be - # used by external applications. See for instance jquery-xblock (now unmaintained): - # https://github.com/openedx/jquery-xblock - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-03-14 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2968 - 'ENABLE_XBLOCK_VIEW_ENDPOINT': False, - - # Allows to configure the LMS to provide CORS headers to serve requests from other - # domains - 'ENABLE_CORS_HEADERS': False, - - # Can be turned off if course lists need to be hidden. Effects views and templates. - # .. toggle_name: FEATURES['COURSES_ARE_BROWSABLE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: When this is set to True, all the courses will be listed on the /courses page and Explore - # Courses link will be visible. Set to False if courses list and Explore Courses link need to be hidden. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-09-28 - # .. toggle_warning: This Effects views and templates. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/1073 - 'COURSES_ARE_BROWSABLE': True, - - # Can be turned off to disable the help link in the navbar - # .. toggle_name: FEATURES['ENABLE_HELP_LINK'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: When True, a help link is displayed on the main navbar. Set False to hide it. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2021-03-05 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/26106 - 'ENABLE_HELP_LINK': True, - - # .. toggle_name: FEATURES['HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When set, it hides the Courses list on the Learner Dashboard page if the learner has not - # yet activated the account and not enrolled in any courses. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2018-05-18 - # .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1814 - 'HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED': False, - - # .. toggle_name: FEATURES['ENABLE_STUDENT_HISTORY_VIEW'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: This provides a UI to show a student's submission history in a problem by the Staff Debug - # tool. Set to False if you want to hide Submission History from the courseware page. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-02-15 - # .. toggle_tickets: https://github.com/openedx/edx-platform/commit/8f17e6ae9ed76fa75b3caf867b65ccb632cb6870 - 'ENABLE_STUDENT_HISTORY_VIEW': True, - - # Turn on a page that lets staff enter Python code to be run in the - # sandbox, for testing whether it's enabled properly. - 'ENABLE_DEBUG_RUN_PYTHON': False, - - # Enable URL that shows information about the status of various services - 'ENABLE_SERVICE_STATUS': False, - - # Don't autoplay videos for students - 'AUTOPLAY_VIDEOS': False, - - # Move the student to next page when a video finishes. Set to True to show - # an auto-advance button in videos. If False, videos never auto-advance. - 'ENABLE_AUTOADVANCE_VIDEOS': False, - - # Enable instructor dash to submit background tasks - 'ENABLE_INSTRUCTOR_BACKGROUND_TASKS': True, - - # Enable instructor to assign individual due dates - # Note: In order for this feature to work, you must also add - # 'lms.djangoapps.courseware.student_field_overrides.IndividualStudentOverrideProvider' to - # the setting FIELD_OVERRIDE_PROVIDERS, in addition to setting this flag to - # True. - 'INDIVIDUAL_DUE_DATES': False, - - # .. toggle_name: CUSTOM_COURSES_EDX - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable Custom Courses for edX, a feature that is more commonly known as - # CCX. Documentation for configuring and using this feature is available at - # https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/enable_ccx.html - # .. toggle_warning: When set to true, 'lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider' will - # be added to MODULESTORE_FIELD_OVERRIDE_PROVIDERS - # .. toggle_use_cases: opt_in, circuit_breaker - # .. toggle_creation_date: 2015-04-10 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6636 - 'CUSTOM_COURSES_EDX': False, - - # Toggle to enable certificates of courses on dashboard - 'ENABLE_VERIFIED_CERTIFICATES': False, - # Settings for course import olx validation - 'ENABLE_COURSE_OLX_VALIDATION': False, - - # .. toggle_name: FEATURES['DISABLE_HONOR_CERTIFICATES'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to disable honor certificates. Typically used when your installation only - # allows verified certificates, like courses.edx.org. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2019-05-14 - # .. toggle_tickets: https://openedx.atlassian.net/browse/PROD-269 - 'DISABLE_HONOR_CERTIFICATES': False, # Toggle to disable honor certificates - - 'DISABLE_AUDIT_CERTIFICATES': False, # Toggle to disable audit certificates - - # .. toggle_name: FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to perform acceptance and load test. Auto auth view is responsible for load - # testing and is controlled by this feature flag. Session verification (of CacheBackedAuthenticationMiddleware) - # is a security feature, but it can be turned off by enabling this feature flag. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-07-25 - # .. toggle_warning: If this has been set to True then the account activation email will be skipped. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/417 - 'AUTOMATIC_AUTH_FOR_TESTING': False, - - # .. toggle_name: FEATURES['RESTRICT_AUTOMATIC_AUTH'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Prevent auto auth from creating superusers or modifying existing users. Auto auth is a - # mechanism where superusers can simply modify attributes of other users by accessing the "/auto_auth url" with - # the right - # querystring parameters. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2018-05-07 - # .. toggle_tickets: https://openedx.atlassian.net/browse/TE-2545 - 'RESTRICT_AUTOMATIC_AUTH': True, - - # .. toggle_name: FEATURES['ENABLE_LOGIN_MICROFRONTEND'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable the login micro frontend. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2018-05-07 - # .. toggle_warning: The login MFE domain name should be listed in LOGIN_REDIRECT_WHITELIST. - 'ENABLE_LOGIN_MICROFRONTEND': False, - - # .. toggle_name: FEATURES['SKIP_EMAIL_VALIDATION'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Turn this on to skip sending emails for user validation. - # Beware, as this leaves the door open to potential spam abuse. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2018-05-07 - # .. toggle_warning: The login MFE domain name should be listed in LOGIN_REDIRECT_WHITELIST. - 'SKIP_EMAIL_VALIDATION': False, - - # .. toggle_name: FEATURES['ENABLE_COSMETIC_DISPLAY_PRICE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable the display of "cosmetic_display_price", set in a course advanced settings. This - # cosmetic price is used when there is no registration price associated to the course. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-10-10 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6876 - # .. toggle_warning: The use case of this feature toggle is uncertain. - 'ENABLE_COSMETIC_DISPLAY_PRICE': False, - - # Automatically approve student identity verification attempts - # .. toggle_name: FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: If set to True, then we want to skip posting anything to Software Secure. Bypass posting - # anything to Software Secure if the auto verify feature for testing is enabled. We actually don't even create - # the message because that would require encryption and message signing that rely on settings.VERIFY_STUDENT - # values that aren't set in dev. So we just pretend like we successfully posted and automatically approve student - # identity verification attempts. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2013-10-03 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/1184 - 'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': False, - - # Maximum number of rows to include in the csv file for downloading problem responses. - 'MAX_PROBLEM_RESPONSES_COUNT': 5000, - - 'ENABLED_PAYMENT_REPORTS': [ - "refund_report", - "itemized_purchase_report", - "university_revenue_share", - "certificate_status" - ], - - # Turn off account locking if failed login attempts exceeds a limit - # .. toggle_name: FEATURES['ENABLE_MAX_FAILED_LOGIN_ATTEMPTS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: This feature will keep track of the number of failed login attempts on a given user's - # email. If the number of consecutive failed login attempts - without a successful login at some point - reaches - # a configurable threshold (default 6), then the account will be locked for a configurable amount of seconds - # (30 minutes) which will prevent additional login attempts until this time period has passed. If a user - # successfully logs in, all the counter which tracks the number of failed attempts will be reset back to 0. If - # set to False then account locking will be disabled for failed login attempts. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-01-30 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2331 - 'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True, - - # Hide any Personally Identifiable Information from application logs - 'SQUELCH_PII_IN_LOGS': True, - - # .. toggle_name: FEATURES['EMBARGO'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Turns on embargo functionality, which blocks users from - # the site or courses based on their location. Embargo can restrict users by states - # and whitelist/blacklist (IP Addresses (ie. 10.0.0.0), Networks (ie. 10.0.0.0/24)), or the user profile country. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-02-27 - # .. toggle_target_removal_date: None - # .. toggle_warning: reverse proxy should be configured appropriately for example Client IP address headers - # (e.g HTTP_X_FORWARDED_FOR) should be configured. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2749 - 'EMBARGO': False, - - # Whether the Wiki subsystem should be accessible via the direct /wiki/ paths. Setting this to True means - # that people can submit content and modify the Wiki in any arbitrary manner. We're leaving this as True in the - # defaults, so that we maintain current behavior - 'ALLOW_WIKI_ROOT_ACCESS': True, - - # .. toggle_name: FEATURES['ENABLE_THIRD_PARTY_AUTH'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Turn on third-party auth. Disabled for now because full implementations are not yet - # available. Remember to run migrations if you enable this; we don't create tables by default. This feature can - # be enabled on a per-site basis. When enabling this feature, remember to define the allowed authentication - # backends with the AUTHENTICATION_BACKENDS setting. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-09-15 - 'ENABLE_THIRD_PARTY_AUTH': False, - - # .. toggle_name: FEATURES['ENABLE_MKTG_SITE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Toggle to enable alternate urls for marketing links. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-03-24 - # .. toggle_warning: When this is enabled, the MKTG_URLS setting should be defined. The use case of this feature - # toggle is uncertain. - 'ENABLE_MKTG_SITE': False, - - # Prevent concurrent logins per user - 'PREVENT_CONCURRENT_LOGINS': True, - - # .. toggle_name: FEATURES['ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: When a logged in user goes to the homepage ('/') the user will be redirected to the - # dashboard page when this flag is set to True - this is default Open edX behavior. Set to False to not redirect - # the user. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2014-09-16 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/5220 - 'ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER': True, - - # .. toggle_name: FEATURES['ENABLE_COURSE_SORTING_BY_START_DATE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: When a user goes to the homepage ('/') the user sees the courses listed in the - # announcement dates order - this is default Open edX behavior. Set to True to change the course sorting behavior - # by their start dates, latest first. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-03-27 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7548 - 'ENABLE_COURSE_SORTING_BY_START_DATE': True, - - # .. toggle_name: FEATURES['ENABLE_COURSE_HOME_REDIRECT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: When enabled, along with the ENABLE_MKTG_SITE feature toggle, users who attempt to access a - # course "about" page will be redirected to the course home url. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2019-01-15 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/19604 - 'ENABLE_COURSE_HOME_REDIRECT': True, - - # Expose Mobile REST API. Note that if you use this, you must also set - # ENABLE_OAUTH2_PROVIDER to True - 'ENABLE_MOBILE_REST_API': False, - - # .. toggle_name: FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Display the standard footer in the login page. This feature can be overridden by a site- - # specific configuration. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2016-06-24 - # .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1320 - 'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER': False, - - # Enable organizational email opt-in - 'ENABLE_MKTG_EMAIL_OPT_IN': False, - - # .. toggle_name: FEATURES['ENABLE_FOOTER_MOBILE_APP_LINKS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True if you want show the mobile app links (Apple App Store & Google Play Store) in - # the footer. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-01-13 - # .. toggle_warning: If you set this to True then you should also set your mobile application's app store and play - # store URLs in the MOBILE_STORE_URLS settings dictionary. These links are not part of the default theme. If you - # want these links on your footer then you should use the edx.org theme. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6588 - 'ENABLE_FOOTER_MOBILE_APP_LINKS': False, - - # Let students save and manage their annotations - # .. toggle_name: FEATURES['ENABLE_EDXNOTES'] - # .. toggle_implementation: SettingToggle - # .. toggle_default: False - # .. toggle_description: This toggle enables the students to save and manage their annotations in the - # course using the notes service. The bulk of the actual work in storing the notes is done by - # a separate service (see the edx-notes-api repo). - # .. toggle_warning: Requires the edx-notes-api service properly running and to have configured the django settings - # EDXNOTES_INTERNAL_API and EDXNOTES_PUBLIC_API. If you update this setting, also update it in Studio. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-01-04 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6321 - 'ENABLE_EDXNOTES': False, - - # Toggle to enable coordination with the Publisher tool (keep in sync with cms/envs/common.py) - 'ENABLE_PUBLISHER': False, - - # Milestones application flag - 'MILESTONES_APP': False, - - # Prerequisite courses feature flag - 'ENABLE_PREREQUISITE_COURSES': False, - - # For easily adding modes to courses during acceptance testing - 'MODE_CREATION_FOR_TESTING': False, - - # For caching programs in contexts where the LMS can only - # be reached over HTTP. - 'EXPOSE_CACHE_PROGRAMS_ENDPOINT': False, - - # Courseware search feature - # .. toggle_name: FEATURES['ENABLE_COURSEWARE_SEARCH'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When enabled, this adds a Search the course widget on the course outline and courseware - # pages for searching courseware data. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-01-29 - # .. toggle_warning: In order to get this working, your courses data should be indexed in Elasticsearch. You will - # see the search widget on the courseware page only if the DISABLE_COURSE_OUTLINE_PAGE_FLAG is set. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6506 - 'ENABLE_COURSEWARE_SEARCH': False, - - # .. toggle_name: FEATURES['ENABLE_COURSEWARE_SEARCH_FOR_COURSE_STAFF'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When enabled, this adds a Search the course widget on the course outline and courseware - # pages for searching courseware data but for course staff users only. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2019-12-06 - # .. toggle_warning: In order to get this working, your courses data should be indexed in Elasticsearch. If - # ENABLE_COURSEWARE_SEARCH is enabled then the search widget will be visible to all learners and this flag's - # value does not matter in that case. This flag is enabled in devstack by default. - # .. toggle_tickets: https://openedx.atlassian.net/browse/TNL-6931 - 'ENABLE_COURSEWARE_SEARCH_FOR_COURSE_STAFF': False, - - # Dashboard search feature - # .. toggle_name: FEATURES['ENABLE_DASHBOARD_SEARCH'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When enabled, this adds a Search Your Courses widget on the dashboard page for searching - # courseware data. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-01-29 - # .. toggle_warning: In order to get this working, your courses data should be indexed in Elasticsearch. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6506 - 'ENABLE_DASHBOARD_SEARCH': False, - - # log all information from cybersource callbacks - 'LOG_POSTPAY_CALLBACKS': True, - - # .. toggle_name: FEATURES['LICENSING'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Toggle platform-wide course licensing. The course.license attribute is then used to append - # license information to the courseware. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-05-14 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7315 - 'LICENSING': False, - - # .. toggle_name: FEATURES['CERTIFICATES_HTML_VIEW'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable course certificates on your instance of Open edX. - # .. toggle_warning: You must enable this feature flag in both Studio and the LMS and complete the configuration tasks - # described here: - # https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/enable_certificates.html pylint: disable=line-too-long,useless-suppression - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-03-13 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7113 - 'CERTIFICATES_HTML_VIEW': False, - - # .. toggle_name: FEATURES['CUSTOM_CERTIFICATE_TEMPLATES_ENABLED'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable custom certificate templates which are configured via Django admin. - # .. toggle_warning: None - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-08-13 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://openedx.atlassian.net/browse/SOL-1044 - 'CUSTOM_CERTIFICATE_TEMPLATES_ENABLED': False, - - # .. toggle_name: FEATURES['ENABLE_COURSE_DISCOVERY'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Add a course search widget to the LMS for searching courses. When this is enabled, the - # latest courses are no longer displayed on the LMS landing page. Also, an "Explore Courses" item is added to the - # navbar. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-04-23 - # .. toggle_target_removal_date: None - # .. toggle_warning: The COURSE_DISCOVERY_MEANINGS setting should be properly defined. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7845 - 'ENABLE_COURSE_DISCOVERY': False, - - # .. toggle_name: FEATURES['ENABLE_COURSE_FILENAME_CCX_SUFFIX'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: If set to True, CCX ID will be included in the generated filename for CCX courses. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2021-03-16 - # .. toggle_target_removal_date: None - # .. toggle_tickets: None - # .. toggle_warning: Turning this feature ON will affect all generated filenames which are related to CCX courses. - 'ENABLE_COURSE_FILENAME_CCX_SUFFIX': False, - - # Setting for overriding default filtering facets for Course discovery - # COURSE_DISCOVERY_FILTERS = ["org", "language", "modes"] - - # Software secure fake page feature flag - 'ENABLE_SOFTWARE_SECURE_FAKE': False, - - # Teams feature - 'ENABLE_TEAMS': True, - - # Show video bumper in LMS - 'ENABLE_VIDEO_BUMPER': False, - - # How many seconds to show the bumper again, default is 7 days: - 'SHOW_BUMPER_PERIODICITY': 7 * 24 * 3600, - - # .. toggle_name: FEATURES['ENABLE_SPECIAL_EXAMS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable to use special exams, aka timed and proctored exams. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-09-04 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/9744 - 'ENABLE_SPECIAL_EXAMS': False, - - # .. toggle_name: FEATURES['ENABLE_LTI_PROVIDER'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When set to True, Open edX site can be used as an LTI Provider to other systems - # and applications. - # .. toggle_warning: After enabling this feature flag there are multiple steps involved to configure edX - # as LTI provider. Full guide is available here: - # https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/lti/index.html - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2015-04-24 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7689 - 'ENABLE_LTI_PROVIDER': False, - - # .. toggle_name: FEATURES['SHOW_HEADER_LANGUAGE_SELECTOR'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When set to True, language selector will be visible in the header. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-05-25 - # .. toggle_warning: You should set the languages in the DarkLangConfig table to get this working. If you have - # not set any languages in the DarkLangConfig table then the language selector will not be visible in the header. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/15133 - 'SHOW_HEADER_LANGUAGE_SELECTOR': False, - - # At edX it's safe to assume that English transcripts are always available - # This is not the case for all installations. - # The default value in {lms,cms}/envs/common.py and xmodule/tests/test_video.py should be consistent. - 'FALLBACK_TO_ENGLISH_TRANSCRIPTS': True, - - # .. toggle_name: FEATURES['SHOW_FOOTER_LANGUAGE_SELECTOR'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When set to True, language selector will be visible in the footer. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-05-25 - # .. toggle_warning: LANGUAGE_COOKIE_NAME is required to use footer-language-selector, set it if it has not been set. - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/15133 - 'SHOW_FOOTER_LANGUAGE_SELECTOR': False, - - # .. toggle_name: FEATURES['ENABLE_CSMH_EXTENDED'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Write Courseware Student Module History (CSMH) to the extended table: this logs all - # student activities to MySQL, in a separate database. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2020-11-05 - # .. toggle_warning: Even though most Open edX instances run with a separate CSMH database, it may not always be - # the case. When disabling this feature flag, remember to remove "lms.djangoapps.coursewarehistoryextended" - # from the INSTALLED_APPS and the "StudentModuleHistoryExtendedRouter" from the DATABASE_ROUTERS. - 'ENABLE_CSMH_EXTENDED': True, - - # Read from both the CSMH and CSMHE history tables. - # This is the default, but can be disabled if all history - # lives in the Extended table, saving the frontend from - # making multiple queries. - 'ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES': True, - - # Set this to False to facilitate cleaning up invalid xml from your modulestore. - 'ENABLE_XBLOCK_XML_VALIDATION': True, - - # .. toggle_name: FEATURES['ALLOW_PUBLIC_ACCOUNT_CREATION'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Allow public account creation. If this is disabled, users will no longer have access to - # the signup page. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-04-12 - # .. toggle_tickets: https://openedx.atlassian.net/browse/YONK-513 - 'ALLOW_PUBLIC_ACCOUNT_CREATION': True, - - # .. toggle_name: FEATURES['SHOW_REGISTRATION_LINKS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Allow registration links. If this is disabled, users will no longer see buttons to the - # the signup page. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2023-03-27 - 'SHOW_REGISTRATION_LINKS': True, - - # .. toggle_name: FEATURES['ENABLE_COOKIE_CONSENT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enable header banner for cookie consent using this service: - # https://cookieconsent.insites.com/ - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-03-03 - # .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1560 - 'ENABLE_COOKIE_CONSENT': False, - - # Whether or not the dynamic EnrollmentTrackUserPartition should be registered. - 'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': True, - - # Enable one click program purchase - # See LEARNER-493 - 'ENABLE_ONE_CLICK_PROGRAM_PURCHASE': False, - - # .. toggle_name: FEATURES['ALLOW_EMAIL_ADDRESS_CHANGE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Allow users to change their email address on the Account Settings page. If this is - # disabled, users will not be able to change their email address. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-06-26 - # .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1735 - 'ALLOW_EMAIL_ADDRESS_CHANGE': True, - - # .. toggle_name: FEATURES['ENABLE_BULK_ENROLLMENT_VIEW'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When set to True the bulk enrollment view is enabled and one can use it to enroll multiple - # users in a course using bulk enrollment API endpoint (/api/bulk_enroll/v1/bulk_enroll). - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-07-15 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/15006 - 'ENABLE_BULK_ENROLLMENT_VIEW': False, - - # Set to enable Enterprise integration - 'ENABLE_ENTERPRISE_INTEGRATION': False, - - # .. toggle_name: FEATURES['ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Whether HTML Block returns HTML content with the Course Blocks API when the API - # is called with student_view_data=html query parameter. - # .. toggle_warning: Because the Course Blocks API caches its data, the cache must be cleared (e.g. by - # re-publishing the course) for changes to this flag to take effect. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-08-28 - # .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1880 - 'ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA': False, - - # .. toggle_name: FEATURES['ENABLE_PASSWORD_RESET_FAILURE_EMAIL'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Whether to send an email for failed password reset attempts or not. This happens when a - # user asks for a password reset but they don't have an account associated to their email. This is useful for - # notifying users that they don't have an account associated with email addresses they believe they've registered - # with. This setting can be overridden by a site-specific configuration. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2017-07-20 - # .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1832 - 'ENABLE_PASSWORD_RESET_FAILURE_EMAIL': False, - - # Sets the default browser support. For more information go to http://browser-update.org/customize.html - 'UNSUPPORTED_BROWSER_ALERT_VERSIONS': "{i:10,f:-3,o:-3,s:-3,c:-3}", - - # .. toggle_name: FEATURES['ENABLE_ACCOUNT_DELETION'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: True - # .. toggle_description: Whether to display the account deletion section on Account Settings page. Set to False to - # hide this section. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2018-06-01 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/18298 - 'ENABLE_ACCOUNT_DELETION': True, - - # Enable feature to remove enrollments and users. Used to reset state of master's integration environments - 'ENABLE_ENROLLMENT_RESET': False, - 'DISABLE_MOBILE_COURSE_AVAILABLE': False, - - # .. toggle_name: FEATURES['ENABLE_CHANGE_USER_PASSWORD_ADMIN'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable changing a user password through django admin. This is disabled by - # default because enabling allows a method to bypass password policy. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2020-02-21 - # .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/21616' - 'ENABLE_CHANGE_USER_PASSWORD_ADMIN': False, - - # .. toggle_name: FEATURES['ENABLE_AUTHN_MICROFRONTEND'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the logistration. - # .. toggle_use_cases: temporary, open_edx - # .. toggle_creation_date: 2020-09-08 - # .. toggle_target_removal_date: None - # .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/24908' - # .. toggle_warning: Also set settings.AUTHN_MICROFRONTEND_URL for rollout. This temporary feature - # toggle does not have a target removal date. - 'ENABLE_AUTHN_MICROFRONTEND': os.environ.get("EDXAPP_ENABLE_AUTHN_MFE", False), - - # .. toggle_name: FEATURES['ENABLE_CATALOG_MICROFRONTEND'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the catalog. - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2025-05-15 - # .. toggle_target_removal_date: 2025-11-01 - 'ENABLE_CATALOG_MICROFRONTEND': False, - - ### ORA Feature Flags ### - # .. toggle_name: FEATURES['ENABLE_ORA_ALL_FILE_URLS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: A "work-around" feature toggle meant to help in cases where some file uploads are not - # discoverable. If enabled, will iterate through all possible file key suffixes up to the max for displaying - # file metadata in staff assessments. - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2020-03-03 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://openedx.atlassian.net/browse/EDUCATOR-4951 - # .. toggle_warning: This temporary feature toggle does not have a target removal date. - 'ENABLE_ORA_ALL_FILE_URLS': False, - - # .. toggle_name: FEATURES['ENABLE_ORA_USER_STATE_UPLOAD_DATA'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: A "work-around" feature toggle meant to help in cases where some file uploads are not - # discoverable. If enabled, will pull file metadata from StudentModule.state for display in staff assessments. - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2020-03-03 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://openedx.atlassian.net/browse/EDUCATOR-4951 - # .. toggle_warning: This temporary feature toggle does not have a target removal date. - 'ENABLE_ORA_USER_STATE_UPLOAD_DATA': False, - - # .. toggle_name: FEATURES['ENABLE_ORA_USERNAMES_ON_DATA_EXPORT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to add deanonymized usernames to ORA data - # report. - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2020-06-11 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://openedx.atlassian.net/browse/TNL-7273 - # .. toggle_warning: This temporary feature toggle does not have a target removal date. - 'ENABLE_ORA_USERNAMES_ON_DATA_EXPORT': False, - - # .. toggle_name: FEATURES['ENABLE_COURSE_ASSESSMENT_GRADE_CHANGE_SIGNAL'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to start sending signals for assessment level grade updates. Notably, the only - # handler of this signal at the time of this writing sends assessment updates to enterprise integrated channels. - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2020-12-09 - # .. toggle_target_removal_date: 2021-02-01 - # .. toggle_tickets: https://openedx.atlassian.net/browse/ENT-3818 - 'ENABLE_COURSE_ASSESSMENT_GRADE_CHANGE_SIGNAL': False, - - # .. toggle_name: FEATURES['ALLOW_ADMIN_ENTERPRISE_COURSE_ENROLLMENT_DELETION'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: If true, allows for the deletion of EnterpriseCourseEnrollment records via Django Admin. - # .. toggle_use_cases: opt_in - # .. toggle_creation_date: 2021-01-27 - # .. toggle_tickets: https://openedx.atlassian.net/browse/ENT-4022 - 'ALLOW_ADMIN_ENTERPRISE_COURSE_ENROLLMENT_DELETION': False, - - # .. toggle_name: FEATURES['ENABLE_BULK_USER_RETIREMENT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable bulk user retirement through REST API. This is disabled by - # default. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2021-03-11 - # .. toggle_target_removal_date: None - # .. toggle_warning: None - # .. toggle_tickets: 'https://openedx.atlassian.net/browse/OSPR-5290' - 'ENABLE_BULK_USER_RETIREMENT': False, - - # .. toggle_name: FEATURES['ENABLE_INTEGRITY_SIGNATURE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Whether to display honor code agreement for learners before their first grade assignment - # (https://github.com/edx/edx-name-affirmation) - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2022-02-15 - # .. toggle_target_removal_date: None - # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1348' - 'ENABLE_INTEGRITY_SIGNATURE': False, - - # .. toggle_name: FEATURES['ENABLE_LTI_PII_ACKNOWLEDGEMENT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enables the lti pii acknowledgement feature for a course - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2023-10 - # .. toggle_target_removal_date: None - # .. toggle_tickets: 'https://2u-internal.atlassian.net/browse/MST-2055' - 'ENABLE_LTI_PII_ACKNOWLEDGEMENT': False, - - # .. toggle_name: FEATURES['ENABLE_NEW_BULK_EMAIL_EXPERIENCE'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When true, replaces the bulk email tool found on the - # instructor dashboard with a link to the new communications MFE version instead. - # Setting the tool to false will leave the old bulk email tool experience in place. - # .. toggle_use_cases: opt_in - # .. toggle_creation_date: 2022-03-21 - # .. toggle_target_removal_date: None - # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MICROBA-1758' - 'ENABLE_NEW_BULK_EMAIL_EXPERIENCE': False, - - # .. toggle_name: MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: If enabled, the Library Content Block is marked as complete when users view it. - # Otherwise (by default), all children of this block must be completed. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2022-03-22 - # .. toggle_target_removal_date: None - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/28268 - # .. toggle_warning: For consistency in user-experience, keep the value in sync with the setting of the same name - # in the LMS and CMS. - 'MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW': False, - - # .. toggle_name: FEATURES['DISABLE_UNENROLLMENT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to disable self-unenrollments via REST API. - # This also hides the "Unenroll" button on the Learner Dashboard. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2021-10-11 - # .. toggle_warning: For consistency in user experience, keep the value in sync with the setting of the same name - # in the LMS and CMS. - # .. toggle_tickets: 'https://github.com/open-craft/edx-platform/pull/429' - 'DISABLE_UNENROLLMENT': False, - - # .. toggle_name: FEATURES['ENABLE_CERTIFICATES_IDV_REQUIREMENT'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Whether to enforce ID Verification requirements for course certificates generation - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2022-04-26 - # .. toggle_target_removal_date: None - # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1458' - 'ENABLE_CERTIFICATES_IDV_REQUIREMENT': False, - - # .. toggle_name: FEATURES['DISABLE_ALLOWED_ENROLLMENT_IF_ENROLLMENT_CLOSED'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to disable enrollment for user invited to a course - # .. if user is registering before enrollment start date or after enrollment end date - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2022-06-06 - # .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/29538' - 'DISABLE_ALLOWED_ENROLLMENT_IF_ENROLLMENT_CLOSED': False, - - # .. toggle_name: FEATURES['SEND_LEARNING_CERTIFICATE_LIFECYCLE_EVENTS_TO_BUS'] - # .. toggle_implementation: SettingToggle - # .. toggle_default: False - # .. toggle_description: When True, the system will publish certificate lifecycle signals to the event bus. - # This toggle is used to create the EVENT_BUS_PRODUCER_CONFIG setting. - # .. toggle_warning: The default may be changed in a later release. See - # https://github.com/openedx/openedx-events/issues/265 - # .. toggle_use_cases: opt_in - # .. toggle_creation_date: 2023-10-10 - # .. toggle_tickets: https://github.com/openedx/openedx-events/issues/210 - 'SEND_LEARNING_CERTIFICATE_LIFECYCLE_EVENTS_TO_BUS': False, - - # .. toggle_name: FEATURES['ENABLE_GRADING_METHOD_IN_PROBLEMS'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Enables the grading method feature in capa problems. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2024-03-22 - # .. toggle_tickets: https://github.com/openedx/edx-platform/pull/33911 - 'ENABLE_GRADING_METHOD_IN_PROBLEMS': False, - - # .. toggle_name: FEATURES['ENABLE_COURSEWARE_SEARCH_VERIFIED_REQUIRED'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: When enabled, the courseware search feature will only be enabled - # for users in a verified enrollment track. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2024-04-24 - 'ENABLE_COURSEWARE_SEARCH_VERIFIED_ENROLLMENT_REQUIRED': False, - - # .. toggle_name: FEATURES['BADGES_ENABLED'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable badges functionality. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2024-04-02 - # .. toggle_target_removal_date: None - 'BADGES_ENABLED': False, -} +# FEATURES + +# .. toggle_name: settings.DISPLAY_DEBUG_INFO_TO_STAFF +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Add a "Staff Debug" button to course blocks for debugging +# by course staff. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-09-04 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2425 +DISPLAY_DEBUG_INFO_TO_STAFF = True + +# .. toggle_name: settings.DISPLAY_HISTOGRAMS_TO_STAFF +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: This displays histograms in the Staff Debug Info panel to course staff. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-02-13 +# .. toggle_warning: Generating histograms requires scanning the courseware_studentmodule table on each view. This +# can make staff access to courseware very slow on large courses. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2425 +DISPLAY_HISTOGRAMS_TO_STAFF = False # For large courses this slows down courseware access for staff. + +REROUTE_ACTIVATION_EMAIL = False # nonempty string = address for all activation emails + +# .. toggle_name: settings.DISABLE_START_DATES +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When True, all courses will be active, regardless of start +# date. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2012-07-24 +# .. toggle_warning: This will cause ALL courses to be immediately visible. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/17913 +## DO NOT SET TO True IN THIS FILE +## Doing so will cause all courses to be released on production +DISABLE_START_DATES = False + +# .. toggle_name: settings.ENABLE_DISCUSSION_SERVICE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: When True, it will enable the Discussion tab in courseware for all courses. Setting this +# to False will not contain inline discussion components and discussion tab in any courses. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2012-08-14 +# .. toggle_warning: If the discussion panel is present in the course and the value for this flag is False then, +# attempting to expand those components will cause errors. So, this should only be set to False with an LMS that +# is running courses that do not contain discussion components. +# For consistency in user-experience, keep the value in sync with the setting of the same name in the CMS. +ENABLE_DISCUSSION_SERVICE = True + +# .. toggle_name: settings.ENABLE_TEXTBOOK +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Add PDF and HTML textbook tabs to the courseware. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-03-27 +# .. toggle_warning: For consistency in user-experience, keep the value in sync with the setting of the same name +# in the CMS. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/3064 +ENABLE_TEXTBOOK = True + +# .. toggle_name: settings.ENABLE_DISCUSSION_HOME_PANEL +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Hides or displays a welcome panel under the Discussion tab, which includes a subscription +# on/off setting for discussion digest emails. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-07-30 +# .. toggle_warning: This should remain off in production until digest notifications are online. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/520 +ENABLE_DISCUSSION_HOME_PANEL = False + +# .. toggle_name: settings.ENABLE_DISCUSSION_EMAIL_DIGEST +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set this to True if you want the discussion digest emails +# enabled automatically for new users. This will be set on all new account +# registrations. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-08-19 +# .. toggle_target_removal_date: None +# .. toggle_warning: It is not recommended to enable this feature if ENABLE_DISCUSSION_HOME_PANEL is not enabled, +# since subscribers who receive digests in that case will only be able to unsubscribe via links embedded in +# their emails, and they will have no way to resubscribe. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/4891 +ENABLE_DISCUSSION_EMAIL_DIGEST = False + +# .. toggle_name: settings.ENABLE_UNICODE_USERNAME +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set this to True to allow unicode characters in username. Enabling this will also +# automatically enable SOCIAL_AUTH_CLEAN_USERNAMES. When this is enabled, usernames will have to match the +# regular expression defined by USERNAME_REGEX_PARTIAL. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-06-27 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/14729 +ENABLE_UNICODE_USERNAME = False + +# .. toggle_name: settings.ENABLE_DJANGO_ADMIN_SITE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Set to False if you want to disable Django's admin site. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-09-26 +# .. toggle_warning: It is not recommended to disable this feature as there are many settings available on +# Django's admin site and will be inaccessible to the superuser. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/829 +ENABLE_DJANGO_ADMIN_SITE = True +ENABLE_LMS_MIGRATION = False + +# .. toggle_name: settings.ENABLE_MASQUERADE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: None +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-04-13 +ENABLE_MASQUERADE = True + +# .. toggle_name: settings.DISABLE_LOGIN_BUTTON +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Removes the display of the login button in the navigation bar. +# Change is only at the UI level. Used in systems where login is automatic, eg MIT SSL +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-12-03 +DISABLE_LOGIN_BUTTON = False + +# .. toggle_name: settings.ENABLE_OAUTH2_PROVIDER +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enable this feature to allow this Open edX platform to be an OAuth2 authentication +# provider. This is necessary to enable some other features, such as the REST API for the mobile application. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2014-09-09 +# .. toggle_target_removal_date: None +# .. toggle_warning: This temporary feature toggle does not have a target removal date. +ENABLE_OAUTH2_PROVIDER = False + +# .. toggle_name: settings.ENABLE_XBLOCK_VIEW_ENDPOINT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enable an API endpoint, named "xblock_view", to serve rendered XBlock views. This might be +# used by external applications. See for instance jquery-xblock (now unmaintained): +# https://github.com/openedx/jquery-xblock +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-03-14 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2968 +ENABLE_XBLOCK_VIEW_ENDPOINT = False + +# Allows to configure the LMS to provide CORS headers to serve requests from other +# domains +ENABLE_CORS_HEADERS = False + +# Can be turned off if course lists need to be hidden. Effects views and templates. +# .. toggle_name: settings.COURSES_ARE_BROWSABLE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: When this is set to True, all the courses will be listed on the /courses page and Explore +# Courses link will be visible. Set to False if courses list and Explore Courses link need to be hidden. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-09-28 +# .. toggle_warning: This Effects views and templates. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/1073 +COURSES_ARE_BROWSABLE = True + +# Can be turned off to disable the help link in the navbar +# .. toggle_name: settings.ENABLE_HELP_LINK +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: When True, a help link is displayed on the main navbar. Set False to hide it. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2021-03-05 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/26106 +ENABLE_HELP_LINK = True + +# .. toggle_name: settings.HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When set, it hides the Courses list on the Learner Dashboard page if the learner has not +# yet activated the account and not enrolled in any courses. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2018-05-18 +# .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1814 +HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED = False + +# .. toggle_name: settings.ENABLE_STUDENT_HISTORY_VIEW +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: This provides a UI to show a student's submission history in a problem by the Staff Debug +# tool. Set to False if you want to hide Submission History from the courseware page. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-02-15 +# .. toggle_tickets: https://github.com/openedx/edx-platform/commit/8f17e6ae9ed76fa75b3caf867b65ccb632cb6870 +ENABLE_STUDENT_HISTORY_VIEW = True + +# Turn on a page that lets staff enter Python code to be run in the +# sandbox, for testing whether it's enabled properly. +ENABLE_DEBUG_RUN_PYTHON = False + +# Enable URL that shows information about the status of various services +ENABLE_SERVICE_STATUS = False + +# Don't autoplay videos for students +AUTOPLAY_VIDEOS = False + +# Move the student to next page when a video finishes. Set to True to show +# an auto-advance button in videos. If False, videos never auto-advance. +ENABLE_AUTOADVANCE_VIDEOS = False + +# Enable instructor dash to submit background tasks +ENABLE_INSTRUCTOR_BACKGROUND_TASKS = True + +# Enable instructor to assign individual due dates +# Note: In order for this feature to work, you must also add +# 'lms.djangoapps.courseware.student_field_overrides.IndividualStudentOverrideProvider' to +# the setting FIELD_OVERRIDE_PROVIDERS, in addition to setting this flag to +# True. +INDIVIDUAL_DUE_DATES = False + +# .. toggle_name: CUSTOM_COURSES_EDX +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable Custom Courses for edX, a feature that is more commonly known as +# CCX. Documentation for configuring and using this feature is available at +# https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/enable_ccx.html +# .. toggle_warning: When set to true, 'lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider' will +# be added to MODULESTORE_FIELD_OVERRIDE_PROVIDERS +# .. toggle_use_cases: opt_in, circuit_breaker +# .. toggle_creation_date: 2015-04-10 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6636 +CUSTOM_COURSES_EDX = False + +# Toggle to enable certificates of courses on dashboard +ENABLE_VERIFIED_CERTIFICATES = False +# Settings for course import olx validation +ENABLE_COURSE_OLX_VALIDATION = False + +# .. toggle_name: settings.DISABLE_HONOR_CERTIFICATES +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to disable honor certificates. Typically used when your installation only +# allows verified certificates, like courses.edx.org. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2019-05-14 +# .. toggle_tickets: https://openedx.atlassian.net/browse/PROD-269 +DISABLE_HONOR_CERTIFICATES = False # Toggle to disable honor certificates + +DISABLE_AUDIT_CERTIFICATES = False # Toggle to disable audit certificates + +# .. toggle_name: settings.AUTOMATIC_AUTH_FOR_TESTING +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to perform acceptance and load test. Auto auth view is responsible for load +# testing and is controlled by this feature flag. Session verification (of CacheBackedAuthenticationMiddleware) +# is a security feature, but it can be turned off by enabling this feature flag. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-07-25 +# .. toggle_warning: If this has been set to True then the account activation email will be skipped. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/417 +AUTOMATIC_AUTH_FOR_TESTING = False + +# .. toggle_name: settings.RESTRICT_AUTOMATIC_AUTH +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Prevent auto auth from creating superusers or modifying existing users. Auto auth is a +# mechanism where superusers can simply modify attributes of other users by accessing the "/auto_auth url" with +# the right +# querystring parameters. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2018-05-07 +# .. toggle_tickets: https://openedx.atlassian.net/browse/TE-2545 +RESTRICT_AUTOMATIC_AUTH = True + +# .. toggle_name: settings.ENABLE_LOGIN_MICROFRONTEND +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enable the login micro frontend. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2018-05-07 +# .. toggle_warning: The login MFE domain name should be listed in LOGIN_REDIRECT_WHITELIST. +ENABLE_LOGIN_MICROFRONTEND = False + +# .. toggle_name: settings.SKIP_EMAIL_VALIDATION +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Turn this on to skip sending emails for user validation. +# Beware, as this leaves the door open to potential spam abuse. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2018-05-07 +# .. toggle_warning: The login MFE domain name should be listed in LOGIN_REDIRECT_WHITELIST. +SKIP_EMAIL_VALIDATION = False + +# .. toggle_name: settings.ENABLE_COSMETIC_DISPLAY_PRICE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enable the display of "cosmetic_display_price", set in a course advanced settings. This +# cosmetic price is used when there is no registration price associated to the course. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-10-10 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6876 +# .. toggle_warning: The use case of this feature toggle is uncertain. +ENABLE_COSMETIC_DISPLAY_PRICE = False + +# Automatically approve student identity verification attempts +# .. toggle_name: settings.AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: If set to True, then we want to skip posting anything to Software Secure. Bypass posting +# anything to Software Secure if the auto verify feature for testing is enabled. We actually don't even create +# the message because that would require encryption and message signing that rely on settings.VERIFY_STUDENT +# values that aren't set in dev. So we just pretend like we successfully posted and automatically approve student +# identity verification attempts. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2013-10-03 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/1184 +AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING = False + +# Maximum number of rows to include in the csv file for downloading problem responses. +MAX_PROBLEM_RESPONSES_COUNT = 5000 + +ENABLED_PAYMENT_REPORTS = [ + "refund_report", + "itemized_purchase_report", + "university_revenue_share", + "certificate_status" +] + +# Turn off account locking if failed login attempts exceeds a limit +# .. toggle_name: settings.ENABLE_MAX_FAILED_LOGIN_ATTEMPTS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: This feature will keep track of the number of failed login attempts on a given user's +# email. If the number of consecutive failed login attempts - without a successful login at some point - reaches +# a configurable threshold (default 6), then the account will be locked for a configurable amount of seconds +# (30 minutes) which will prevent additional login attempts until this time period has passed. If a user +# successfully logs in, all the counter which tracks the number of failed attempts will be reset back to 0. If +# set to False then account locking will be disabled for failed login attempts. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-01-30 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2331 +ENABLE_MAX_FAILED_LOGIN_ATTEMPTS = True + +# Hide any Personally Identifiable Information from application logs +SQUELCH_PII_IN_LOGS = True + +# .. toggle_name: settings.EMBARGO +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Turns on embargo functionality, which blocks users from +# the site or courses based on their location. Embargo can restrict users by states +# and whitelist/blacklist (IP Addresses (ie. 10.0.0.0), Networks (ie. 10.0.0.0/24)), or the user profile country. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-02-27 +# .. toggle_target_removal_date: None +# .. toggle_warning: reverse proxy should be configured appropriately for example Client IP address headers +# (e.g HTTP_X_FORWARDED_FOR) should be configured. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/2749 +EMBARGO = False + +# Whether the Wiki subsystem should be accessible via the direct /wiki/ paths. Setting this to True means +# that people can submit content and modify the Wiki in any arbitrary manner. We're leaving this as True in the +# defaults, so that we maintain current behavior +ALLOW_WIKI_ROOT_ACCESS = True + +# .. toggle_name: settings.ENABLE_THIRD_PARTY_AUTH +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Turn on third-party auth. Disabled for now because full implementations are not yet +# available. Remember to run migrations if you enable this; we don't create tables by default. This feature can +# be enabled on a per-site basis. When enabling this feature, remember to define the allowed authentication +# backends with the AUTHENTICATION_BACKENDS setting. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-09-15 +ENABLE_THIRD_PARTY_AUTH = False + +# .. toggle_name: settings.ENABLE_MKTG_SITE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Toggle to enable alternate urls for marketing links. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-03-24 +# .. toggle_warning: When this is enabled, the MKTG_URLS setting should be defined. The use case of this feature +# toggle is uncertain. +ENABLE_MKTG_SITE = False + +# Prevent concurrent logins per user +PREVENT_CONCURRENT_LOGINS = True + +# .. toggle_name: settings.ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: When a logged in user goes to the homepage ('/') the user will be redirected to the +# dashboard page when this flag is set to True - this is default Open edX behavior. Set to False to not redirect +# the user. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2014-09-16 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/5220 +ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER = True + +# .. toggle_name: settings.ENABLE_COURSE_SORTING_BY_START_DATE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: When a user goes to the homepage ('/') the user sees the courses listed in the +# announcement dates order - this is default Open edX behavior. Set to True to change the course sorting behavior +# by their start dates, latest first. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-03-27 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7548 +ENABLE_COURSE_SORTING_BY_START_DATE = True + +# .. toggle_name: settings.ENABLE_COURSE_HOME_REDIRECT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: When enabled, along with the ENABLE_MKTG_SITE feature toggle, users who attempt to access a +# course "about" page will be redirected to the course home url. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2019-01-15 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/19604 +ENABLE_COURSE_HOME_REDIRECT = True + +# Expose Mobile REST API. Note that if you use this, you must also set +# ENABLE_OAUTH2_PROVIDER to True +ENABLE_MOBILE_REST_API = False + +# .. toggle_name: settings.ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Display the standard footer in the login page. This feature can be overridden by a site- +# specific configuration. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2016-06-24 +# .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1320 +ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER = False + +# Enable organizational email opt-in +ENABLE_MKTG_EMAIL_OPT_IN = False + +# .. toggle_name: settings.ENABLE_FOOTER_MOBILE_APP_LINKS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True if you want show the mobile app links (Apple App Store & Google Play Store) in +# the footer. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-01-13 +# .. toggle_warning: If you set this to True then you should also set your mobile application's app store and play +# store URLs in the MOBILE_STORE_URLS settings dictionary. These links are not part of the default theme. If you +# want these links on your footer then you should use the edx.org theme. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6588 +ENABLE_FOOTER_MOBILE_APP_LINKS = False + +# Let students save and manage their annotations +# .. toggle_name: settings.ENABLE_EDXNOTES +# .. toggle_implementation: SettingToggle +# .. toggle_default: False +# .. toggle_description: This toggle enables the students to save and manage their annotations in the +# course using the notes service. The bulk of the actual work in storing the notes is done by +# a separate service (see the edx-notes-api repo). +# .. toggle_warning: Requires the edx-notes-api service properly running and to have configured the django settings +# EDXNOTES_INTERNAL_API and EDXNOTES_PUBLIC_API. If you update this setting, also update it in Studio. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-01-04 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6321 +ENABLE_EDXNOTES = False + +# Toggle to enable coordination with the Publisher tool (keep in sync with cms/envs/common.py) +ENABLE_PUBLISHER = False + +# Milestones application flag +MILESTONES_APP = False + +# Prerequisite courses feature flag +ENABLE_PREREQUISITE_COURSES = False + +# For easily adding modes to courses during acceptance testing +MODE_CREATION_FOR_TESTING = False + +# For caching programs in contexts where the LMS can only +# be reached over HTTP. +EXPOSE_CACHE_PROGRAMS_ENDPOINT = False + +# Courseware search feature +# .. toggle_name: settings.ENABLE_COURSEWARE_SEARCH +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When enabled, this adds a Search the course widget on the course outline and courseware +# pages for searching courseware data. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-01-29 +# .. toggle_warning: In order to get this working, your courses data should be indexed in Elasticsearch. You will +# see the search widget on the courseware page only if the DISABLE_COURSE_OUTLINE_PAGE_FLAG is set. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6506 +ENABLE_COURSEWARE_SEARCH = False + +# .. toggle_name: settings.ENABLE_COURSEWARE_SEARCH_FOR_COURSE_STAFF +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When enabled, this adds a Search the course widget on the course outline and courseware +# pages for searching courseware data but for course staff users only. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2019-12-06 +# .. toggle_warning: In order to get this working, your courses data should be indexed in Elasticsearch. If +# ENABLE_COURSEWARE_SEARCH is enabled then the search widget will be visible to all learners and this flag's +# value does not matter in that case. This flag is enabled in devstack by default. +# .. toggle_tickets: https://openedx.atlassian.net/browse/TNL-6931 +ENABLE_COURSEWARE_SEARCH_FOR_COURSE_STAFF = False + +# Dashboard search feature +# .. toggle_name: settings.ENABLE_DASHBOARD_SEARCH +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When enabled, this adds a Search Your Courses widget on the dashboard page for searching +# courseware data. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-01-29 +# .. toggle_warning: In order to get this working, your courses data should be indexed in Elasticsearch. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/6506 +ENABLE_DASHBOARD_SEARCH = False + +# log all information from cybersource callbacks +LOG_POSTPAY_CALLBACKS = True + +# .. toggle_name: settings.LICENSING +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Toggle platform-wide course licensing. The course.license attribute is then used to append +# license information to the courseware. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-05-14 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7315 +LICENSING = False + +# .. toggle_name: settings.CERTIFICATES_HTML_VIEW +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable course certificates on your instance of Open edX. +# .. toggle_warning: You must enable this feature flag in both Studio and the LMS and complete the configuration tasks +# described here: +# https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/enable_certificates.html pylint: disable=line-too-long,useless-suppression +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-03-13 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7113 +CERTIFICATES_HTML_VIEW = False + +# .. toggle_name: settings.CUSTOM_CERTIFICATE_TEMPLATES_ENABLED +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable custom certificate templates which are configured via Django admin. +# .. toggle_warning: None +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-08-13 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://openedx.atlassian.net/browse/SOL-1044 +CUSTOM_CERTIFICATE_TEMPLATES_ENABLED = False + +# .. toggle_name: settings.ENABLE_COURSE_DISCOVERY +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Add a course search widget to the LMS for searching courses. When this is enabled, the +# latest courses are no longer displayed on the LMS landing page. Also, an "Explore Courses" item is added to the +# navbar. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-04-23 +# .. toggle_target_removal_date: None +# .. toggle_warning: The COURSE_DISCOVERY_MEANINGS setting should be properly defined. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7845 +ENABLE_COURSE_DISCOVERY = False + +# .. toggle_name: settings.ENABLE_COURSE_FILENAME_CCX_SUFFIX +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: If set to True, CCX ID will be included in the generated filename for CCX courses. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2021-03-16 +# .. toggle_target_removal_date: None +# .. toggle_tickets: None +# .. toggle_warning: Turning this feature ON will affect all generated filenames which are related to CCX courses. +ENABLE_COURSE_FILENAME_CCX_SUFFIX = False + +# Setting for overriding default filtering facets for Course discovery +# COURSE_DISCOVERY_FILTERS = ["org", "language", "modes"] + +# Software secure fake page feature flag +ENABLE_SOFTWARE_SECURE_FAKE = False + +# Teams feature +ENABLE_TEAMS = True + +# Show video bumper in LMS +ENABLE_VIDEO_BUMPER = False + +# How many seconds to show the bumper again, default is 7 days: +SHOW_BUMPER_PERIODICITY = 7 * 24 * 3600 + +# .. toggle_name: settings.ENABLE_SPECIAL_EXAMS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enable to use special exams, aka timed and proctored exams. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-09-04 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/9744 +ENABLE_SPECIAL_EXAMS = False + +# .. toggle_name: settings.ENABLE_LTI_PROVIDER +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When set to True, Open edX site can be used as an LTI Provider to other systems +# and applications. +# .. toggle_warning: After enabling this feature flag there are multiple steps involved to configure edX +# as LTI provider. Full guide is available here: +# https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/lti/index.html +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-04-24 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/7689 +ENABLE_LTI_PROVIDER = False + +# .. toggle_name: settings.SHOW_HEADER_LANGUAGE_SELECTOR +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When set to True, language selector will be visible in the header. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-05-25 +# .. toggle_warning: You should set the languages in the DarkLangConfig table to get this working. If you have +# not set any languages in the DarkLangConfig table then the language selector will not be visible in the header. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/15133 +SHOW_HEADER_LANGUAGE_SELECTOR = False + +# At edX it's safe to assume that English transcripts are always available +# This is not the case for all installations. +# The default value in {lms,cms}/envs/common.py and xmodule/tests/test_video.py should be consistent. +FALLBACK_TO_ENGLISH_TRANSCRIPTS = True + +# .. toggle_name: settings.SHOW_FOOTER_LANGUAGE_SELECTOR +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When set to True, language selector will be visible in the footer. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-05-25 +# .. toggle_warning: LANGUAGE_COOKIE_NAME is required to use footer-language-selector, set it if it has not been set. +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/15133 +SHOW_FOOTER_LANGUAGE_SELECTOR = False + +# .. toggle_name: settings.ENABLE_CSMH_EXTENDED +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Write Courseware Student Module History (CSMH) to the extended table: this logs all +# student activities to MySQL, in a separate database. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2020-11-05 +# .. toggle_warning: Even though most Open edX instances run with a separate CSMH database, it may not always be +# the case. When disabling this feature flag, remember to remove "lms.djangoapps.coursewarehistoryextended" +# from the INSTALLED_APPS and the "StudentModuleHistoryExtendedRouter" from the DATABASE_ROUTERS. +ENABLE_CSMH_EXTENDED = True + +# Read from both the CSMH and CSMHE history tables. +# This is the default, but can be disabled if all history +# lives in the Extended table, saving the frontend from +# making multiple queries. +ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES = True + +# Set this to False to facilitate cleaning up invalid xml from your modulestore. +ENABLE_XBLOCK_XML_VALIDATION = True + +# .. toggle_name: settings.ALLOW_PUBLIC_ACCOUNT_CREATION +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Allow public account creation. If this is disabled, users will no longer have access to +# the signup page. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-04-12 +# .. toggle_tickets: https://openedx.atlassian.net/browse/YONK-513 +ALLOW_PUBLIC_ACCOUNT_CREATION = True + +# .. toggle_name: settings.SHOW_REGISTRATION_LINKS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Allow registration links. If this is disabled, users will no longer see buttons to the +# the signup page. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2023-03-27 +SHOW_REGISTRATION_LINKS = True + +# .. toggle_name: settings.ENABLE_COOKIE_CONSENT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enable header banner for cookie consent using this service: +# https://cookieconsent.insites.com/ +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-03-03 +# .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1560 +ENABLE_COOKIE_CONSENT = False + +# Whether or not the dynamic EnrollmentTrackUserPartition should be registered. +ENABLE_ENROLLMENT_TRACK_USER_PARTITION = True + +# Enable one click program purchase +# See LEARNER-493 +ENABLE_ONE_CLICK_PROGRAM_PURCHASE = False + +# .. toggle_name: settings.ALLOW_EMAIL_ADDRESS_CHANGE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Allow users to change their email address on the Account Settings page. If this is +# disabled, users will not be able to change their email address. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-06-26 +# .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1735 +ALLOW_EMAIL_ADDRESS_CHANGE = True + +# .. toggle_name: settings.ENABLE_BULK_ENROLLMENT_VIEW +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When set to True the bulk enrollment view is enabled and one can use it to enroll multiple +# users in a course using bulk enrollment API endpoint (/api/bulk_enroll/v1/bulk_enroll). +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-07-15 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/15006 +ENABLE_BULK_ENROLLMENT_VIEW = False + +# Set to enable Enterprise integration +ENABLE_ENTERPRISE_INTEGRATION = False + +# .. toggle_name: settings.ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Whether HTML Block returns HTML content with the Course Blocks API when the API +# is called with student_view_data=html query parameter. +# .. toggle_warning: Because the Course Blocks API caches its data, the cache must be cleared (e.g. by +# re-publishing the course) for changes to this flag to take effect. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-08-28 +# .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1880 +ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA = False + +# .. toggle_name: settings.ENABLE_PASSWORD_RESET_FAILURE_EMAIL +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Whether to send an email for failed password reset attempts or not. This happens when a +# user asks for a password reset but they don't have an account associated to their email. This is useful for +# notifying users that they don't have an account associated with email addresses they believe they've registered +# with. This setting can be overridden by a site-specific configuration. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2017-07-20 +# .. toggle_tickets: https://openedx.atlassian.net/browse/OSPR-1832 +ENABLE_PASSWORD_RESET_FAILURE_EMAIL = False + +# Sets the default browser support. For more information go to http://browser-update.org/customize.html +UNSUPPORTED_BROWSER_ALERT_VERSIONS = "{i:10,f:-3,o:-3,s:-3,c:-3}" + +# .. toggle_name: settings.ENABLE_ACCOUNT_DELETION +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Whether to display the account deletion section on Account Settings page. Set to False to +# hide this section. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2018-06-01 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/18298 +ENABLE_ACCOUNT_DELETION = True + +# Enable feature to remove enrollments and users. Used to reset state of master's integration environments +ENABLE_ENROLLMENT_RESET = False +DISABLE_MOBILE_COURSE_AVAILABLE = False + +# .. toggle_name: settings.ENABLE_CHANGE_USER_PASSWORD_ADMIN +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable changing a user password through django admin. This is disabled by +# default because enabling allows a method to bypass password policy. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2020-02-21 +# .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/21616' +ENABLE_CHANGE_USER_PASSWORD_ADMIN = False + +# .. toggle_name: settings.ENABLE_AUTHN_MICROFRONTEND +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the logistration. +# .. toggle_use_cases: temporary, open_edx +# .. toggle_creation_date: 2020-09-08 +# .. toggle_target_removal_date: None +# .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/24908' +# .. toggle_warning: Also set settings.AUTHN_MICROFRONTEND_URL for rollout. This temporary feature +# toggle does not have a target removal date. +ENABLE_AUTHN_MICROFRONTEND = os.environ.get("EDXAPP_ENABLE_AUTHN_MFE", False) + +# .. toggle_name: settings.ENABLE_CATALOG_MICROFRONTEND +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the catalog. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2025-05-15 +# .. toggle_target_removal_date: 2025-11-01 +ENABLE_CATALOG_MICROFRONTEND = False + +### ORA Feature Flags ### +# .. toggle_name: settings.ENABLE_ORA_ALL_FILE_URLS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: A "work-around" feature toggle meant to help in cases where some file uploads are not +# discoverable. If enabled, will iterate through all possible file key suffixes up to the max for displaying +# file metadata in staff assessments. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2020-03-03 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://openedx.atlassian.net/browse/EDUCATOR-4951 +# .. toggle_warning: This temporary feature toggle does not have a target removal date. +ENABLE_ORA_ALL_FILE_URLS = False + +# .. toggle_name: settings.ENABLE_ORA_USER_STATE_UPLOAD_DATA +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: A "work-around" feature toggle meant to help in cases where some file uploads are not +# discoverable. If enabled, will pull file metadata from StudentModule.state for display in staff assessments. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2020-03-03 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://openedx.atlassian.net/browse/EDUCATOR-4951 +# .. toggle_warning: This temporary feature toggle does not have a target removal date. +ENABLE_ORA_USER_STATE_UPLOAD_DATA = False + +# .. toggle_name: settings.ENABLE_ORA_USERNAMES_ON_DATA_EXPORT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to add deanonymized usernames to ORA data +# report. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2020-06-11 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://openedx.atlassian.net/browse/TNL-7273 +# .. toggle_warning: This temporary feature toggle does not have a target removal date. +ENABLE_ORA_USERNAMES_ON_DATA_EXPORT = False + +# .. toggle_name: settings.ENABLE_COURSE_ASSESSMENT_GRADE_CHANGE_SIGNAL +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to start sending signals for assessment level grade updates. Notably, the only +# handler of this signal at the time of this writing sends assessment updates to enterprise integrated channels. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2020-12-09 +# .. toggle_target_removal_date: 2021-02-01 +# .. toggle_tickets: https://openedx.atlassian.net/browse/ENT-3818 +ENABLE_COURSE_ASSESSMENT_GRADE_CHANGE_SIGNAL = False + +# .. toggle_name: settings.ALLOW_ADMIN_ENTERPRISE_COURSE_ENROLLMENT_DELETION +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: If true, allows for the deletion of EnterpriseCourseEnrollment records via Django Admin. +# .. toggle_use_cases: opt_in +# .. toggle_creation_date: 2021-01-27 +# .. toggle_tickets: https://openedx.atlassian.net/browse/ENT-4022 +ALLOW_ADMIN_ENTERPRISE_COURSE_ENROLLMENT_DELETION = False + +# .. toggle_name: settings.ENABLE_BULK_USER_RETIREMENT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable bulk user retirement through REST API. This is disabled by +# default. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2021-03-11 +# .. toggle_target_removal_date: None +# .. toggle_warning: None +# .. toggle_tickets: 'https://openedx.atlassian.net/browse/OSPR-5290' +ENABLE_BULK_USER_RETIREMENT = False + +# .. toggle_name: settings.ENABLE_INTEGRITY_SIGNATURE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Whether to display honor code agreement for learners before their first grade assignment +# (https://github.com/edx/edx-name-affirmation) +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2022-02-15 +# .. toggle_target_removal_date: None +# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1348' +ENABLE_INTEGRITY_SIGNATURE = False + +# .. toggle_name: settings.ENABLE_LTI_PII_ACKNOWLEDGEMENT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enables the lti pii acknowledgement feature for a course +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2023-10 +# .. toggle_target_removal_date: None +# .. toggle_tickets: 'https://2u-internal.atlassian.net/browse/MST-2055' +ENABLE_LTI_PII_ACKNOWLEDGEMENT = False + +# .. toggle_name: settings.ENABLE_NEW_BULK_EMAIL_EXPERIENCE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When true, replaces the bulk email tool found on the +# instructor dashboard with a link to the new communications MFE version instead. +# Setting the tool to false will leave the old bulk email tool experience in place. +# .. toggle_use_cases: opt_in +# .. toggle_creation_date: 2022-03-21 +# .. toggle_target_removal_date: None +# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MICROBA-1758' +ENABLE_NEW_BULK_EMAIL_EXPERIENCE = False + +# .. toggle_name: MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: If enabled, the Library Content Block is marked as complete when users view it. +# Otherwise (by default), all children of this block must be completed. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2022-03-22 +# .. toggle_target_removal_date: None +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/28268 +# .. toggle_warning: For consistency in user-experience, keep the value in sync with the setting of the same name +# in the LMS and CMS. +MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW = False + +# .. toggle_name: settings.DISABLE_UNENROLLMENT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to disable self-unenrollments via REST API. +# This also hides the "Unenroll" button on the Learner Dashboard. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2021-10-11 +# .. toggle_warning: For consistency in user experience, keep the value in sync with the setting of the same name +# in the LMS and CMS. +# .. toggle_tickets: 'https://github.com/open-craft/edx-platform/pull/429' +DISABLE_UNENROLLMENT = False + +# .. toggle_name: settings.ENABLE_CERTIFICATES_IDV_REQUIREMENT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Whether to enforce ID Verification requirements for course certificates generation +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2022-04-26 +# .. toggle_target_removal_date: None +# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1458' +ENABLE_CERTIFICATES_IDV_REQUIREMENT = False + +# .. toggle_name: settings.DISABLE_ALLOWED_ENROLLMENT_IF_ENROLLMENT_CLOSED +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to disable enrollment for user invited to a course +# .. if user is registering before enrollment start date or after enrollment end date +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2022-06-06 +# .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/29538' +DISABLE_ALLOWED_ENROLLMENT_IF_ENROLLMENT_CLOSED = False + +# .. toggle_name: settings.SEND_LEARNING_CERTIFICATE_LIFECYCLE_EVENTS_TO_BUS +# .. toggle_implementation: SettingToggle +# .. toggle_default: False +# .. toggle_description: When True, the system will publish certificate lifecycle signals to the event bus. +# This toggle is used to create the EVENT_BUS_PRODUCER_CONFIG setting. +# .. toggle_warning: The default may be changed in a later release. See +# https://github.com/openedx/openedx-events/issues/265 +# .. toggle_use_cases: opt_in +# .. toggle_creation_date: 2023-10-10 +# .. toggle_tickets: https://github.com/openedx/openedx-events/issues/210 +SEND_LEARNING_CERTIFICATE_LIFECYCLE_EVENTS_TO_BUS = False + +# .. toggle_name: settings.ENABLE_GRADING_METHOD_IN_PROBLEMS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Enables the grading method feature in capa problems. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-03-22 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/33911 +ENABLE_GRADING_METHOD_IN_PROBLEMS = False + +# .. toggle_name: settings.ENABLE_COURSEWARE_SEARCH_VERIFIED_REQUIRED +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When enabled, the courseware search feature will only be enabled +# for users in a verified enrollment track. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-04-24 +ENABLE_COURSEWARE_SEARCH_VERIFIED_ENROLLMENT_REQUIRED = False + +# .. toggle_name: settings.BADGES_ENABLED +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable badges functionality. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-04-02 +# .. toggle_target_removal_date: None +BADGES_ENABLED = False + +################ Enable credit eligibility feature #################### +# .. toggle_name: settings.ENABLE_CREDIT_ELIGIBILITY +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: When enabled, it is possible to define a credit eligibility criteria in the CMS. A "Credit +# Eligibility" section then appears for those courses in the LMS. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2015-06-17 +# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/8550 +ENABLE_CREDIT_ELIGIBILITY = True + +ENABLE_CROSS_DOMAIN_CSRF_COOKIE = False # .. toggle_name: ENABLE_REQUIRE_THIRD_PARTY_AUTH # .. toggle_implementation: DjangoSetting @@ -1983,7 +1999,7 @@ PAID_COURSE_REGISTRATION_CURRENCY = ['usd', '$'] # .. setting_description: Set the public API endpoint LMS will use in the frontend to # interact with the edx_notes_api service. # .. setting_warning: This setting must be a publicly accessible endpoint. It is only used -# when the setting FEATURES['ENABLE_EDXNOTES'] is activated. +# when the setting settings.ENABLE_EDXNOTES is activated. EDXNOTES_PUBLIC_API = 'http://localhost:18120/api/v1' # .. setting_name: EDXNOTES_INTERNAL_API # .. setting_default: http://localhost:18120/api/v1 @@ -1991,7 +2007,7 @@ EDXNOTES_PUBLIC_API = 'http://localhost:18120/api/v1' # interact with the edx_notes_api service. # .. setting_warning: Normally set to the same value of EDXNOTES_PUBLIC_API. It is not # mandatory for this setting to be a publicly accessible endpoint, but to be accessible -# by the LMS service. It is only used when the setting FEATURES['ENABLE_EDXNOTES'] is +# by the LMS service. It is only used when the setting settings.ENABLE_EDXNOTES is # activated. EDXNOTES_INTERNAL_API = 'http://localhost:18120/api/v1' # .. setting_name: EDXNOTES_CLIENT_NAME @@ -3420,22 +3436,9 @@ VERIFICATION_EXPIRY_EMAIL = { "DEFAULT_EMAILS": 2, } - -################ Enable credit eligibility feature #################### -ENABLE_CREDIT_ELIGIBILITY = True -# .. toggle_name: FEATURES['ENABLE_CREDIT_ELIGIBILITY'] -# .. toggle_implementation: DjangoSetting -# .. toggle_default: True -# .. toggle_description: When enabled, it is possible to define a credit eligibility criteria in the CMS. A "Credit -# Eligibility" section then appears for those courses in the LMS. -# .. toggle_use_cases: open_edx -# .. toggle_creation_date: 2015-06-17 -# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/8550 -FEATURES['ENABLE_CREDIT_ELIGIBILITY'] = ENABLE_CREDIT_ELIGIBILITY - ############# Cross-domain requests ################# -if FEATURES.get('ENABLE_CORS_HEADERS'): +if ENABLE_CORS_HEADERS: CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_WHITELIST = () CORS_ORIGIN_ALLOW_ALL = False @@ -3915,7 +3918,7 @@ COMMENTS_SERVICE_KEY = '' CHECKPOINT_PATTERN = r'(?P[^/]+)' # For the fields override feature -# If using FEATURES['INDIVIDUAL_DUE_DATES'], you should add +# If using settings.INDIVIDUAL_DUE_DATES, you should add # 'lms.djangoapps.courseware.student_field_overrides.IndividualStudentOverrideProvider' to # this setting. FIELD_OVERRIDE_PROVIDERS = () @@ -4133,7 +4136,7 @@ HELP_TOKENS_BOOKS = { # These configuration settings are specific to the Enterprise service and you should # not find references to them within the edx-platform project. # -# Only used if FEATURES['ENABLE_ENTERPRISE_INTEGRATION'] == True. +# Only used if settings.ENABLE_ENTERPRISE_INTEGRATION == True. ENTERPRISE_PUBLIC_ENROLLMENT_API_URL = Derived( lambda settings: (settings.LMS_ROOT_URL or '') + settings.LMS_ENROLLMENT_API_PATH @@ -4842,13 +4845,13 @@ SIMPLE_HISTORY_DATE_INDEX = False def _should_send_certificate_events(settings): - return settings.FEATURES['SEND_LEARNING_CERTIFICATE_LIFECYCLE_EVENTS_TO_BUS'] + return settings.SEND_LEARNING_CERTIFICATE_LIFECYCLE_EVENTS_TO_BUS #### Event bus producing #### def _should_send_learning_badge_events(settings): - return settings.FEATURES['BADGES_ENABLED'] + return settings.BADGES_ENABLED # .. setting_name: EVENT_BUS_PRODUCER_CONFIG # .. setting_default: all events disabled diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index a2ee3ea9d8..e2fc7fb6c2 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -142,31 +142,31 @@ PIPELINE['SASS_ARGUMENTS'] = '--debug-info' ########################### VERIFIED CERTIFICATES ################################# -FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True +AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING = True ########################### External REST APIs ################################# -FEATURES['ENABLE_OAUTH2_PROVIDER'] = True -FEATURES['ENABLE_MOBILE_REST_API'] = True -FEATURES['ENABLE_VIDEO_ABSTRACTION_LAYER_API'] = True +ENABLE_OAUTH2_PROVIDER = True +ENABLE_MOBILE_REST_API = True +ENABLE_VIDEO_ABSTRACTION_LAYER_API = True ########################## SECURITY ####################### -FEATURES['ENABLE_MAX_FAILED_LOGIN_ATTEMPTS'] = False -FEATURES['SQUELCH_PII_IN_LOGS'] = False -FEATURES['PREVENT_CONCURRENT_LOGINS'] = False +ENABLE_MAX_FAILED_LOGIN_ATTEMPTS = False +SQUELCH_PII_IN_LOGS = False +PREVENT_CONCURRENT_LOGINS = False ########################### Milestones ################################# -FEATURES['MILESTONES_APP'] = True +MILESTONES_APP = True ########################### Entrance Exams ################################# -FEATURES['ENTRANCE_EXAMS'] = True +ENTRANCE_EXAMS = True ################################ COURSE LICENSES ################################ -FEATURES['LICENSING'] = True +LICENSING = True ########################## Courseware Search ####################### -FEATURES['ENABLE_COURSEWARE_SEARCH'] = True -FEATURES['ENABLE_COURSEWARE_SEARCH_FOR_COURSE_STAFF'] = True +ENABLE_COURSEWARE_SEARCH = True +ENABLE_COURSEWARE_SEARCH_FOR_COURSE_STAFF = True SEARCH_ENGINE = 'search.elastic.ElasticSearchEngine' SEARCH_COURSEWARE_CONTENT_LOG_PARAMS = True @@ -179,11 +179,11 @@ ELASTIC_SEARCH_CONFIG = [ ] ########################## Dashboard Search ####################### -FEATURES['ENABLE_DASHBOARD_SEARCH'] = False +ENABLE_DASHBOARD_SEARCH = False ########################## Certificates Web/HTML View ####################### -FEATURES['CERTIFICATES_HTML_VIEW'] = True +CERTIFICATES_HTML_VIEW = True ########################## Course Discovery ####################### @@ -205,14 +205,14 @@ COURSE_DISCOVERY_MEANINGS = { 'language': LANGUAGE_MAP, } -FEATURES['ENABLE_COURSE_DISCOVERY'] = False +ENABLE_COURSE_DISCOVERY = False # Setting for overriding default filtering facets for Course discovery # COURSE_DISCOVERY_FILTERS = ["org", "language", "modes"] -FEATURES['COURSES_ARE_BROWSEABLE'] = True +COURSES_ARE_BROWSEABLE = True HOMEPAGE_COURSE_MAX = 9 # Software secure fake page feature flag -FEATURES['ENABLE_SOFTWARE_SECURE_FAKE'] = True +ENABLE_SOFTWARE_SECURE_FAKE = True # Setting for the testing of Software Secure Result Callback VERIFY_STUDENT["SOFTWARE_SECURE"] = { @@ -226,14 +226,14 @@ SEARCH_SKIP_ENROLLMENT_START_DATE_FILTERING = True ########################## Shopping cart ########################## -FEATURES['ENABLE_COSMETIC_DISPLAY_PRICE'] = True +ENABLE_COSMETIC_DISPLAY_PRICE = True ######################### Program Enrollments ##################### -FEATURES['ENABLE_ENROLLMENT_RESET'] = True +ENABLE_ENROLLMENT_RESET = True ########################## Third Party Auth ####################### -if FEATURES.get('ENABLE_THIRD_PARTY_AUTH') and ( +if ENABLE_THIRD_PARTY_AUTH and ( 'common.djangoapps.third_party_auth.dummy.DummyBackend' not in AUTHENTICATION_BACKENDS ): AUTHENTICATION_BACKENDS = ['common.djangoapps.third_party_auth.dummy.DummyBackend'] + list(AUTHENTICATION_BACKENDS) @@ -293,7 +293,7 @@ LEARNING_MICROFRONTEND_URL = os.environ.get("LEARNING_MICROFRONTEND_URL", "http: LEARNING_MICROFRONTEND_NETLOC = os.environ.get("LEARNING_MICROFRONTEND_NETLOC", urlparse(LEARNING_MICROFRONTEND_URL).netloc) ###################### Cross-domain requests ###################### -FEATURES['ENABLE_CORS_HEADERS'] = True +ENABLE_CORS_HEADERS = True CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_WHITELIST = () CORS_ORIGIN_ALLOW_ALL = True @@ -407,17 +407,15 @@ DISCUSSION_SPAM_URLS = [] ############## Docker based devstack settings ####################### -FEATURES.update({ - 'AUTOMATIC_AUTH_FOR_TESTING': True, - 'ENABLE_DISCUSSION_SERVICE': True, - 'SHOW_HEADER_LANGUAGE_SELECTOR': True, +AUTOMATIC_AUTH_FOR_TESTING = True +ENABLE_DISCUSSION_SERVICE = True +SHOW_HEADER_LANGUAGE_SELECTOR = True - # Enable enterprise integration by default. - # See https://github.com/openedx/edx-enterprise/blob/master/docs/development.rst for - # more background on edx-enterprise. - # Toggle this off if you don't want anything to do with enterprise in devstack. - 'ENABLE_ENTERPRISE_INTEGRATION': True, -}) +# Enable enterprise integration by default. +# See https://github.com/openedx/edx-enterprise/blob/master/docs/development.rst for +# more background on edx-enterprise. +# Toggle this off if you don't want anything to do with enterprise in devstack. +ENABLE_ENTERPRISE_INTEGRATION = True ENABLE_MKTG_SITE = os.environ.get('ENABLE_MARKETING_SITE', False) MARKETING_SITE_ROOT = os.environ.get('MARKETING_SITE_ROOT', 'http://localhost:8080') @@ -463,7 +461,7 @@ SYSTEM_WIDE_ROLE_CLASSES.append( 'system_wide_roles.SystemWideRoleAssignment', ) -if FEATURES.get('ENABLE_ENTERPRISE_INTEGRATION'): +if ENABLE_ENTERPRISE_INTEGRATION: SYSTEM_WIDE_ROLE_CLASSES.append( 'enterprise.SystemWideEnterpriseUserRoleAssignment', ) @@ -498,8 +496,8 @@ DCS_SESSION_COOKIE_SAMESITE_FORCE_ALL = True # LOGGING['loggers']['django.db.backends'] = {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False} ################### Special Exams (Proctoring) and Prereqs ################### -FEATURES['ENABLE_SPECIAL_EXAMS'] = True -FEATURES['ENABLE_PREREQUISITE_COURSES'] = True +ENABLE_SPECIAL_EXAMS = True +ENABLE_PREREQUISITE_COURSES = True # Used in edx-proctoring for ID generation in lieu of SECRET_KEY - dummy value # (ref MST-637) diff --git a/lms/envs/production.py b/lms/envs/production.py index 584ebf726e..0620d4f2c0 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -32,6 +32,11 @@ from xmodule.modulestore.modulestore_settings import convert_module_store_settin from .common import * +from openedx.core.lib.features_setting_proxy import FeaturesProxy + +# A proxy for feature flags stored in the settings namespace +FEATURES = FeaturesProxy(globals()) + def get_env_setting(setting): """ Get the environment setting or return exception """ @@ -198,7 +203,7 @@ LOGGING = get_logger_config( service_variant=SERVICE_VARIANT, ) -if FEATURES['ENABLE_CORS_HEADERS'] or FEATURES.get('ENABLE_CROSS_DOMAIN_CSRF_COOKIE'): +if ENABLE_CORS_HEADERS or ENABLE_CROSS_DOMAIN_CSRF_COOKIE: CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_WHITELIST = _YAML_TOKENS.get('CORS_ORIGIN_WHITELIST', ()) CORS_ORIGIN_ALLOW_ALL = _YAML_TOKENS.get('CORS_ORIGIN_ALLOW_ALL', False) @@ -281,7 +286,7 @@ EVENT_TRACKING_BACKENDS['segmentio']['OPTIONS']['processors'][0]['OPTIONS']['whi EVENT_TRACKING_SEGMENTIO_EMIT_WHITELIST ) -if FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): +if ENABLE_THIRD_PARTY_AUTH: AUTHENTICATION_BACKENDS = _YAML_TOKENS.get('THIRD_PARTY_AUTH_BACKENDS', [ 'social_core.backends.google.GoogleOAuth2', 'social_core.backends.linkedin.LinkedinOAuth2', @@ -318,7 +323,7 @@ if FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): THIRD_PARTY_AUTH_CUSTOM_AUTH_FORMS = _YAML_TOKENS.get('THIRD_PARTY_AUTH_CUSTOM_AUTH_FORMS', {}) ##### OAUTH2 Provider ############## -if FEATURES['ENABLE_OAUTH2_PROVIDER']: +if ENABLE_OAUTH2_PROVIDER: OAUTH_ENFORCE_SECURE = True OAUTH_ENFORCE_CLIENT_SECURE = True # Defaults for the following are defined in lms.envs.common @@ -326,10 +331,10 @@ if FEATURES['ENABLE_OAUTH2_PROVIDER']: OAUTH_EXPIRE_DELTA_PUBLIC = datetime.timedelta(days=OAUTH_EXPIRE_PUBLIC_CLIENT_DAYS) if ( - FEATURES['ENABLE_COURSEWARE_SEARCH'] or - FEATURES['ENABLE_DASHBOARD_SEARCH'] or - FEATURES['ENABLE_COURSE_DISCOVERY'] or - FEATURES['ENABLE_TEAMS'] + ENABLE_COURSEWARE_SEARCH or + ENABLE_DASHBOARD_SEARCH or + ENABLE_COURSE_DISCOVERY or + ENABLE_TEAMS ): # Use ElasticSearch as the search engine herein SEARCH_ENGINE = "search.elastic.ElasticSearchEngine" @@ -341,7 +346,7 @@ XBLOCK_SETTINGS.setdefault("VideoBlock", {})["licensing_enabled"] = FEATURES["LI XBLOCK_SETTINGS.setdefault("VideoBlock", {})['YOUTUBE_API_KEY'] = YOUTUBE_API_KEY ##### Custom Courses for EdX ##### -if FEATURES['CUSTOM_COURSES_EDX']: +if CUSTOM_COURSES_EDX: INSTALLED_APPS += ['lms.djangoapps.ccx', 'openedx.core.djangoapps.ccxcon.apps.CCXConnectorConfig'] MODULESTORE_FIELD_OVERRIDE_PROVIDERS += ( 'lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider', @@ -350,7 +355,7 @@ if FEATURES['CUSTOM_COURSES_EDX']: FIELD_OVERRIDE_PROVIDERS = tuple(FIELD_OVERRIDE_PROVIDERS) ##### Individual Due Date Extensions ##### -if FEATURES['INDIVIDUAL_DUE_DATES']: +if INDIVIDUAL_DUE_DATES: FIELD_OVERRIDE_PROVIDERS += ( 'lms.djangoapps.courseware.student_field_overrides.IndividualStudentOverrideProvider', ) @@ -375,7 +380,7 @@ PROFILE_IMAGE_DEFAULT_FILENAME = 'images/profiles/default' ##### Credit Provider Integration ##### ##################### LTI Provider ##################### -if FEATURES['ENABLE_LTI_PROVIDER']: +if ENABLE_LTI_PROVIDER: INSTALLED_APPS.append('lms.djangoapps.lti_provider.apps.LtiProviderConfig') AUTHENTICATION_BACKENDS.append('lms.djangoapps.lti_provider.users.LtiBackend') diff --git a/lms/envs/test.py b/lms/envs/test.py index c78604c0c1..c2b2b15569 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -27,10 +27,15 @@ from openedx.core.lib.derived import derive_settings from openedx.core.lib.tempdir import mkdtemp_clean from xmodule.modulestore.modulestore_settings import update_module_store_settings # lint-amnesty, pylint: disable=wrong-import-order +from openedx.core.lib.features_setting_proxy import FeaturesProxy + from .common import * from common.djangoapps.util.testing import patch_sessions, patch_testcase # pylint: disable=wrong-import-order +# A proxy for feature flags stored in the settings namespace +FEATURES = FeaturesProxy(globals()) + # This patch disables the commit_on_success decorator during tests # in TestCase subclasses. patch_testcase() @@ -55,33 +60,33 @@ MONGO_HOST = os.environ.get('EDXAPP_TEST_MONGO_HOST', 'localhost') THIS_UUID = uuid4().hex[:5] -FEATURES['DISABLE_SET_JWT_COOKIES_FOR_TESTS'] = True +DISABLE_SET_JWT_COOKIES_FOR_TESTS = True # can't test start dates with this True, but on the other hand, # can test everything else :) -FEATURES['DISABLE_START_DATES'] = True +DISABLE_START_DATES = True # Most tests don't use the discussion service, so we turn it off to speed them up. # Tests that do can enable this flag, but must use the UrlResetMixin class to force urls.py # to reload. For consistency in user-experience, keep the value of this setting in sync with # the one in cms/envs/test.py -FEATURES['ENABLE_DISCUSSION_SERVICE'] = False +ENABLE_DISCUSSION_SERVICE = False -FEATURES['ENABLE_SERVICE_STATUS'] = True +ENABLE_SERVICE_STATUS = True -FEATURES['ENABLE_VERIFIED_CERTIFICATES'] = True +ENABLE_VERIFIED_CERTIFICATES = True # Toggles embargo on for testing -FEATURES['EMBARGO'] = True +EMBARGO = True # Enable the milestones app in tests to be consistent with it being enabled in production -FEATURES['MILESTONES_APP'] = True +MILESTONES_APP = True -FEATURES['ENABLE_ENROLLMENT_TRACK_USER_PARTITION'] = True +ENABLE_ENROLLMENT_TRACK_USER_PARTITION = True -FEATURES['ENABLE_BULK_ENROLLMENT_VIEW'] = True +ENABLE_BULK_ENROLLMENT_VIEW = True -FEATURES['ENABLE_BULK_USER_RETIREMENT'] = True +ENABLE_BULK_USER_RETIREMENT = True DEFAULT_MOBILE_AVAILABLE = True @@ -221,13 +226,13 @@ CACHES = { ############################# SECURITY SETTINGS ################################ # Default to advanced security in common.py, so tests can reset here to use # a simpler security model -FEATURES['ENFORCE_PASSWORD_POLICY'] = False -FEATURES['ENABLE_MAX_FAILED_LOGIN_ATTEMPTS'] = False -FEATURES['SQUELCH_PII_IN_LOGS'] = False -FEATURES['PREVENT_CONCURRENT_LOGINS'] = False +ENFORCE_PASSWORD_POLICY = False +ENABLE_MAX_FAILED_LOGIN_ATTEMPTS = False +SQUELCH_PII_IN_LOGS = False +PREVENT_CONCURRENT_LOGINS = False ######### Third-party auth ########## -FEATURES['ENABLE_THIRD_PARTY_AUTH'] = True +ENABLE_THIRD_PARTY_AUTH = True AUTHENTICATION_BACKENDS = [ 'social_core.backends.google.GoogleOAuth2', @@ -250,12 +255,12 @@ THIRD_PARTY_AUTH_CUSTOM_AUTH_FORMS = { } ############################## OAUTH2 Provider ################################ -FEATURES['ENABLE_OAUTH2_PROVIDER'] = True +ENABLE_OAUTH2_PROVIDER = True OAUTH_ENFORCE_SECURE = False ########################### External REST APIs ################################# -FEATURES['ENABLE_MOBILE_REST_API'] = True -FEATURES['ENABLE_VIDEO_ABSTRACTION_LAYER_API'] = True +ENABLE_MOBILE_REST_API = True +ENABLE_VIDEO_ABSTRACTION_LAYER_API = True ################################# CELERY ###################################### @@ -343,7 +348,7 @@ PASSWORD_HASHERS = [ ] ### This enables the Metrics tab for the Instructor dashboard ########### -FEATURES['CLASS_DASHBOARD'] = True +CLASS_DASHBOARD = True ################### Make tests quieter @@ -393,13 +398,13 @@ MONGODB_LOG = { NOTES_DISABLED_TABS = [] # Enable EdxNotes for tests. -FEATURES['ENABLE_EDXNOTES'] = True +ENABLE_EDXNOTES = True # Enable courseware search for tests -FEATURES['ENABLE_COURSEWARE_SEARCH'] = True +ENABLE_COURSEWARE_SEARCH = True # Enable dashboard search for tests -FEATURES['ENABLE_DASHBOARD_SEARCH'] = True +ENABLE_DASHBOARD_SEARCH = True # Use MockSearchEngine as the search engine for test scenario SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine" @@ -410,7 +415,7 @@ FACEBOOK_API_VERSION = "v2.8" ######### custom courses ######### INSTALLED_APPS += ['lms.djangoapps.ccx', 'openedx.core.djangoapps.ccxcon.apps.CCXConnectorConfig'] -FEATURES['CUSTOM_COURSES_EDX'] = True +CUSTOM_COURSES_EDX = True # Set dummy values for profile image settings. PROFILE_IMAGE_BACKEND = { @@ -427,12 +432,12 @@ PROFILE_IMAGE_MAX_BYTES = 1024 * 1024 PROFILE_IMAGE_MIN_BYTES = 100 # Enable the LTI provider feature for testing -FEATURES['ENABLE_LTI_PROVIDER'] = True +ENABLE_LTI_PROVIDER = True INSTALLED_APPS.append('lms.djangoapps.lti_provider.apps.LtiProviderConfig') AUTHENTICATION_BACKENDS.append('lms.djangoapps.lti_provider.users.LtiBackend') # Financial assistance page -FEATURES['ENABLE_FINANCIAL_ASSISTANCE_FORM'] = True +ENABLE_FINANCIAL_ASSISTANCE_FORM = True COURSE_BLOCKS_API_EXTRA_FIELDS = [ ('course', 'course_visibility'), diff --git a/openedx/core/djangoapps/ace_common/settings/common.py b/openedx/core/djangoapps/ace_common/settings/common.py index 634ab328ba..5d10766ede 100644 --- a/openedx/core/djangoapps/ace_common/settings/common.py +++ b/openedx/core/djangoapps/ace_common/settings/common.py @@ -24,7 +24,7 @@ def plugin_settings(settings): # lint-amnesty, pylint: disable=missing-function settings.ACE_ROUTING_KEY = ACE_ROUTING_KEY - settings.FEATURES['test_django_plugin'] = True + settings.test_django_plugin = True settings.FCM_APP_NAME = 'fcm-edx-platform' settings.ACE_CHANNEL_DEFAULT_PUSH = 'push_notification' diff --git a/openedx/core/djangoapps/models/course_details.py b/openedx/core/djangoapps/models/course_details.py index c00d7d0b88..344be4ea75 100644 --- a/openedx/core/djangoapps/models/course_details.py +++ b/openedx/core/djangoapps/models/course_details.py @@ -71,9 +71,8 @@ class CourseDetails: self.pre_requisite_courses = [] # pre-requisite courses self.entrance_exam_enabled = "" # is entrance exam enabled self.entrance_exam_id = "" # the content location for the entrance exam - self.entrance_exam_minimum_score_pct = settings.FEATURES.get( - 'ENTRANCE_EXAM_MIN_SCORE_PCT', - '50' + self.entrance_exam_minimum_score_pct = str( + settings.ENTRANCE_EXAM_MIN_SCORE_PCT ) # minimum passing score for entrance exam content module/tree, self.self_paced = None self.learning_info = [] diff --git a/openedx/core/lib/features_setting_proxy.py b/openedx/core/lib/features_setting_proxy.py new file mode 100644 index 0000000000..9de2b3c44d --- /dev/null +++ b/openedx/core/lib/features_setting_proxy.py @@ -0,0 +1,106 @@ +""" +Features Proxy Implementation +""" +import warnings + +from collections.abc import MutableMapping, Mapping + + +class FeaturesProxy(MutableMapping): + """ + A proxy for feature flags stored in the settings namespace. + + Features: + - Flattens features from configuration (e.g., YAML or env) into the local settings namespace. + - Automatically updates `django.conf.settings` when a feature is modified. + - Acts like a dict (get, set, update, etc.). + + Example usage: + fp = FeaturesProxy(LocalNamespace) + fp["NEW_FEATURE"] = True + val = fp.get("EXISTING_FLAG", False) + """ + + def __init__(self, namespace=None): + """Store the namespace (as a dict)""" + self.ns = namespace or {} + + def __getitem__(self, key): + """Retrieve a feature flag by key""" + return self.ns[key] + + def __setitem__(self, key, value): + """Sets a key-value pair while emitting a deprecation warning about using FEATURES as a dict.""" + warnings.warn( + f"Accessing FEATURES as a dict is deprecated. " + f"Add '{key} = {value!r}' to your Django settings module instead of modifying FEATURES.", + DeprecationWarning, + stacklevel=2 + ) + self.ns[key] = value + + def __delitem__(self, key): + """Remove a feature flag from the namespace.""" + del self.ns[key] + + def __iter__(self): + return iter(self.ns) + + def __len__(self): + return len(self.ns) + + def __contains__(self, key): + return key in self.ns + + def clear(self): + """Remove all feature flags from the namespace.""" + self.ns.clear() + + def get(self, key, default=None): + """Standard dict-style get with default""" + return self.ns.get(key, default) + + def update(self, other=(), /, **kwds): + """ + Update multiple features at once, ensuring each goes through __setitem__ + to emit deprecation warnings. + + Mirrors dict.update() behavior: + - If `other` is a mapping, uses its keys. + - If `other` is iterable of pairs, updates from those. + - Then applies any keyword arguments. + + Examples: + proxy.update({'FEATURE_A': True}) + -> other = {'FEATURE_A': True} + + proxy.update([('FEATURE_A', True)]) + -> other = [('FEATURE_A', True)] + + proxy.update(FEATURE_B=False) + -> kwds = {'FEATURE_B': False} + + proxy.update({'FEATURE_A': True}, FEATURE_B=False) + -> other={'FEATURE_A': True}; kwds = {'FEATURE_B': False} + """ + if isinstance(other, Mapping): + # Handles objects that formally conform to the Mapping interface + # Mapping-like types: defaultdict, OrderedDict, Counter + for key in other: + self[key] = other[key] + elif hasattr(other, "keys"): + # Fallback for objects that implement a .keys() method but + # may not formally subclass Mapping + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + + def copy(self): + """ + Return a shallow copy of the underlying namespace wrapped in a new FeaturesProxy. + """ + return FeaturesProxy(self.ns.copy()) diff --git a/openedx/envs/common.py b/openedx/envs/common.py index c888ec8fd7..f6a9a8647a 100644 --- a/openedx/envs/common.py +++ b/openedx/envs/common.py @@ -884,3 +884,5 @@ API_DOCUMENTATION_URL = 'https://course-catalog-api-guide.readthedocs.io/en/late AUTH_DOCUMENTATION_URL = 'https://course-catalog-api-guide.readthedocs.io/en/latest/authentication/index.html' CSRF_TRUSTED_ORIGINS = [] + +ENTRANCE_EXAM_MIN_SCORE_PCT = 50 diff --git a/openedx/features/announcements/settings/common.py b/openedx/features/announcements/settings/common.py index 1a1a5ca497..4de3740d2d 100644 --- a/openedx/features/announcements/settings/common.py +++ b/openedx/features/announcements/settings/common.py @@ -16,6 +16,6 @@ def plugin_settings(settings): .. toggle_creation_date: 2017-11-08 .. toggle_tickets: https://github.com/openedx/edx-platform/pull/16496 """ - settings.FEATURES['ENABLE_ANNOUNCEMENTS'] = False + settings.ENABLE_ANNOUNCEMENTS = False # Configure number of announcements to show per page - settings.FEATURES['ANNOUNCEMENTS_PER_PAGE'] = 5 + settings.ANNOUNCEMENTS_PER_PAGE = 5 diff --git a/openedx/features/announcements/settings/test.py b/openedx/features/announcements/settings/test.py index 47d57ca3dc..8c8406d23f 100644 --- a/openedx/features/announcements/settings/test.py +++ b/openedx/features/announcements/settings/test.py @@ -5,4 +5,4 @@ def plugin_settings(settings): """ Test settings for Announcements """ - settings.FEATURES['ENABLE_ANNOUNCEMENTS'] = True + settings.ENABLE_ANNOUNCEMENTS = True From 11a82bbaf738019ebc33cf64dc175c501a73e8c8 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Mon, 15 Sep 2025 22:35:56 -0400 Subject: [PATCH 04/27] chore: Upgrade Python requirements --- requirements/edx-sandbox/base.txt | 6 +-- requirements/edx/base.txt | 22 +++++----- requirements/edx/development.txt | 40 +++++++++---------- requirements/edx/doc.txt | 22 +++++----- requirements/edx/semgrep.txt | 2 +- requirements/edx/testing.txt | 30 +++++++------- scripts/user_retirement/requirements/base.txt | 20 +++++----- .../user_retirement/requirements/testing.txt | 22 +++++----- 8 files changed, 82 insertions(+), 82 deletions(-) diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt index 4b5fd28677..d1bb7ca61c 100644 --- a/requirements/edx-sandbox/base.txt +++ b/requirements/edx-sandbox/base.txt @@ -60,9 +60,9 @@ packaging==25.0 # via matplotlib pillow==11.3.0 # via matplotlib -pycparser==2.22 +pycparser==2.23 # via cffi -pyparsing==3.2.3 +pyparsing==3.2.4 # via # -r requirements/edx-sandbox/base.in # chem @@ -74,7 +74,7 @@ random2==1.0.2 # via -r requirements/edx-sandbox/base.in regex==2025.9.1 # via nltk -scipy==1.16.1 +scipy==1.16.2 # via # -r requirements/edx-sandbox/base.in # chem diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index c28f8e68cd..fbc7a37dc0 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -70,14 +70,14 @@ bleach[css]==6.2.0 # xblock-poll boto==2.49.0 # via -r requirements/edx/kernel.in -boto3==1.40.26 +boto3==1.40.31 # via # -r requirements/edx/kernel.in # django-ses # fs-s3fs # ora2 # snowflake-connector-python -botocore==1.40.26 +botocore==1.40.31 # via # -r requirements/edx/kernel.in # boto3 @@ -620,7 +620,7 @@ google-cloud-core==2.4.3 # google-cloud-storage google-cloud-firestore==2.21.0 # via firebase-admin -google-cloud-storage==3.3.1 +google-cloud-storage==3.4.0 # via firebase-admin google-crc32c==1.7.1 # via @@ -898,7 +898,7 @@ proto-plus==1.26.1 # via # google-api-core # google-cloud-firestore -protobuf==6.32.0 +protobuf==6.32.1 # via # google-api-core # google-cloud-firestore @@ -918,14 +918,14 @@ pyasn1-modules==0.4.2 # via google-auth pycountry==24.6.1 # via -r requirements/edx/kernel.in -pycparser==2.22 +pycparser==2.23 # via cffi pycryptodomex==3.23.0 # via # -r requirements/edx/kernel.in # edx-proctoring # lti-consumer-xblock -pydantic==2.11.7 +pydantic==2.11.9 # via camel-converter pydantic-core==2.33.2 # via pydantic @@ -956,15 +956,15 @@ pymongo==4.4.0 # event-tracking # mongoengine # openedx-forum -pynacl==1.5.0 +pynacl==1.6.0 # via # edx-django-utils # paramiko pynliner==0.8.0 # via -r requirements/edx/kernel.in -pyopenssl==25.1.0 +pyopenssl==25.2.0 # via snowflake-connector-python -pyparsing==3.2.3 +pyparsing==3.2.4 # via # chem # openedx-calc @@ -1082,11 +1082,11 @@ rules==3.5 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 sailthru-client==2.2.3 # via edx-ace -scipy==1.16.1 +scipy==1.16.2 # via chem semantic-version==2.10.0 # via edx-drf-extensions diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 1096e5a455..e24448b307 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -140,7 +140,7 @@ boto==2.49.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -boto3==1.40.26 +boto3==1.40.31 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -148,7 +148,7 @@ boto3==1.40.26 # fs-s3fs # ora2 # snowflake-connector-python -botocore==1.40.26 +botocore==1.40.31 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -586,12 +586,12 @@ django-storages==1.14.6 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edxval -django-stubs[compatible-mypy]==5.2.2 +django-stubs[compatible-mypy]==5.2.5 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/development.in # djangorestframework-stubs -django-stubs-ext==5.2.2 +django-stubs-ext==5.2.5 # via django-stubs django-user-tasks==3.4.3 # via @@ -899,7 +899,7 @@ execnet==2.1.1 # pytest-xdist factory-boy==3.3.3 # via -r requirements/edx/testing.txt -faker==37.6.0 +faker==37.8.0 # via # -r requirements/edx/testing.txt # factory-boy @@ -985,7 +985,7 @@ google-cloud-firestore==2.21.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # firebase-admin -google-cloud-storage==3.3.1 +google-cloud-storage==3.4.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1086,7 +1086,7 @@ imagesize==1.4.1 # via # -r requirements/edx/doc.txt # sphinx -import-linter==2.4 +import-linter==2.5 # via -r requirements/edx/testing.txt importlib-metadata==8.7.0 # via @@ -1302,7 +1302,7 @@ multidict==6.6.4 # -r requirements/edx/testing.txt # aiohttp # yarl -mypy==1.17.1 +mypy==1.18.1 # via # -r requirements/edx/development.in # django-stubs @@ -1513,7 +1513,7 @@ proto-plus==1.26.1 # -r requirements/edx/testing.txt # google-api-core # google-cloud-firestore -protobuf==6.32.0 +protobuf==6.32.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1551,7 +1551,7 @@ pycountry==24.6.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -pycparser==2.22 +pycparser==2.23 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1562,7 +1562,7 @@ pycryptodomex==3.23.0 # -r requirements/edx/testing.txt # edx-proctoring # lti-consumer-xblock -pydantic==2.11.7 +pydantic==2.11.9 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1645,7 +1645,7 @@ pymongo==4.4.0 # event-tracking # mongoengine # openedx-forum -pynacl==1.5.0 +pynacl==1.6.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1655,12 +1655,12 @@ pynliner==0.8.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -pyopenssl==25.1.0 +pyopenssl==25.2.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # snowflake-connector-python -pyparsing==3.2.3 +pyparsing==3.2.4 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1700,7 +1700,7 @@ pytest==8.2.0 # pytest-xdist pytest-attrib==0.1.3 # via -r requirements/edx/testing.txt -pytest-cov==6.3.0 +pytest-cov==7.0.0 # via -r requirements/edx/testing.txt pytest-django==4.11.1 # via -r requirements/edx/testing.txt @@ -1710,7 +1710,7 @@ pytest-metadata==3.1.1 # via # -r requirements/edx/testing.txt # pytest-json-report -pytest-randomly==3.16.0 +pytest-randomly==4.0.1 # via -r requirements/edx/testing.txt pytest-xdist[psutil]==3.8.0 # via -r requirements/edx/testing.txt @@ -1870,7 +1870,7 @@ rules==3.5 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.13.1 +s3transfer==0.14.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1880,7 +1880,7 @@ sailthru-client==2.2.3 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-ace -scipy==1.16.1 +scipy==1.16.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -2098,11 +2098,11 @@ tqdm==4.67.1 # -r requirements/edx/testing.txt # nltk # openai -types-pyyaml==6.0.12.20250822 +types-pyyaml==6.0.12.20250915 # via # django-stubs # djangorestframework-stubs -types-requests==2.32.4.20250809 +types-requests==2.32.4.20250913 # via djangorestframework-stubs typing-extensions==4.15.0 # via diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 13dc4c5c26..e2cfeef3eb 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -105,14 +105,14 @@ bleach[css]==6.2.0 # xblock-poll boto==2.49.0 # via -r requirements/edx/base.txt -boto3==1.40.26 +boto3==1.40.31 # via # -r requirements/edx/base.txt # django-ses # fs-s3fs # ora2 # snowflake-connector-python -botocore==1.40.26 +botocore==1.40.31 # via # -r requirements/edx/base.txt # boto3 @@ -725,7 +725,7 @@ google-cloud-firestore==2.21.0 # via # -r requirements/edx/base.txt # firebase-admin -google-cloud-storage==3.3.1 +google-cloud-storage==3.4.0 # via # -r requirements/edx/base.txt # firebase-admin @@ -1090,7 +1090,7 @@ proto-plus==1.26.1 # -r requirements/edx/base.txt # google-api-core # google-cloud-firestore -protobuf==6.32.0 +protobuf==6.32.1 # via # -r requirements/edx/base.txt # google-api-core @@ -1114,7 +1114,7 @@ pyasn1-modules==0.4.2 # google-auth pycountry==24.6.1 # via -r requirements/edx/base.txt -pycparser==2.22 +pycparser==2.23 # via # -r requirements/edx/base.txt # cffi @@ -1123,7 +1123,7 @@ pycryptodomex==3.23.0 # -r requirements/edx/base.txt # edx-proctoring # lti-consumer-xblock -pydantic==2.11.7 +pydantic==2.11.9 # via # -r requirements/edx/base.txt # camel-converter @@ -1168,18 +1168,18 @@ pymongo==4.4.0 # event-tracking # mongoengine # openedx-forum -pynacl==1.5.0 +pynacl==1.6.0 # via # -r requirements/edx/base.txt # edx-django-utils # paramiko pynliner==0.8.0 # via -r requirements/edx/base.txt -pyopenssl==25.1.0 +pyopenssl==25.2.0 # via # -r requirements/edx/base.txt # snowflake-connector-python -pyparsing==3.2.3 +pyparsing==3.2.4 # via # -r requirements/edx/base.txt # chem @@ -1318,7 +1318,7 @@ rules==3.5 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.13.1 +s3transfer==0.14.0 # via # -r requirements/edx/base.txt # boto3 @@ -1326,7 +1326,7 @@ sailthru-client==2.2.3 # via # -r requirements/edx/base.txt # edx-ace -scipy==1.16.1 +scipy==1.16.2 # via # -r requirements/edx/base.txt # chem diff --git a/requirements/edx/semgrep.txt b/requirements/edx/semgrep.txt index e5fe1dc79f..aec4c59acb 100644 --- a/requirements/edx/semgrep.txt +++ b/requirements/edx/semgrep.txt @@ -113,7 +113,7 @@ ruamel-yaml==0.18.15 # via semgrep ruamel-yaml-clib==0.2.12 # via ruamel-yaml -semgrep==1.135.0 +semgrep==1.136.0 # via -r requirements/edx/semgrep.in tomli==2.0.2 # via semgrep diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 29f63b9366..4a822005fc 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -102,14 +102,14 @@ bleach[css]==6.2.0 # xblock-poll boto==2.49.0 # via -r requirements/edx/base.txt -boto3==1.40.26 +boto3==1.40.31 # via # -r requirements/edx/base.txt # django-ses # fs-s3fs # ora2 # snowflake-connector-python -botocore==1.40.26 +botocore==1.40.31 # via # -r requirements/edx/base.txt # boto3 @@ -694,7 +694,7 @@ execnet==2.1.1 # via pytest-xdist factory-boy==3.3.3 # via -r requirements/edx/testing.in -faker==37.6.0 +faker==37.8.0 # via factory-boy fastapi==0.116.1 # via pact-python @@ -756,7 +756,7 @@ google-cloud-firestore==2.21.0 # via # -r requirements/edx/base.txt # firebase-admin -google-cloud-storage==3.3.1 +google-cloud-storage==3.4.0 # via # -r requirements/edx/base.txt # firebase-admin @@ -831,7 +831,7 @@ idna==3.10 # requests # snowflake-connector-python # yarl -import-linter==2.4 +import-linter==2.5 # via -r requirements/edx/testing.in importlib-metadata==8.7.0 # via -r requirements/edx/base.txt @@ -1147,7 +1147,7 @@ proto-plus==1.26.1 # -r requirements/edx/base.txt # google-api-core # google-cloud-firestore -protobuf==6.32.0 +protobuf==6.32.1 # via # -r requirements/edx/base.txt # google-api-core @@ -1179,7 +1179,7 @@ pycodestyle==2.8.0 # -r requirements/edx/testing.in pycountry==24.6.1 # via -r requirements/edx/base.txt -pycparser==2.22 +pycparser==2.23 # via # -r requirements/edx/base.txt # cffi @@ -1188,7 +1188,7 @@ pycryptodomex==3.23.0 # -r requirements/edx/base.txt # edx-proctoring # lti-consumer-xblock -pydantic==2.11.7 +pydantic==2.11.9 # via # -r requirements/edx/base.txt # camel-converter @@ -1247,18 +1247,18 @@ pymongo==4.4.0 # event-tracking # mongoengine # openedx-forum -pynacl==1.5.0 +pynacl==1.6.0 # via # -r requirements/edx/base.txt # edx-django-utils # paramiko pynliner==0.8.0 # via -r requirements/edx/base.txt -pyopenssl==25.1.0 +pyopenssl==25.2.0 # via # -r requirements/edx/base.txt # snowflake-connector-python -pyparsing==3.2.3 +pyparsing==3.2.4 # via # -r requirements/edx/base.txt # chem @@ -1288,7 +1288,7 @@ pytest==8.2.0 # pytest-xdist pytest-attrib==0.1.3 # via -r requirements/edx/testing.in -pytest-cov==6.3.0 +pytest-cov==7.0.0 # via -r requirements/edx/testing.in pytest-django==4.11.1 # via -r requirements/edx/testing.in @@ -1298,7 +1298,7 @@ pytest-metadata==3.1.1 # via # -r requirements/edx/testing.in # pytest-json-report -pytest-randomly==3.16.0 +pytest-randomly==4.0.1 # via -r requirements/edx/testing.in pytest-xdist[psutil]==3.8.0 # via -r requirements/edx/testing.in @@ -1425,7 +1425,7 @@ rules==3.5 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.13.1 +s3transfer==0.14.0 # via # -r requirements/edx/base.txt # boto3 @@ -1433,7 +1433,7 @@ sailthru-client==2.2.3 # via # -r requirements/edx/base.txt # edx-ace -scipy==1.16.1 +scipy==1.16.2 # via # -r requirements/edx/base.txt # chem diff --git a/scripts/user_retirement/requirements/base.txt b/scripts/user_retirement/requirements/base.txt index 8b576aead5..a2cf89a5d8 100644 --- a/scripts/user_retirement/requirements/base.txt +++ b/scripts/user_retirement/requirements/base.txt @@ -10,9 +10,9 @@ attrs==25.3.0 # via zeep backoff==2.2.1 # via -r scripts/user_retirement/requirements/base.in -boto3==1.40.26 +boto3==1.40.31 # via -r scripts/user_retirement/requirements/base.in -botocore==1.40.26 +botocore==1.40.31 # via # boto3 # s3transfer @@ -59,7 +59,7 @@ google-auth-httplib2==0.2.0 # via google-api-python-client googleapis-common-protos==1.70.0 # via google-api-core -httplib2==0.30.0 +httplib2==0.31.0 # via # google-api-python-client # google-auth-httplib2 @@ -67,7 +67,7 @@ idna==3.10 # via requests isodate==0.7.2 # via zeep -jenkinsapi==0.3.15 +jenkinsapi==0.3.16 # via -r scripts/user_retirement/requirements/base.in jmespath==1.0.1 # via @@ -83,7 +83,7 @@ platformdirs==4.4.0 # via zeep proto-plus==1.26.1 # via google-api-core -protobuf==6.32.0 +protobuf==6.32.1 # via # google-api-core # googleapis-common-protos @@ -96,15 +96,15 @@ pyasn1==0.6.1 # rsa pyasn1-modules==0.4.2 # via google-auth -pycparser==2.22 +pycparser==2.23 # via cffi pyjwt[crypto]==2.10.1 # via # edx-rest-api-client # simple-salesforce -pynacl==1.5.0 +pynacl==1.6.0 # via edx-django-utils -pyparsing==3.2.3 +pyparsing==3.2.4 # via httplib2 python-dateutil==2.9.0.post0 # via botocore @@ -130,7 +130,7 @@ requests-toolbelt==1.0.0 # via zeep rsa==4.9.1 # via google-auth -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 simple-salesforce==1.12.9 # via -r scripts/user_retirement/requirements/base.in @@ -152,5 +152,5 @@ urllib3==2.5.0 # via # botocore # requests -zeep==4.3.1 +zeep==4.3.2 # via simple-salesforce diff --git a/scripts/user_retirement/requirements/testing.txt b/scripts/user_retirement/requirements/testing.txt index f5b98650f3..bd11766dd0 100644 --- a/scripts/user_retirement/requirements/testing.txt +++ b/scripts/user_retirement/requirements/testing.txt @@ -14,11 +14,11 @@ attrs==25.3.0 # zeep backoff==2.2.1 # via -r scripts/user_retirement/requirements/base.txt -boto3==1.40.26 +boto3==1.40.31 # via # -r scripts/user_retirement/requirements/base.txt # moto -botocore==1.40.26 +botocore==1.40.31 # via # -r scripts/user_retirement/requirements/base.txt # boto3 @@ -92,7 +92,7 @@ googleapis-common-protos==1.70.0 # via # -r scripts/user_retirement/requirements/base.txt # google-api-core -httplib2==0.30.0 +httplib2==0.31.0 # via # -r scripts/user_retirement/requirements/base.txt # google-api-python-client @@ -107,7 +107,7 @@ isodate==0.7.2 # via # -r scripts/user_retirement/requirements/base.txt # zeep -jenkinsapi==0.3.15 +jenkinsapi==0.3.16 # via -r scripts/user_retirement/requirements/base.txt jinja2==3.1.6 # via moto @@ -144,7 +144,7 @@ proto-plus==1.26.1 # via # -r scripts/user_retirement/requirements/base.txt # google-api-core -protobuf==6.32.0 +protobuf==6.32.1 # via # -r scripts/user_retirement/requirements/base.txt # google-api-core @@ -163,7 +163,7 @@ pyasn1-modules==0.4.2 # via # -r scripts/user_retirement/requirements/base.txt # google-auth -pycparser==2.22 +pycparser==2.23 # via # -r scripts/user_retirement/requirements/base.txt # cffi @@ -174,11 +174,11 @@ pyjwt[crypto]==2.10.1 # -r scripts/user_retirement/requirements/base.txt # edx-rest-api-client # simple-salesforce -pynacl==1.5.0 +pynacl==1.6.0 # via # -r scripts/user_retirement/requirements/base.txt # edx-django-utils -pyparsing==3.2.3 +pyparsing==3.2.4 # via # -r scripts/user_retirement/requirements/base.txt # httplib2 @@ -229,7 +229,7 @@ rsa==4.9.1 # via # -r scripts/user_retirement/requirements/base.txt # google-auth -s3transfer==0.13.1 +s3transfer==0.14.0 # via # -r scripts/user_retirement/requirements/base.txt # boto3 @@ -268,9 +268,9 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.15.1 +xmltodict==1.0.0 # via moto -zeep==4.3.1 +zeep==4.3.2 # via # -r scripts/user_retirement/requirements/base.txt # simple-salesforce From b5d4ac44ebf02d2f7d4d473bd4cbf2736d021180 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 12:51:24 +0500 Subject: [PATCH 05/27] feat: Upgrade Python dependency edx-enterprise (#37360) fix: Moodle configuration updates were not being saved correctly Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` Co-authored-by: zamanafzal <11922730+zamanafzal@users.noreply.github.com> Co-authored-by: Zaman Afzal --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index e0543146ff..72b27f8fbe 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -47,7 +47,7 @@ django-stubs<6 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==6.4.0 +edx-enterprise==6.4.1 # Date: 2023-07-26 # Our legacy Sass code is incompatible with anything except this ancient libsass version. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index c28f8e68cd..4ea283974d 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -476,7 +476,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==6.4.0 +edx-enterprise==6.4.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 1096e5a455..7411d9a3fd 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -752,7 +752,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==6.4.0 +edx-enterprise==6.4.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 13dc4c5c26..11bc019fec 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -560,7 +560,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==6.4.0 +edx-enterprise==6.4.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 29f63b9366..65fbe4d90d 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -581,7 +581,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==6.4.0 +edx-enterprise==6.4.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From a11086ffac05f8296937d63a4ecf387e5d450792 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 17 Sep 2025 14:50:24 +0530 Subject: [PATCH 06/27] feat: allow editing imported text blocks (#37124) * feat: allow editing html block imported from upstream The modified field is left untouched in future sync while storing the upstream values in hidden fields to allow authors to revert to upstream version at any point. * fix: sync downstream_customized field for copy-pasted modified block * test: add more tests * fix: lint issues * test: copy paste * feat: skip sync if html data is modified * feat: update upstream fields only when modified * refactor: use version_synced field to skip sync * feat: edit title inplace for library source components * fixup! feat: edit title inplace for library source components * fix: edit title button style * fix: test case * fix: lint issue * refactor: don't show different icon for modified upstream blocks * Revert "refactor: use version_synced field to skip sync" This reverts commit 8b784fff2f49b43702c952e7f955bd4048e8cc69. * feat: only skip sync for modified blocks if updated as part of container * refactor: update sync behaviour when synced individually and as part of parent * feat: include ready to sync children info in downstream link get api * test: fix failing tests * fix: lint issues * feat: new tests and update api to allow overriding modified fields in sync * test: api changes * refactor: edit options should be visible for individual imports * docs: update api docs * chore: remove old comments --- cms/djangoapps/contentstore/helpers.py | 9 + .../rest_api/v2/views/downstreams.py | 8 +- .../tests/test_downstream_sync_integration.py | 353 +++++++++++++++++- .../v2/views/tests/test_downstreams.py | 24 ++ cms/djangoapps/contentstore/views/preview.py | 18 +- .../xblock_storage_handlers/view_handlers.py | 29 +- cms/envs/common.py | 2 +- cms/lib/xblock/test/test_upstream_sync.py | 180 +++++---- cms/lib/xblock/upstream_sync.py | 217 ++++++----- cms/lib/xblock/upstream_sync_block.py | 78 ++-- cms/static/js/views/pages/container.js | 32 ++ .../sass/course-unit-mfe-iframe-bundle.scss | 9 +- cms/templates/studio_xblock_wrapper.html | 170 +++++---- .../lib/xblock_serializer/block_serializer.py | 10 +- xmodule/html_block.py | 14 + 15 files changed, 862 insertions(+), 291 deletions(-) diff --git a/cms/djangoapps/contentstore/helpers.py b/cms/djangoapps/contentstore/helpers.py index 103c34a7b3..2bdbabc7df 100644 --- a/cms/djangoapps/contentstore/helpers.py +++ b/cms/djangoapps/contentstore/helpers.py @@ -2,6 +2,7 @@ Helper methods for Studio views. """ from __future__ import annotations +import json import logging import pathlib import urllib @@ -493,6 +494,14 @@ def _fetch_and_set_upstream_link( # new values from the published upstream content. if isinstance(upstream_link.upstream_key, UsageKey): # only if upstream is a block, not a container fetch_customizable_fields_from_block(downstream=temp_xblock, user=user, upstream=temp_xblock) + # Although the above function will set all customisable fields to match its upstream_* counterpart + # We copy the downstream_customized list to the new block to avoid overriding user customisations on sync + # So we will have: + # temp_xblock.display_name == temp_xblock.upstream_display_name + # temp_xblock.data == temp_xblock.upstream_data # for html blocks + # Even then we want to set `downstream_customized` value to avoid overriding user customisations on sync + downstream_customized = temp_xblock.xml_attributes.get("downstream_customized", '[]') + temp_xblock.downstream_customized = json.loads(downstream_customized) def _import_xml_node_to_parent( diff --git a/cms/djangoapps/contentstore/rest_api/v2/views/downstreams.py b/cms/djangoapps/contentstore/rest_api/v2/views/downstreams.py index d417707029..7cfd095ae9 100644 --- a/cms/djangoapps/contentstore/rest_api/v2/views/downstreams.py +++ b/cms/djangoapps/contentstore/rest_api/v2/views/downstreams.py @@ -394,7 +394,7 @@ class DownstreamView(DeveloperErrorViewMixin, APIView): Inspect an XBlock's link to upstream content. """ downstream = _load_accessible_block(request.user, usage_key_string, require_write_access=False) - return Response(UpstreamLink.try_get_for_block(downstream).to_json()) + return Response(UpstreamLink.try_get_for_block(downstream).to_json(include_child_info=True)) def put(self, request: _AuthenticatedRequest, usage_key_string: str) -> Response: """ @@ -499,6 +499,12 @@ class SyncFromUpstreamView(DeveloperErrorViewMixin, APIView): def post(self, request: _AuthenticatedRequest, usage_key_string: str) -> Response: """ Pull latest updates to the block at {usage_key_string} from its linked upstream content. + Optionally accepts json data in below format to control override of locally customized fields + { + "override_customizations": True or False (default: False), + "keep_custom_fields": [] (If override_customizations is True, this key can be used to preserve + some fields from override). + } """ downstream = _load_accessible_block(request.user, usage_key_string, require_write_access=True) try: diff --git a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py index c03f3f4e33..8eba50c829 100644 --- a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py +++ b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py @@ -15,6 +15,7 @@ from cms.djangoapps.contentstore.xblock_storage_handlers.xblock_helpers import g from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory +from xmodule.xml_block import serialize_field @ddt.ddt @@ -78,6 +79,29 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): data = self._api('get', f'/api/olx-export/v1/xblock/{usage_key}/', {}, expect_response=200) return data["blocks"][data["root_block_id"]]["olx"] + def _copy_course_block(self, usage_key: str): + """ + Copy a course block to the clipboard + """ + data = self._api( + 'post', + "/api/content-staging/v1/clipboard/", + {"usage_key": usage_key}, + expect_response=200 + ) + return data + + def _paste_course_block(self, parent_usage_key: str): + """ + Paste a course block from the clipboard + """ + return self._api( + 'post', + '/xblock/', + {"parent_locator": parent_usage_key, "staged_content": "clipboard"}, + expect_response=200 + ) + # def _get_course_block_fields(self, usage_key: str): # return self._api('get', f'/xblock/{usage_key}', {}, expect_response=200) @@ -173,6 +197,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): 'version_declined': None, 'ready_to_sync': False, 'error_message': None, + 'is_modified': False, # 'upstream_link': 'http://course-authoring-mfe/library/lib:CL-TEST:testlib/components?usageKey=...' }) assert status["upstream_link"].startswith("http://course-authoring-mfe/library/") @@ -216,6 +241,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): upstream="{self.upstream_problem1['id']}" upstream_display_name="Problem 1 Display Name" upstream_version="2" + downstream_customized="["display_name"]" {self.standard_capa_attributes} >multiple choice... """) @@ -228,6 +254,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): 'version_declined': None, 'ready_to_sync': True, # <--- updated 'error_message': None, + 'is_modified': True, }) # 3️⃣ Now, sync and check the resulting OLX of the downstream @@ -247,6 +274,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): max_attempts="3" upstream="{self.upstream_problem1['id']}" upstream_display_name="Problem 1 NEW name" + downstream_customized="["display_name"]" upstream_version="3" {self.standard_capa_attributes} >multiple choice v2... @@ -268,9 +296,9 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): parent_usage_key=str(self.course_subsection.usage_key), upstream_key=self.upstream_unit["id"], ) - downstream_unit_block_key = get_block_key_dict( + downstream_unit_block_key = serialize_field(get_block_key_dict( UsageKey.from_string(downstream_unit["locator"]), - ) + )).replace('"', '"') status = self._get_sync_status(downstream_unit["locator"]) self.assertDictContainsEntries(status, { 'upstream_ref': self.upstream_unit["id"], # e.g. 'lct:CL-TEST:testlib:unit:u1' @@ -279,6 +307,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): 'version_declined': None, 'ready_to_sync': False, 'error_message': None, + 'is_modified': False, # 'upstream_link': 'http://course-authoring-mfe/library/lib:CL-TEST:testlib/units/...' }) assert status["upstream_link"].startswith("http://course-authoring-mfe/library/") @@ -302,6 +331,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): editor="visual" upstream="{self.upstream_html1['id']}" upstream_version="2" + upstream_data="This is the HTML." top_level_downstream_parent_key="{downstream_unit_block_key}" >This is the HTML. This is the HTML. @@ -580,6 +613,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): 'version_declined': None, 'ready_to_sync': True, 'error_message': None, + 'is_modified': False, }) # Sync and check the resulting OLX of the downstream @@ -598,6 +632,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): editor="visual" upstream="{self.upstream_html1['id']}" upstream_version="2" + upstream_data="This is the HTML." top_level_downstream_parent_key="{downstream_unit_block_key}" >This is the HTML. This is the HTML. @@ -831,6 +867,313 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): self.assertEqual(data["count"], 4) self.assertListEqual(data["results"], expected_downstreams) + def test_unit_sync_with_modified_downstream(self): + """ + Test that modified children component is not overridden when syncing parent container like unit + """ + # pylint: disable=too-many-statements + + # 1️⃣ Create a "vertical" block in the course based on a "unit" container: + downstream_unit = self._create_block_from_upstream( + # The API consumer needs to specify "vertical" here, even though upstream is "unit". + # In the future we could create a nicer REST API endpoint for this that's not part of + # the messy '/xblock/' API and which auto-detects the types based on the upstream_key. + block_category="vertical", + parent_usage_key=str(self.course_subsection.usage_key), + upstream_key=self.upstream_unit["id"], + ) + downstream_unit_block_key = serialize_field(get_block_key_dict( + UsageKey.from_string(downstream_unit["locator"]), + )).replace('"', '"') + status = self._get_sync_status(downstream_unit["locator"]) + self.assertDictContainsEntries(status, { + 'upstream_ref': self.upstream_unit["id"], # e.g. 'lct:CL-TEST:testlib:unit:u1' + 'version_available': 2, + 'version_synced': 2, + 'version_declined': None, + 'ready_to_sync': False, + 'error_message': None, + 'is_modified': False, + # 'upstream_link': 'http://course-authoring-mfe/library/lib:CL-TEST:testlib/units/...' + }) + assert status["upstream_link"].startswith("http://course-authoring-mfe/library/") + assert status["upstream_link"].endswith(f"/units/{self.upstream_unit['id']}") + + # Check that the downstream container matches our expectations. + # Note that: + # (1) Every XBlock has an "upstream" field + # (2) some "downstream only" fields like weight and max_attempts are omitted. + # (3) The "top_level_downstream_parent" is the container created + self.assertXmlEqual(self._get_course_block_olx(downstream_unit["locator"]), f""" + + This is the HTML. + multiple choice... + multi select... + + """) + + children_downstream_keys = self._get_course_block_children(downstream_unit["locator"]) + downstream_html1 = children_downstream_keys[0] + assert "type@html" in downstream_html1 + downstream_problem1 = children_downstream_keys[1] + assert "type@problem" in downstream_problem1 + downstream_problem2 = children_downstream_keys[2] + assert "type@problem" in downstream_problem2 + + # 2️⃣ Now, lets modify the upstream problem 1 and upstream html 1: + self._set_library_block_olx( + self.upstream_problem1["id"], + 'multiple choice v2...' + ) + self._set_library_block_olx( + self.upstream_html1["id"], + 'updated content upstream 1' + ) + self._publish_container(self.upstream_unit["id"]) + + status = self._get_sync_status(downstream_unit["locator"]) + self.assertDictContainsEntries(status, { + 'upstream_ref': self.upstream_unit["id"], # e.g. 'lct:CL-TEST:testlib:unit:u1' + 'version_available': 2, # <--- not updated since we didn't directly modify the unit + 'version_synced': 2, + 'version_declined': None, + 'ready_to_sync': True, # <--- It's the top-level parent of the block + 'error_message': None, + 'is_modified': False, + }) + + # Check the upstream/downstream status of [one of] the children + + self.assertDictContainsEntries(self._get_sync_status(downstream_problem1), { + 'upstream_ref': self.upstream_problem1["id"], + 'version_available': 3, # <--- updated since we modified the problem + 'version_synced': 2, + 'version_declined': None, + 'ready_to_sync': False, # <-- It has top-level parent, the parent is the one who must synchronize + 'error_message': None, + 'is_modified': False, + }) + + self.assertDictContainsEntries(self._get_sync_status(downstream_html1), { + 'upstream_ref': self.upstream_html1["id"], + 'version_available': 3, # <--- updated since we modified the problem + 'version_synced': 2, + 'version_declined': None, + 'ready_to_sync': False, # <-- It has top-level parent, the parent is the one who must synchronize + 'error_message': None, + 'is_modified': False, + }) + + # Now let's modify course html block + self._update_course_block_fields(downstream_html1, { + "data": "The new downstream data.", + }) + + # Sync and check the resulting OLX of the downstream + self._sync_downstream(downstream_unit["locator"]) + + # Notice how the customization of html block i.e. modified child block is preserved by + # not updating any of the fields except for upstream_* fields + self.assertXmlEqual(self._get_course_block_olx(downstream_unit["locator"]), f""" + + The new downstream data. + + multiple choice v2... + multi select... + + """) + + def test_modified_html_copy_paste(self): + """ + Test that we can sync a html from a library into a course. + """ + # 1️⃣ First, create the html in the course, using the upstream problem as a template: + downstream_html1 = self._create_block_from_upstream( + block_category="html", + parent_usage_key=str(self.course_subsection.usage_key), + upstream_key=self.upstream_html1["id"], + ) + status = self._get_sync_status(downstream_html1["locator"]) + self.assertDictContainsEntries(status, { + 'upstream_ref': self.upstream_html1["id"], # e.g. 'lb:CL-TEST:testlib:html:html1' + 'version_available': 2, + 'version_synced': 2, + 'version_declined': None, + 'ready_to_sync': False, + 'error_message': None, + 'is_modified': False, + # 'upstream_link': 'http://course-authoring-mfe/library/lib:CL-TEST:testlib/components?usageKey=...' + }) + assert status["upstream_link"].startswith("http://course-authoring-mfe/library/") + assert status["upstream_link"].endswith(f"/components?usageKey={self.upstream_html1['id']}") + + # Check the OLX of the downstream block. Notice that: + # (1) fields display_name and data (content/body of the ) are synced. + self.assertXmlEqual(self._get_course_block_olx(downstream_html1["locator"]), f""" + This is the HTML. + """) + + # 2️⃣ Now, lets modify the upstream html AND the downstream display_name: + self._update_course_block_fields(downstream_html1["locator"], { + "display_name": "New Text Content", + }) + + self._set_library_block_olx( + self.upstream_html1["id"], + 'The new upstream data.' + ) + self._publish_library_block(self.upstream_html1["id"]) + + # Here's how the downstream OLX looks now, before we sync: + self.assertXmlEqual(self._get_course_block_olx(downstream_html1["locator"]), f""" + This is the HTML. + """) + + status = self._get_sync_status(downstream_html1["locator"]) + self.assertDictContainsEntries(status, { + 'upstream_ref': self.upstream_html1["id"], # e.g. 'lb:CL-TEST:testlib:html:html1' + 'version_available': 3, # <--- updated + 'version_synced': 2, + 'version_declined': None, + 'ready_to_sync': True, # <--- updated + 'error_message': None, + 'is_modified': True, + }) + + # 3️⃣ Now, sync and check the resulting OLX of the downstream + + self._sync_downstream(downstream_html1["locator"]) + + # Here's how the downstream OLX looks now, after we synced it. + # All customizations are preserved based on the post data + self.assertXmlEqual(self._get_course_block_olx(downstream_html1["locator"]), f""" + The new upstream data. + """) + + self._update_course_block_fields(downstream_html1["locator"], { + "data": "The new downstream data.", + }) + + # Here's how the downstream OLX looks now + self.assertXmlEqual(self._get_course_block_olx(downstream_html1["locator"]), f""" + The new downstream data. + """) + + # Copy this modified html block + self._copy_course_block(downstream_html1["locator"]) + # Paste it + pasted_block = self._paste_course_block(str(self.course_subsection.usage_key)) + + # The pasted block will have same fields as the above block except the + # upstream_* fields will now have the original blocks base value, so + # pasted_block.upstream_display_name == downstream_html1.display_name + # pasted_block.upstream_data == downstream_html1.data + # while still have `downstream_customized` same to avoid overriding during sync + # to allow authors to revert back to back to the original copied customized value + + # See `upstream_data` below is same as how downstream_html1.data is set. + self.assertXmlEqual(self._get_course_block_olx(pasted_block["locator"]), f""" + The new downstream data. + """) + def test_unit_decline_sync(self): """ Test that we can decline sync a unit from the library into the course @@ -844,9 +1187,9 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): parent_usage_key=str(self.course_subsection.usage_key), upstream_key=self.upstream_unit["id"], ) - downstream_unit_block_key = get_block_key_dict( + downstream_unit_block_key = serialize_field(get_block_key_dict( UsageKey.from_string(downstream_unit["locator"]), - ) + )).replace('"', '"') children_downstream_keys = self._get_course_block_children(downstream_unit["locator"]) downstream_problem1 = children_downstream_keys[1] assert "type@problem" in downstream_problem1 @@ -882,6 +1225,7 @@ class CourseToLibraryTestCase(ContentLibrariesRestApiTest, ModuleStoreTestCase): upstream="{self.upstream_html1['id']}" upstream_version="2" top_level_downstream_parent_key="{downstream_unit_block_key}" + upstream_data="This is the HTML." >This is the HTML. This is the HTML. Hello world!") + self._publish_library_block(self.html_lib_id_2) + + response = self.client.get(f"/api/contentstore/v2/downstreams/{self.top_level_downstream_unit.usage_key}") + assert response.status_code == 200 + data = response.json() + assert data['upstream_ref'] == self.top_level_unit_id + assert data['error_message'] is None + assert data['ready_to_sync'] is True + assert len(data['ready_to_sync_children']) == 1 + html_block = modulestore().get_item(self.top_level_downstream_html_key) + self.assertDictEqual(data['ready_to_sync_children'][0], { + 'name': html_block.display_name, + 'upstream': str(self.html_lib_id_2), + 'id': str(html_block.usage_key), + }) + def test_200_all_downstreams_for_a_course(self): """ Returns all links for given course diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index c98e3d7641..4f8312fdb3 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -292,6 +292,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): """ Wraps the results of rendering an XBlock view in a div which adds a header and Studio action buttons. """ + # Allow some imported components to be edited by authors in course. + editable_library_components = ["html"] # Only add the Studio wrapper when on the container page. The "Pages" page will remain as is for now. if not context.get('is_pages_view', None) and view in PREVIEW_VIEWS: root_xblock = context.get('root_xblock') @@ -304,13 +306,22 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): can_edit = context.get('can_edit', True) can_add = context.get('can_add', True) - upstream_link = UpstreamLink.try_get_for_block(root_xblock, log_error=False) - if upstream_link.error_message is None and isinstance(upstream_link.upstream_key, LibraryContainerLocator): + can_move = context.get('can_move', True) + root_upstream_link = UpstreamLink.try_get_for_block(root_xblock, log_error=False) + upstream_link = UpstreamLink.try_get_for_block(xblock, log_error=False) + if ( + root_upstream_link.error_message is None + and isinstance(root_upstream_link.upstream_key, LibraryContainerLocator) + ): # If this unit is linked to a library unit, for now we make it completely read-only # because when it is synced, all local changes like added components will be lost. # (This is only on the frontend; the backend doesn't enforce it) can_edit = False can_add = False + can_move = False + + if upstream_link.error_message is None and upstream_link.upstream_ref: + can_edit = xblock.category in editable_library_components # Is this a course or a library? is_course = xblock.context_key.is_course @@ -336,10 +347,11 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): # Generally speaking, "if you can add, you can delete". One exception is itembank (Problem Bank) # which has its own separate "add" workflow but uses the normal delete workflow for its child blocks. 'can_delete': can_add or (root_xblock and root_xblock.scope_ids.block_type == "itembank" and can_edit), - 'can_move': context.get('can_move', is_course), + 'can_move': can_move, 'language': getattr(course, 'language', None), 'is_course': is_course, 'tags_count': tags_count, + 'can_edit_title': True, # This is always true even for imported components } add_webpack_js_to_fragment(frag, "js/factories/xblock_validation") diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py index c0ab6e0e59..ae7492ddbc 100644 --- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py +++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py @@ -301,14 +301,11 @@ def modify_xblock(usage_key, request): ) -def _update_with_callback(xblock, user, old_metadata=None, old_content=None): +def save_xblock_with_callback(xblock, user, old_metadata=None, old_content=None): """ Updates the xblock in the modulestore. But before doing so, it calls the xblock's editor_saved callback function, and after doing so, it calls the xblock's post_editor_saved callback function. - - TODO: Remove getattrs from this function. - See https://github.com/openedx/edx-platform/issues/33715 """ if old_metadata is None: old_metadata = own_metadata(xblock) @@ -377,7 +374,7 @@ def _save_xblock( if old_parent_location: old_parent = store.get_item(old_parent_location) old_parent.children.remove(new_child) - old_parent = _update_with_callback(old_parent, user) + old_parent = save_xblock_with_callback(old_parent, user) else: # the Studio UI currently doesn't present orphaned children, so assume this is an error return JsonResponse( @@ -447,7 +444,7 @@ def _save_xblock( validate_and_update_xblock_due_date(xblock) # update the xblock and call any xblock callbacks - xblock = _update_with_callback(xblock, user, old_metadata, old_content) + xblock = save_xblock_with_callback(xblock, user, old_metadata, old_content) # for static tabs, their containing course also records their display name course = store.get_course(xblock.location.course_key) @@ -533,7 +530,7 @@ def sync_library_content( downstream: XBlock, request, store, - top_level_parent: XBlock | None = None + top_level_parent: XBlock | None = None, ) -> StaticFileNotices: """ Handle syncing library content for given xblock depending on its upstream type. @@ -541,9 +538,21 @@ def sync_library_content( """ link = UpstreamLink.get_for_block(downstream) upstream_key = link.upstream_key + request_data = getattr(request, "json", getattr(request, "data", {})) + override_customizations = request_data.get("override_customizations", False) + keep_custom_fields = request_data.get("keep_custom_fields", []) if isinstance(upstream_key, LibraryUsageLocatorV2): - lib_block = sync_from_upstream_block(downstream=downstream, user=request.user) - static_file_notices = import_static_assets_for_library_sync(downstream, lib_block, request) + lib_block = sync_from_upstream_block( + downstream=downstream, + user=request.user, + top_level_parent=top_level_parent, + override_customizations=override_customizations, + keep_custom_fields=keep_custom_fields, + ) + if lib_block: + static_file_notices = import_static_assets_for_library_sync(downstream, lib_block, request) + else: + static_file_notices = StaticFileNotices() store.update_item(downstream, request.user.id) else: with store.bulk_operations(downstream.usage_key.context_key): @@ -1678,7 +1687,7 @@ def _get_release_date(xblock, user=None): if reset_to_default and user: xblock.start = DEFAULT_START_DATE - xblock = _update_with_callback(xblock, user) + xblock = save_xblock_with_callback(xblock, user) # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set return ( diff --git a/cms/envs/common.py b/cms/envs/common.py index 13811d2a13..00a2ba63b1 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -844,8 +844,8 @@ XBLOCK_MIXINS = ( ResourceTemplates, XModuleMixin, EditInfoMixin, + UpstreamSyncMixin, # Should be above AuthoringMixin for UpstreamSyncMixin.editor_saved to take effect AuthoringMixin, - UpstreamSyncMixin, ) # .. setting_name: XBLOCK_EXTRA_MIXINS diff --git a/cms/lib/xblock/test/test_upstream_sync.py b/cms/lib/xblock/test/test_upstream_sync.py index 8902d67c8c..c65c0fcb20 100644 --- a/cms/lib/xblock/test/test_upstream_sync.py +++ b/cms/lib/xblock/test/test_upstream_sync.py @@ -17,6 +17,7 @@ from cms.lib.xblock.upstream_sync import ( sever_upstream_link, ) from cms.lib.xblock.upstream_sync_block import sync_from_upstream_block, fetch_customizable_fields_from_block +from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import save_xblock_with_callback from common.djangoapps.student.tests.factories import UserFactory from openedx.core.djangoapps.content_libraries import api as libs from openedx.core.djangoapps.content_tagging import api as tagging_api @@ -305,7 +306,7 @@ class UpstreamTestCase(ModuleStoreTestCase): # Modifying synchronized fields are "unsafe" customizations downstream.rerandomize = '"onreset"' downstream.matlab_api_key = 'hij' - downstream.save() + save_xblock_with_callback(downstream, self.user) # Follow-up sync. sync_from_upstream_block(downstream, self.user) @@ -349,73 +350,96 @@ class UpstreamTestCase(ModuleStoreTestCase): upstream.save() libs.publish_changes(self.library.key, self.user.id) - # Downstream modifications - downstream.display_name = "Downstream Title Override" # "safe" customization - downstream.data = "Downstream content override" # "unsafe" override - downstream.save() + # Downstream modifications, currently all fields are overridden on individual sync + downstream.display_name = "Downstream Title Override" + downstream.data = "Downstream content override" + save_xblock_with_callback(downstream, self.user) # Follow-up sync. Assert that updates are pulled into downstream, but customizations are saved. sync_from_upstream_block(downstream, self.user) - assert downstream.upstream_display_name == "Upstream Title V3" assert downstream.display_name == "Downstream Title Override" # "safe" customization survives - assert downstream.data == "Upstream content V3" # "unsafe" override is gone + assert downstream.data == "Downstream content override" # "safe" customization survives + # Verify hidden field has latest upstream value + assert downstream.upstream_data == "Upstream content V3" + assert downstream.upstream_display_name == "Upstream Title V3" - # For the Content Libraries Relaunch Beta, we do not yet need to support this edge case. - # See "PRESERVING DOWNSTREAM CUSTOMIZATIONS and RESTORING UPSTREAM DEFAULTS" in cms/lib/xblock/upstream_sync.py. - # - # def test_sync_to_downstream_with_subtle_customization(self): - # """ - # Edge case: If our downstream customizes a field, but then the upstream is changed to match the - # customization do we still remember that the downstream field is customized? That is, - # if the upstream later changes again, do we retain the downstream customization (rather than - # following the upstream update?) - # """ - # # Start with an uncustomized downstream block. - # downstream = BlockFactory.create(category='html', parent=self.unit, upstream=str(self.upstream_key)) - # sync_from_upstream_block(downstream, self.user) - # assert downstream.downstream_customized == [] - # assert downstream.display_name == downstream.upstream_display_name == "Upstream Title V2" - # - # # Then, customize our downstream title. - # downstream.display_name = "Title V3" - # downstream.save() - # assert downstream.downstream_customized == ["display_name"] - # - # # Syncing should retain the customization. - # sync_from_upstream_block(downstream, self.user) - # assert downstream.upstream_version == 2 - # assert downstream.upstream_display_name == "Upstream Title V2" - # assert downstream.display_name == "Title V3" - # - # # Whoa, look at that, the upstream has updated itself to the exact same title... - # upstream = xblock.load_block(self.upstream_key, self.user) - # upstream.display_name = "Title V3" - # upstream.save() - # - # # ...which is reflected when we sync. - # sync_from_upstream_block(downstream, self.user) - # assert downstream.upstream_version == 3 - # assert downstream.upstream_display_name == downstream.display_name == "Title V3" - # - # # But! Our downstream knows that its title is still customized. - # assert downstream.downstream_customized == ["display_name"] - # # So, if the upstream title changes again... - # upstream.display_name = "Title V4" - # upstream.save() - # - # # ...then the downstream title should remain put. - # sync_from_upstream_block(downstream, self.user) - # assert downstream.upstream_version == 4 - # assert downstream.upstream_display_name == "Title V4" - # assert downstream.display_name == "Title V3" - # - # # Finally, if we "de-customize" the display_name field, then it should go back to syncing normally. - # downstream.downstream_customized = [] - # upstream.display_name = "Title V5" - # upstream.save() - # sync_from_upstream_block(downstream, self.user) - # assert downstream.upstream_version == 5 - # assert downstream.upstream_display_name == downstream.display_name == "Title V5" + def test_sync_to_downstream_with_subtle_customization(self): + """ + Edge case: If our downstream customizes a field, but then the upstream is changed to match the + customization do we still remember that the downstream field is customized? That is, + if the upstream later changes again, do we retain the downstream customization (rather than + following the upstream update?) + """ + # Start with an uncustomized downstream block. + downstream = BlockFactory.create(category='html', parent=self.unit, upstream=str(self.upstream_key)) + sync_from_upstream_block(downstream, self.user) + assert downstream.downstream_customized == [] + assert downstream.display_name == downstream.upstream_display_name == "Upstream Title V2" + + # Then, customize our downstream title. + downstream.display_name = "Title V3" + save_xblock_with_callback(downstream, self.user) + assert downstream.downstream_customized == ["display_name"] + + # Syncing should retain the customization if we allow display name customization. + sync_from_upstream_block( + downstream, + self.user, + override_customizations=True, + keep_custom_fields=["display_name"] + ) + assert downstream.upstream_version == 2 + assert downstream.upstream_display_name == "Upstream Title V2" + assert downstream.display_name == "Title V3" + + # Whoa, look at that, the upstream has updated itself to the exact same title... + upstream = xblock.load_block(self.upstream_key, self.user) + upstream.display_name = "Title V3" + upstream.save() + libs.publish_changes(self.library.key, self.user.id) + + # ...which is reflected when we sync. + sync_from_upstream_block( + downstream, + self.user, + override_customizations=True, + keep_custom_fields=["display_name"] + ) + assert downstream.upstream_version == 3 + assert downstream.upstream_display_name == downstream.display_name == "Title V3" + + # But! Our downstream knows that its title is still customized. + assert downstream.downstream_customized == ["display_name"] + # So, if the upstream title changes again... + upstream.display_name = "Title V4" + upstream.save() + libs.publish_changes(self.library.key, self.user.id) + + # ...then the downstream title should remain put. + sync_from_upstream_block( + downstream, + self.user, + override_customizations=True, + keep_custom_fields=["display_name"] + ) + assert downstream.upstream_version == 4 + assert downstream.upstream_display_name == "Title V4" + assert downstream.display_name == "Title V3" + + # Finally, if we don't allow keeping any customizations + upstream.display_name = "Title V5" + upstream.save() + libs.publish_changes(self.library.key, self.user.id) + sync_from_upstream_block( + downstream, + self.user, + override_customizations=True, + keep_custom_fields=[] + ) # No customizations! + assert downstream.upstream_version == 5 + assert downstream.upstream_display_name == downstream.display_name == "Title V5" + # Clears downstream_customized field as well + assert downstream.downstream_customized == [] @ddt.data(None, "Title From Some Other Upstream Version") def test_update_customizable_fields(self, initial_upstream_display_name): @@ -587,3 +611,33 @@ class UpstreamTestCase(ModuleStoreTestCase): # `edx_video_id` doesn't change assert downstream.edx_video_id == "test_video_id" + + def test_sync_keep_customizaton_option(self): + """ + Test that when an upstream block has a customized downstream block, we keep + the customized options when syncing based on keep_custom_fields option. + """ + # Start with an uncustomized downstream block. + downstream = BlockFactory.create(category='html', parent=self.unit, upstream=str(self.upstream_key)) + sync_from_upstream_block(downstream, self.user) + assert downstream.downstream_customized == [] + assert downstream.display_name == downstream.upstream_display_name == "Upstream Title V2" + + # Then, customize our downstream title and content + downstream.display_name = "Title V3" + downstream.data = "Some content" + save_xblock_with_callback(downstream, self.user) + assert downstream.downstream_customized == ["display_name", "data"] + + # Now, sync the upstream block with `keep_custom_fields=["display_name"] only`. + # And let data be overridden + sync_from_upstream_block( + downstream, + self.user, + override_customizations=True, + keep_custom_fields=['display_name'] + ) + assert downstream.display_name == "Title V3" + # data is overridden + assert downstream.data == "Upstream content V2" + assert downstream.downstream_customized == ["display_name"] diff --git a/cms/lib/xblock/upstream_sync.py b/cms/lib/xblock/upstream_sync.py index 1b78b6b366..5448a9b1cf 100644 --- a/cms/lib/xblock/upstream_sync.py +++ b/cms/lib/xblock/upstream_sync.py @@ -26,7 +26,7 @@ from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import LibraryContainerLocator, LibraryUsageLocatorV2 from opaque_keys.edx.keys import UsageKey from xblock.exceptions import XBlockNotFoundError -from xblock.fields import Scope, String, Integer, Dict +from xblock.fields import Scope, String, Integer, Dict, List from xblock.core import XBlockMixin, XBlock from xmodule.util.keys import BlockKey @@ -84,10 +84,11 @@ class UpstreamLink: version_available: int | None # Latest version of the upstream that's available, or None if it couldn't be loaded. version_declined: int | None # Latest version which the user has declined to sync with, if any. error_message: str | None # If link is valid, None. Otherwise, a localized, human-friendly error message. + is_modified: bool | None # If modified in course, True. Otherwise, False. has_top_level_parent: bool # True if this Upstream link has a top-level parent @property - def _is_ready_to_sync_individually(self) -> bool: + def is_ready_to_sync_individually(self) -> bool: return bool( self.upstream_ref and self.version_available and @@ -95,6 +96,44 @@ class UpstreamLink: self.version_available > (self.version_declined or 0) ) + def _check_children_ready_to_sync(self, xblock_downstream: XBlock, return_fast: bool) -> list[dict[str, str]]: + """ + Check if all the children of the current XBlock are ready to be synced individually. + + Args: + xblock_downstream (XBlock): The XBlock mixin instance whose children need to be checked. + return_fast (bool): If True, return the first child that is ready to sync. + + Returns: + list[dict]: A list of children id and names that ready to sync. + """ + if not xblock_downstream.has_children: + return [] + + downstream_children = xblock_downstream.get_children() + child_info = [] + + for child in downstream_children: + if child.upstream: + child_upstream_link = UpstreamLink.try_get_for_block(child) + # If one child needs sync, it is not needed to check more children + if child_upstream_link.is_ready_to_sync_individually: + child_info.append({ + 'name': child.display_name, + 'upstream': getattr(child, 'upstream', None), + 'id': str(child.usage_key), + }) + if return_fast: + return child_info + + grand_children_info = self._check_children_ready_to_sync(child, return_fast) + child_info.extend(grand_children_info) + if return_fast and len(grand_children_info) > 0: + # If one child needs sync, it is not needed to check more children + return child_info + + return child_info + @property def ready_to_sync(self) -> bool: """ @@ -108,45 +147,16 @@ class UpstreamLink: return False if isinstance(self.upstream_key, LibraryUsageLocatorV2): - return self._is_ready_to_sync_individually + return self.is_ready_to_sync_individually elif isinstance(self.upstream_key, LibraryContainerLocator): # The container itself has changes to update, it is not necessary to review its children - if self._is_ready_to_sync_individually: - return True - - def check_children_ready_to_sync(xblock_downstream): - """ - Checks if one of the children of `xblock_downstream` is ready to sync - """ - if not xblock_downstream.has_children: - return False - - downstream_children = xblock_downstream.get_children() - - for child in downstream_children: - if child.upstream: - child_upstream_link = UpstreamLink.try_get_for_block(child) - - child_ready_to_sync = bool( - child_upstream_link.upstream_ref and - child_upstream_link.version_available and - child_upstream_link.version_available > (child_upstream_link.version_synced or 0) and - child_upstream_link.version_available > (child_upstream_link.version_declined or 0) - ) - - # If one child needs sync, it is not needed to check more children - if child_ready_to_sync: - return True - - if check_children_ready_to_sync(child): - # If one child needs sync, it is not needed to check more children - return True - - return False - if self.downstream_key is not None: - return check_children_ready_to_sync( - modulestore().get_item(UsageKey.from_string(self.downstream_key)) - ) + return self.is_ready_to_sync_individually or ( + self.downstream_key is not None + and len(self._check_children_ready_to_sync( + modulestore().get_item(UsageKey.from_string(self.downstream_key)), + return_fast=True, + )) > 0 + ) return False @property @@ -162,7 +172,7 @@ class UpstreamLink: return _get_library_container_url(self.upstream_key) return None - def to_json(self) -> dict[str, t.Any]: + def to_json(self, include_child_info=False) -> dict[str, t.Any]: """ Get an JSON-API-friendly representation of this upstream link. """ @@ -171,6 +181,18 @@ class UpstreamLink: "ready_to_sync": self.ready_to_sync, "upstream_link": self.upstream_link, } + if ( + include_child_info + and self.ready_to_sync + and isinstance(self.upstream_key, LibraryContainerLocator) + and self.downstream_key is not None + ): + from xmodule.modulestore.django import modulestore + + data["ready_to_sync_children"] = self._check_children_ready_to_sync( + modulestore().get_item(UsageKey.from_string(self.downstream_key)), + return_fast=False, + ) del data["upstream_key"] # As JSON (string), this would be redundant with upstream_ref return data @@ -198,6 +220,7 @@ class UpstreamLink: version_available=None, version_declined=None, error_message=str(exc), + is_modified=len(getattr(downstream, "downstream_customized", [])) > 0, has_top_level_parent=getattr(downstream, "top_level_downstream_parent_key", None) is not None, ) @@ -280,6 +303,7 @@ class UpstreamLink: version_available=version_available, version_declined=downstream.upstream_version_declined, error_message=None, + is_modified=len(getattr(downstream, "downstream_customized", [])) > 0, has_top_level_parent=downstream.top_level_downstream_parent_key is not None, ) @@ -453,6 +477,27 @@ class UpstreamSyncMixin(XBlockMixin): default=None, scope=Scope.settings, hidden=True, enforce_type=True, ) + # PRESERVING DOWNSTREAM CUSTOMIZATIONS and RESTORING UPSTREAM VALUES + # + # For the full Content Libraries Relaunch, we would like to keep track of which customizable fields the user has + # actually customized. The idea is: once an author has customized a customizable field.... + # + # - future upstream syncs will NOT blow away the customization, + # - but future upstream syncs WILL fetch the upstream values and tuck them away in a hidden field, + # - and the author can can revert back to said fetched upstream value at any point. + # + # Now, whether field is "customized" (and thus "revertible") is dependent on whether they have ever edited it. + # To instrument this, we need to keep track of which customizable fields have been edited using a new XBlock field: + # `downstream_customized` + downstream_customized = List( + help=( + "Names of the fields which have values set on the upstream block yet have been explicitly " + "overridden on this downstream block. Unless explicitly cleared by the user, these customizations " + "will persist even when updates are synced from the upstream." + ), + default=[], scope=Scope.settings, hidden=True, enforce_type=True, + ) + @classmethod def get_customizable_fields(cls) -> dict[str, str | None]: """ @@ -478,66 +523,32 @@ class UpstreamSyncMixin(XBlockMixin): "weight": None, } - # PRESERVING DOWNSTREAM CUSTOMIZATIONS and RESTORING UPSTREAM VALUES - # - # For the full Content Libraries Relaunch, we would like to keep track of which customizable fields the user has - # actually customized. The idea is: once an author has customized a customizable field.... - # - # - future upstream syncs will NOT blow away the customization, - # - but future upstream syncs WILL fetch the upstream values and tuck them away in a hidden field, - # - and the author can can revert back to said fetched upstream value at any point. - # - # Now, whether field is "customized" (and thus "revertible") is dependent on whether they have ever edited it. - # To instrument this, we need to keep track of which customizable fields have been edited using a new XBlock field: - # `downstream_customized` - # - # Implementing `downstream_customized` has proven difficult, because there is no simple way to keep it up-to-date - # with the many different ways XBlock fields can change. The `.save()` and `.editor_saved()` methods are promising, - # but we need to do more due diligence to be sure that they cover all cases, including API edits, import/export, - # copy/paste, etc. We will figure this out in time for the full Content Libraries Relaunch (related ticket: - # https://github.com/openedx/frontend-app-authoring/issues/1317). But, for the Beta realease, we're going to - # implement something simpler: - # - # - We fetch upstream values for customizable fields and tuck them away in a hidden field (same as above). - # - If a customizable field DOES match the fetched upstream value, then future upstream syncs DO update it. - # - If a customizable field does NOT the fetched upstream value, then future upstream syncs DO NOT update it. - # - There is no UI option for explicitly reverting back to the fetched upstream value. - # - # For future reference, here is a partial implementation of what we are thinking for the full Content Libraries - # Relaunch:: - # - # downstream_customized = List( - # help=( - # "Names of the fields which have values set on the upstream block yet have been explicitly " - # "overridden on this downstream block. Unless explicitly cleared by the user, these customizations " - # "will persist even when updates are synced from the upstream." - # ), - # default=[], scope=Scope.settings, hidden=True, enforce_type=True, - # ) - # - # def save(self, *args, **kwargs): - # """ - # Update `downstream_customized` when a customizable field is modified. - # - # NOTE: This does not work, because save() isn't actually called in all the cases that we'd want it to be. - # """ - # super().save(*args, **kwargs) - # customizable_fields = self.get_customizable_fields() - # - # # Loop through all the fields that are potentially cutomizable. - # for field_name, restore_field_name in self.get_customizable_fields(): - # - # # If the field is already marked as customized, then move on so that we don't - # # unneccessarily query the block for its current value. - # if field_name in self.downstream_customized: - # continue - # - # # If there is no restore_field name, it's a downstream-only field - # if restore_field_name is None: - # continue - # - # # If this field's value doesn't match the synced upstream value, then mark the field - # # as customized so that we don't clobber it later when syncing. - # # NOTE: Need to consider the performance impact of all these field lookups. - # if getattr(self, field_name) != getattr(self, restore_field_name): - # self.downstream_customized.append(field_name) + def editor_saved(self, user, old_metadata, old_content): + """ + Update `downstream_customized` when a customizable field is modified. + """ + super().editor_saved(user, old_metadata, old_content) + customizable_fields = self.get_customizable_fields() + new_data = ( + self.get_explicitly_set_fields_by_scope(Scope.settings) + | self.get_explicitly_set_fields_by_scope(Scope.content) + ) + old_data = old_metadata | old_content + + # Loop through all the fields that are potentially cutomizable. + for field_name, restore_field_name in customizable_fields.items(): + + # If the field is already marked as customized, then move on so that we don't + # unneccessarily query the block for its current value. + if field_name in self.downstream_customized: + continue + + # If there is no restore_field name, it's a downstream-only field + if restore_field_name is None: + continue + + # If this field's value doesn't match the synced upstream value, then mark the field + # as customized so that we don't clobber it later when syncing. + # NOTE: Need to consider the performance impact of all these field lookups. + if new_data.get(field_name) != old_data.get(restore_field_name): + self.downstream_customized.append(field_name) diff --git a/cms/lib/xblock/upstream_sync_block.py b/cms/lib/xblock/upstream_sync_block.py index 12c0095fe3..4ac7f11bd1 100644 --- a/cms/lib/xblock/upstream_sync_block.py +++ b/cms/lib/xblock/upstream_sync_block.py @@ -6,22 +6,33 @@ upstream is a container, not an XBlock. """ from __future__ import annotations +import logging import typing as t from django.core.exceptions import PermissionDenied from django.utils.translation import gettext_lazy as _ -from rest_framework.exceptions import NotFound from opaque_keys.edx.locator import LibraryUsageLocatorV2 -from xblock.fields import Scope +from rest_framework.exceptions import NotFound from xblock.core import XBlock +from xblock.fields import Scope -from .upstream_sync import UpstreamLink, BadUpstream +from .upstream_sync import BadDownstream, BadUpstream, UpstreamLink if t.TYPE_CHECKING: from django.contrib.auth.models import User # pylint: disable=imported-auth-user -def sync_from_upstream_block(downstream: XBlock, user: User) -> XBlock: +logger = logging.getLogger(__name__) + + +def sync_from_upstream_block( + downstream: XBlock, + user: User, + *, + top_level_parent: XBlock | None = None, + override_customizations: bool = False, + keep_custom_fields: list[str] | None = None, +) -> XBlock | None: """ Update `downstream` with content+settings from the latest available version of its linked upstream content. @@ -36,9 +47,16 @@ def sync_from_upstream_block(downstream: XBlock, user: User) -> XBlock: link = UpstreamLink.get_for_block(downstream) # can raise UpstreamLinkException if not isinstance(link.upstream_key, LibraryUsageLocatorV2): raise TypeError("sync_from_upstream_block() only supports XBlock upstreams, not containers") - # Upstream is a library block: upstream = _load_upstream_block(downstream, user) - _update_customizable_fields(upstream=upstream, downstream=downstream, only_fetch=False) + # Upstream is a library block: + # Sync all fields from the upstream block and override customizations + _update_customizable_fields( + upstream=upstream, + downstream=downstream, + only_fetch=False, + override_customizations=override_customizations, + keep_custom_fields=keep_custom_fields, + ) _update_non_customizable_fields(upstream=upstream, downstream=downstream) _update_tags(upstream=upstream, downstream=downstream) downstream.upstream_version = link.version_available @@ -57,6 +75,17 @@ def fetch_customizable_fields_from_block(*, downstream: XBlock, user: User, upst _update_customizable_fields(upstream=upstream, downstream=downstream, only_fetch=True) +def _verify_modification_to(downstream: XBlock, allowed_fields: list[str]): + """ + Raise error if any field except for fields in allowed_fields is modified in course locally. + """ + if len(downstream.downstream_customized) > len(allowed_fields): + raise BadDownstream("Too many fields modified, skip sync operation") + not_allowed_modified = set(downstream.downstream_customized).difference(allowed_fields) + if len(not_allowed_modified) > 0: + raise BadDownstream(f"{not_allowed_modified} fields are modified locally") + + def _load_upstream_block(downstream: XBlock, user: User) -> XBlock: """ Load the upstream metadata and content for a downstream block. @@ -80,12 +109,21 @@ def _load_upstream_block(downstream: XBlock, user: User) -> XBlock: return lib_block -def _update_customizable_fields(*, upstream: XBlock, downstream: XBlock, only_fetch: bool) -> None: +def _update_customizable_fields( + *, + upstream: XBlock, + downstream: XBlock, + only_fetch: bool, + override_customizations: bool = False, + keep_custom_fields: list[str] | None = None, +) -> None: """ For each customizable field: * Save the upstream value to a hidden field on the downstream ("FETCH"). - * If `not only_fetch`, and if the field *isn't* customized on the downstream, then: + * If `not only_fetch`, and if the field *isn't* customized on the downstream + or if override_customizations=True and keep_custom_fields does not contain the field name, then: * Update it the downstream field's value from the upstream field ("SYNC"). + * Remove the field from downstream.downstream_customized field if exists. Concrete example: Imagine `lib_problem` is our upstream and `course_problem` is our downstream. @@ -107,7 +145,6 @@ def _update_customizable_fields(*, upstream: XBlock, downstream: XBlock, only_fe continue # FETCH the upstream's value and save it on the downstream (ie, `downstream.upstream_$FIELD`). - old_upstream_value = getattr(downstream, fetch_field_name) new_upstream_value = getattr(upstream, field_name) setattr(downstream, fetch_field_name, new_upstream_value) @@ -116,19 +153,15 @@ def _update_customizable_fields(*, upstream: XBlock, downstream: XBlock, only_fe # Okay, now for the nuanced part... # We need to update the downstream field *iff it has not been customized**. - # Determining whether a field has been customized will differ in Beta vs Future release. - # (See "PRESERVING DOWNSTREAM CUSTOMIZATIONS" comment below for details.) - ## FUTURE BEHAVIOR: field is "customized" iff we have noticed that the user edited it. - # if field_name in downstream.downstream_customized: - # continue + if field_name in downstream.downstream_customized: + if not override_customizations or keep_custom_fields and field_name in keep_custom_fields: + continue + else: + # Remove the field from downstream_customized field as it can be overridden + downstream.downstream_customized.remove(field_name) - ## BETA BEHAVIOR: field is "customized" iff we have the prev upstream value, but field doesn't match it. - downstream_value = getattr(downstream, field_name) - if old_upstream_value and downstream_value != old_upstream_value: - continue # Field has been customized. Don't touch it. Move on. - - # Field isn't customized -- SYNC it! + # Field isn't customized or is can be overridden -- SYNC it! setattr(downstream, field_name, new_upstream_value) @@ -137,7 +170,10 @@ def _update_non_customizable_fields(*, upstream: XBlock, downstream: XBlock) -> For each field `downstream.blah` that isn't customizable: set it to `upstream.blah`. """ syncable_fields = _get_synchronizable_fields(upstream, downstream) - customizable_fields = set(downstream.get_customizable_fields().keys()) + # Remove both field_name and its upstream_* counterpart from the list of fields to copy + customizable_fields = set(downstream.get_customizable_fields().keys()) | set( + downstream.get_customizable_fields().values() + ) # TODO: resolve this so there's no special-case happening for video block. # e.g. by some non_cloneable_fields property of the XBlock class? is_video_block = downstream.usage_key.block_type == "video" diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index a7aabb1096..831f71747d 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -25,6 +25,7 @@ function($, _, Backbone, gettext, BasePage, events: { 'click .edit-button': 'editXBlock', + 'click .title-edit-button': 'clickTitleButton', 'click .access-button': 'editVisibilitySettings', 'click .duplicate-button': 'duplicateXBlock', 'click .copy-button': 'copyXBlock', @@ -1274,6 +1275,37 @@ function($, _, Backbone, gettext, BasePage, scrollToNewComponentButtons: function(event) { event.preventDefault(); $.scrollTo(this.$('.add-xblock-component'), {duration: 250}); + }, + + clickTitleButton: function(event) { + const xblockElement = this.findXBlockElement(event.target); + const xblockInfo = XBlockUtils.findXBlockInfo(xblockElement, this.model); + var self = this, + oldTitle = xblockInfo.get('display_name'), + titleElt = $(xblockElement).find('.xblock-display-name'), + buttonElt = $(xblockElement).find('.title-edit-button'), + $input = $(''), + changeFunc = function(evt) { + var newTitle = $(evt.target).val(); + if (oldTitle !== newTitle) { + xblockInfo.set('display_name', newTitle); + return XBlockUtils.updateXBlockField(xblockInfo, "display_name", newTitle).done(function() { + self.refreshXBlock(xblockElement, false); + }); + } else { + titleElt.html(newTitle); // xss-lint: disable=javascript-jquery-html + $(buttonElt).show(); + } + return true; + }; + event.preventDefault(); + + $input.val(oldTitle); + $input.change(changeFunc).blur(changeFunc); + titleElt.html($input); // xss-lint: disable=javascript-jquery-html + $input.focus().select(); + $(buttonElt).hide(); + return true; } }); diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index 4100310f40..b53210484a 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -567,11 +567,18 @@ body, background-color: #f8f7f6; } +input.xblock-inline-title-editor { + padding-top: 0px; + padding-bottom: 0px; + font-size: 18px; + height: 50px; + font-weight: 700; +} + .btn-default.action-edit.title-edit-button { @extend %button-styles; position: relative; - top: -7px; .fa-pencil { display: none; diff --git a/cms/templates/studio_xblock_wrapper.html b/cms/templates/studio_xblock_wrapper.html index 89b62cc17c..2c57872e10 100644 --- a/cms/templates/studio_xblock_wrapper.html +++ b/cms/templates/studio_xblock_wrapper.html @@ -120,10 +120,14 @@ can_unlink = upstream_info.upstream_ref and not upstream_info.has_top_level_pare ${_("Sourced from a library - but the upstream link is broken/invalid.")} % else: - ${_("Sourced from a library - but has been modified locally.")} + % else: ${_("Sourced from a library.")} + % endif % endif % endif ${label} @@ -135,88 +139,94 @@ can_unlink = upstream_info.upstream_ref and not upstream_info.has_top_level_pare
diff --git a/openedx/core/lib/xblock_serializer/block_serializer.py b/openedx/core/lib/xblock_serializer/block_serializer.py index 4fb7301ced..76611624cd 100644 --- a/openedx/core/lib/xblock_serializer/block_serializer.py +++ b/openedx/core/lib/xblock_serializer/block_serializer.py @@ -9,6 +9,7 @@ from lxml import etree from opaque_keys.edx.locator import LibraryLocatorV2 from openedx.core.djangoapps.content_tagging.api import get_all_object_tags, TagValuesByObjectIdDict +from xmodule.xml_block import serialize_field from .data import StaticFile from . import utils @@ -140,7 +141,7 @@ class XBlockSerializer: if "top_level_downstream_parent_key" in block.fields \ and block.fields["top_level_downstream_parent_key"].is_set_on(block): - olx_node.attrib["top_level_downstream_parent_key"] = str(block.top_level_downstream_parent_key) + olx_node.attrib["top_level_downstream_parent_key"] = serialize_field(block.top_level_downstream_parent_key) return olx_node @@ -166,9 +167,10 @@ class XBlockSerializer: if block.use_latex_compiler: olx_node.attrib["use_latex_compiler"] = "true" for field_name in block.fields: - if (field_name.startswith("upstream") or field_name == "top_level_downstream_parent_key") \ - and block.fields[field_name].is_set_on(block): - olx_node.attrib[field_name] = str(getattr(block, field_name)) + if ( + field_name.startswith(("upstream", "downstream")) or field_name == "top_level_downstream_parent_key" + ) and block.fields[field_name].is_set_on(block): + olx_node.attrib[field_name] = serialize_field(getattr(block, field_name)) # Escape any CDATA special chars escaped_block_data = block.data.replace("]]>", "]]>") diff --git a/xmodule/html_block.py b/xmodule/html_block.py index 7820201319..3067a7cf9b 100644 --- a/xmodule/html_block.py +++ b/xmodule/html_block.py @@ -61,6 +61,13 @@ class HtmlBlockMixin( # lint-amnesty, pylint: disable=abstract-method default=_("Text") ) data = String(help=_("Html contents to display for this block"), default="", scope=Scope.content) + upstream_data = String( + help=_("Upstream html contents to store upstream data field"), + default=None, + hidden=True, + enforce_type=True, + scope=Scope.content, + ) source_code = String( help=_("Source code for LaTeX documents. This feature is not well-supported."), scope=Scope.settings @@ -143,6 +150,13 @@ class HtmlBlockMixin( # lint-amnesty, pylint: disable=abstract-method shim_xmodule_js(fragment, 'HTMLEditingDescriptor') return fragment + @classmethod + def get_customizable_fields(cls) -> dict[str, str | None]: + return { + "display_name": "upstream_display_name", + "data": "upstream_data", + } + uses_xmodule_styles_setup = True mako_template = "widgets/html-edit.html" From 68d68203a2755482b2168365635af8accc1ae598 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Wed, 17 Sep 2025 17:34:11 +0530 Subject: [PATCH 07/27] chore: update @edx/brand dependency to new package @openedx/brand-openedx (#37244) --- package-lock.json | 11 ++++++----- package.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44fad66332..6bc1261774 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@babel/plugin-transform-object-assign": "^7.18.6", "@babel/preset-env": "^7.19.0", "@babel/preset-react": "7.26.3", - "@edx/brand-edx.org": "^2.0.7", + "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", "@edx/edx-bootstrap": "1.0.4", "@edx/edx-proctoring": "^4.18.1", "@edx/frontend-component-cookie-policy-banner": "2.2.0", @@ -2021,10 +2021,11 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@edx/brand-edx.org": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@edx/brand-edx.org/-/brand-edx.org-2.1.3.tgz", - "integrity": "sha512-1TwUwW7YVgvhh7aO1ql1poAosUCA0zd7/DNuqeSO0wXui0oCHL+WQW8b9tXS2tRJ5BMRKjG8t22fcOcKX3ljbg==" + "node_modules/@edx/brand": { + "name": "@openedx/brand-openedx", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@openedx/brand-openedx/-/brand-openedx-1.2.3.tgz", + "integrity": "sha512-Dn9CtpC8fovh++Xi4NF5NJoeR9yU2yXZnV9IujxIyGd/dn0Phq5t6dzJVfupwq09mpDnzJv7egA8Znz/3ljO+w==" }, "node_modules/@edx/edx-bootstrap": { "version": "1.0.4", diff --git a/package.json b/package.json index 2ca951d7db..b9eb483e9c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@babel/plugin-transform-object-assign": "^7.18.6", "@babel/preset-env": "^7.19.0", "@babel/preset-react": "7.26.3", - "@edx/brand-edx.org": "^2.0.7", + "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", "@edx/edx-bootstrap": "1.0.4", "@edx/edx-proctoring": "^4.18.1", "@edx/frontend-component-cookie-policy-banner": "2.2.0", From 1eb387b11b9e613324735f20d0c5944faec77dbf Mon Sep 17 00:00:00 2001 From: Krish Tyagi Date: Wed, 17 Sep 2025 18:32:55 +0530 Subject: [PATCH 08/27] feat: update saml management command (#37330) The SAML management command has been refactored from an auto-update tool to a comprehensive report-only audit system. The changes introduce a new --run-checks option that provides detailed reporting on SAML configuration issues without making any automatic changes. --- .../management/commands/saml.py | 185 ++++++++++++----- .../management/commands/tests/test_saml.py | 196 ++++++++++++------ .../signals/tests/test_handlers.py | 9 +- 3 files changed, 279 insertions(+), 111 deletions(-) diff --git a/common/djangoapps/third_party_auth/management/commands/saml.py b/common/djangoapps/third_party_auth/management/commands/saml.py index e3891cefa0..afe369c2ad 100644 --- a/common/djangoapps/third_party_auth/management/commands/saml.py +++ b/common/djangoapps/third_party_auth/management/commands/saml.py @@ -6,6 +6,7 @@ Management commands for third_party_auth import logging from django.core.management.base import BaseCommand, CommandError +from edx_django_utils.monitoring import set_custom_attribute from common.djangoapps.third_party_auth.tasks import fetch_saml_metadata from common.djangoapps.third_party_auth.models import SAMLProviderConfig, SAMLConfiguration @@ -18,34 +19,24 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('--pull', action='store_true', help="Pull updated metadata from external IDPs") parser.add_argument( - '--fix-references', + '--run-checks', action='store_true', - help="Fix SAMLProviderConfig references to use current SAMLConfiguration versions" - ) - parser.add_argument( - '--site-id', - type=int, - help='Only fix configurations for a specific site ID (to be used with --fix-references)' - ) - parser.add_argument( - '--dry-run', - action='store_true', - help='Show what would be changed, but do not make any changes.' + help="Run checks on SAMLProviderConfig configurations and report potential issues" ) def handle(self, *args, **options): should_pull_saml_metadata = options.get('pull', False) - should_fix_references = options.get('fix_references', False) - dry_run = options.get('dry_run', False) - - if not should_pull_saml_metadata and not should_fix_references: - raise CommandError("Command must be used with '--pull' or '--fix-references' option.") + should_run_checks = options.get('run_checks', False) if should_pull_saml_metadata: self._handle_pull_metadata() + return - if should_fix_references: - self._handle_fix_references(options, dry_run=dry_run) + if should_run_checks: + self._handle_run_checks() + return + + raise CommandError("Command must be used with '--pull' or '--run-checks' option.") def _handle_pull_metadata(self): """ @@ -76,45 +67,139 @@ class Command(BaseCommand): ) ) - def _handle_fix_references(self, options, dry_run=False): - """Handle the --fix-references option for fixing outdated SAML configuration references.""" - site_id = options.get('site_id') - updated_count = 0 - error_count = 0 + def _handle_run_checks(self): + """ + Handle the --run-checks option for checking SAMLProviderConfig configuration issues. + + This is a report-only command. It identifies potential configuration problems such as: + - Outdated SAMLConfiguration references (provider pointing to old config version) + - Site ID mismatches between SAMLProviderConfig and its SAMLConfiguration + - Slug mismatches (except 'default' slugs) # noqa: E501 + - SAMLProviderConfig objects with null SAMLConfiguration references (informational) + + Includes observability attributes for monitoring. + """ + # Set custom attributes for monitoring the check operation + # .. custom_attribute_name: saml_management_command.operation + # .. custom_attribute_description: Records current SAML operation ('run_checks'). + set_custom_attribute('saml_management_command.operation', 'run_checks') + + metrics = self._check_provider_configurations() + self._report_check_summary(metrics) + + def _check_provider_configurations(self): + """ + Check each provider configuration for potential issues. + Returns a dictionary of metrics about the found issues. + """ + outdated_count = 0 + site_mismatch_count = 0 + slug_mismatch_count = 0 + null_config_count = 0 + error_count = 0 + total_providers = 0 - # Filter by site if specified provider_configs = SAMLProviderConfig.objects.current_set() - if site_id: - provider_configs = provider_configs.filter(site_id=site_id) + + self.stdout.write(self.style.SUCCESS("SAML Configuration Check Report")) + self.stdout.write("=" * 50) + self.stdout.write("") for provider_config in provider_configs: - if provider_config.saml_configuration: - try: - current_config = SAMLConfiguration.current( - provider_config.site_id, - provider_config.saml_configuration.slug - ) + total_providers += 1 + provider_info = ( + f"Provider (id={provider_config.id}, name={provider_config.name}, " + f"slug={provider_config.slug}, site_id={provider_config.site_id})" + ) - if current_config and current_config.id != provider_config.saml_configuration_id: + if not provider_config.saml_configuration: + self.stdout.write( + f"[INFO] {provider_info} has no SAML configuration because " + "a matching default was not found." + ) + null_config_count += 1 + continue + + try: + current_config = SAMLConfiguration.current( + provider_config.saml_configuration.site_id, + provider_config.saml_configuration.slug + ) + + # Check for outdated configuration references + if current_config: + if current_config.id != provider_config.saml_configuration_id: self.stdout.write( - f"Provider '{provider_config.slug}' (site {provider_config.site_id}) " - f"has outdated config (ID: {provider_config.saml_configuration_id} -> {current_config.id})" + f"[WARNING] {provider_info} " + f"has outdated SAML config (id={provider_config.saml_configuration_id} which " + f"should be updated to the current SAML config (id={current_config.id})." ) + outdated_count += 1 - if not dry_run: - provider_config.saml_configuration = current_config - provider_config.save() - updated_count += 1 - - except Exception as e: # pylint: disable=broad-except - self.stderr.write( - f"Error processing provider '{provider_config.slug}': {e}" + if provider_config.saml_configuration.site_id != provider_config.site_id: + config_site_id = provider_config.saml_configuration.site_id + provider_site_id = provider_config.site_id + self.stdout.write( + f"[WARNING] {provider_info} " + f"SAML config (id={provider_config.saml_configuration_id}, site_id={config_site_id}) " + "does not match the provider's site_id." ) - error_count += 1 + site_mismatch_count += 1 - style = self.style.SUCCESS - if dry_run: - msg = f"[DRY RUN] Would update {updated_count} provider configurations. {error_count} errors encountered." + saml_configuration_slug = provider_config.saml_configuration.slug + provider_config_slug = provider_config.slug + + if saml_configuration_slug not in (provider_config_slug, 'default'): + self.stdout.write( + f"[WARNING] {provider_info} " + f"SAML config (id={provider_config.saml_configuration_id}, slug='{saml_configuration_slug}') " + "does not match the provider's slug." + ) + slug_mismatch_count += 1 + + except Exception as e: # pylint: disable=broad-except + self.stderr.write(f"[ERROR] Error processing {provider_info}: {e}") + error_count += 1 + + metrics = { + 'total_providers': {'count': total_providers, 'requires_attention': False}, + 'outdated_count': {'count': outdated_count, 'requires_attention': True}, + 'site_mismatch_count': {'count': site_mismatch_count, 'requires_attention': True}, + 'slug_mismatch_count': {'count': slug_mismatch_count, 'requires_attention': True}, + 'null_config_count': {'count': null_config_count, 'requires_attention': False}, + 'error_count': {'count': error_count, 'requires_attention': True}, + } + + for key, metric_data in metrics.items(): + # .. custom_attribute_name: saml_management_command.{key} + # .. custom_attribute_description: Records metrics from SAML configuration checks. + set_custom_attribute(f'saml_management_command.{key}', metric_data['count']) + + return metrics + + def _report_check_summary(self, metrics): + """ + Print a summary of the check results and set the total_requiring_attention custom attribute. + """ + total_requiring_attention = sum( + metric_data['count'] for metric_data in metrics.values() + if metric_data['requires_attention'] + ) + + # .. custom_attribute_name: saml_management_command.total_requiring_attention + # .. custom_attribute_description: The total number of configuration issues requiring attention. + set_custom_attribute('saml_management_command.total_requiring_attention', total_requiring_attention) + + self.stdout.write(self.style.SUCCESS("CHECK SUMMARY:")) + self.stdout.write(f" Providers checked: {metrics['total_providers']['count']}") + self.stdout.write(f" Null configs: {metrics['null_config_count']['count']}") + + if total_requiring_attention > 0: + self.stdout.write("\nIssues requiring attention:") + self.stdout.write(f" Outdated: {metrics['outdated_count']['count']}") + self.stdout.write(f" Site mismatches: {metrics['site_mismatch_count']['count']}") + self.stdout.write(f" Slug mismatches: {metrics['slug_mismatch_count']['count']}") + self.stdout.write(f" Errors: {metrics['error_count']['count']}") + self.stdout.write(f"\nTotal issues requiring attention: {total_requiring_attention}") else: - msg = f"Updated {updated_count} provider configurations. {error_count} errors encountered." - self.stdout.write(style(msg)) + self.stdout.write(self.style.SUCCESS("\nNo configuration issues found!")) diff --git a/common/djangoapps/third_party_auth/management/commands/tests/test_saml.py b/common/djangoapps/third_party_auth/management/commands/tests/test_saml.py index 168d88ae3b..6963d5dcd0 100644 --- a/common/djangoapps/third_party_auth/management/commands/tests/test_saml.py +++ b/common/djangoapps/third_party_auth/management/commands/tests/test_saml.py @@ -8,7 +8,7 @@ import os from io import StringIO from unittest import mock -from ddt import ddt, data, unpack +from ddt import ddt from django.contrib.sites.models import Site from django.core.management import call_command from django.core.management.base import CommandError @@ -18,8 +18,6 @@ from requests.models import Response from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms from common.djangoapps.third_party_auth.tests.factories import SAMLConfigurationFactory, SAMLProviderConfigFactory -from common.djangoapps.third_party_auth.models import SAMLProviderConfig - def mock_get(status_code=200): """ @@ -64,6 +62,7 @@ class TestSAMLCommand(CacheIsolationTestCase): self.stdout = StringIO() self.site = Site.objects.get_current() + self.other_site = Site.objects.create(domain='other.example.com', name='Other Site') # We are creating SAMLConfiguration instance here so that there is always at-least one # disabled saml configuration instance, this is done to verify that disabled configurations are @@ -82,9 +81,9 @@ class TestSAMLCommand(CacheIsolationTestCase): metadata_source='https://www.testshib.org/metadata/testshib-providers.xml', ) - def _setup_test_configs_for_fix_references(self): + def _setup_test_configs_for_run_checks(self): """ - Helper method to create SAML configurations for fix-references tests. + Helper method to create SAML configurations for run-checks tests. Returns tuple of (old_config, new_config, provider_config) @@ -108,7 +107,7 @@ class TestSAMLCommand(CacheIsolationTestCase): entity_id='https://updated.example.com' ) - # Create a provider config that references the old config for fix-references tests + # Create a provider config that references the old config for run-checks tests test_provider_config = SAMLProviderConfigFactory.create( site=self.site, slug='test-provider', @@ -148,14 +147,10 @@ class TestSAMLCommand(CacheIsolationTestCase): This test would fail with an error if ValueError is raised. """ - # Call `saml` command without any argument so that it raises a CommandError - with self.assertRaisesMessage(CommandError, "Command must be used with '--pull' or '--fix-references' option."): + # Call `saml` command without any arguments so that it raises a CommandError + with self.assertRaisesMessage(CommandError, "Command must be used with '--pull' or '--run-checks' option."): call_command("saml") - # Call `saml` command without any argument so that it raises a CommandError - with self.assertRaisesMessage(CommandError, "Command must be used with '--pull' or '--fix-references' option."): - call_command("saml", pull=False) - def test_no_saml_configuration(self): """ Test that management command completes without errors and logs correct information when no @@ -334,59 +329,144 @@ class TestSAMLCommand(CacheIsolationTestCase): call_command("saml", pull=True, stdout=self.stdout) assert expected in self.stdout.getvalue() - @data( - (True, '[DRY RUN]', 'should not update provider configs'), - (False, '', 'should create new provider config for new version') - ) - @unpack - def test_fix_references(self, dry_run, expected_output_marker, test_description): + def _run_checks_command(self): """ - Test the --fix-references command with and without --dry-run option. - - Args: - dry_run (bool): Whether to run with --dry-run flag - expected_output_marker (str): Expected marker in output - test_description (str): Description of what the test should do + Helper method to run the --run-checks command and return output. """ - old_config, new_config, test_provider_config = self._setup_test_configs_for_fix_references() - new_config_id = new_config.id - original_config_id = old_config.id - out = StringIO() - if dry_run: - call_command('saml', '--fix-references', '--dry-run', stdout=out) - else: - call_command('saml', '--fix-references', stdout=out) + call_command('saml', '--run-checks', stdout=out) + return out.getvalue() - output = out.getvalue() + @mock.patch('common.djangoapps.third_party_auth.management.commands.saml.set_custom_attribute') + def test_run_checks_outdated_configs(self, mock_set_custom_attribute): + """ + Test the --run-checks command identifies outdated configurations. + """ + old_config, new_config, test_provider_config = self._setup_test_configs_for_run_checks() + output = self._run_checks_command() + + self.assertIn('[WARNING]', output) self.assertIn('test-provider', output) - if expected_output_marker: - self.assertIn(expected_output_marker, output) + self.assertIn( + f'id={old_config.id} which should be updated to the current SAML config (id={new_config.id})', + output + ) + self.assertIn('CHECK SUMMARY:', output) + self.assertIn('Providers checked: 2', output) + self.assertIn('Outdated: 1', output) - test_provider_config.refresh_from_db() + # Check key observability calls + expected_calls = [ + mock.call('saml_management_command.operation', 'run_checks'), + mock.call('saml_management_command.total_providers', 2), + mock.call('saml_management_command.outdated_count', 1), + mock.call('saml_management_command.site_mismatch_count', 0), + mock.call('saml_management_command.slug_mismatch_count', 1), + mock.call('saml_management_command.null_config_count', 1), + mock.call('saml_management_command.error_count', 0), + mock.call('saml_management_command.total_requiring_attention', 2), + ] + mock_set_custom_attribute.assert_has_calls(expected_calls, any_order=False) - if dry_run: - # For dry run, ensure the provider config was NOT updated - self.assertEqual( - test_provider_config.saml_configuration_id, - original_config_id, - "Provider config should not be updated in dry run mode" - ) - else: - # For actual run, check that a new provider config was created - new_provider = SAMLProviderConfig.objects.filter( - site=self.site, - slug='test-provider', - saml_configuration_id=new_config_id - ).exclude(id=test_provider_config.id).first() + @mock.patch('common.djangoapps.third_party_auth.management.commands.saml.set_custom_attribute') + def test_run_checks_site_mismatches(self, mock_set_custom_attribute): + """ + Test the --run-checks command identifies site ID mismatches. + """ + config = SAMLConfigurationFactory.create( + site=self.other_site, + slug='test-config', + entity_id='https://example.com' + ) - self.assertIsNotNone(new_provider, "New provider config should be created") - self.assertEqual(new_provider.saml_configuration_id, new_config_id) + SAMLProviderConfigFactory.create( + site=self.site, + slug='test-provider', + saml_configuration=config + ) - # Original provider config should still reference the old config - self.assertEqual( - test_provider_config.saml_configuration_id, - original_config_id, - "Original provider config should still reference old config" - ) + output = self._run_checks_command() + + self.assertIn('[WARNING]', output) + self.assertIn('test-provider', output) + self.assertIn('does not match the provider\'s site_id', output) + + # Check observability calls + expected_calls = [ + mock.call('saml_management_command.operation', 'run_checks'), + mock.call('saml_management_command.total_providers', 2), + mock.call('saml_management_command.outdated_count', 0), + mock.call('saml_management_command.site_mismatch_count', 1), + mock.call('saml_management_command.slug_mismatch_count', 1), + mock.call('saml_management_command.null_config_count', 1), + mock.call('saml_management_command.error_count', 0), + mock.call('saml_management_command.total_requiring_attention', 2), + ] + mock_set_custom_attribute.assert_has_calls(expected_calls, any_order=False) + + @mock.patch('common.djangoapps.third_party_auth.management.commands.saml.set_custom_attribute') + def test_run_checks_slug_mismatches(self, mock_set_custom_attribute): + """ + Test the --run-checks command identifies slug mismatches. + """ + config = SAMLConfigurationFactory.create( + site=self.site, + slug='config-slug', + entity_id='https://example.com' + ) + + SAMLProviderConfigFactory.create( + site=self.site, + slug='provider-slug', + saml_configuration=config + ) + + output = self._run_checks_command() + + self.assertIn('[WARNING]', output) + self.assertIn('provider-slug', output) + self.assertIn('does not match the provider\'s slug', output) + + # Check observability calls + expected_calls = [ + mock.call('saml_management_command.operation', 'run_checks'), + mock.call('saml_management_command.total_providers', 2), + mock.call('saml_management_command.outdated_count', 0), + mock.call('saml_management_command.site_mismatch_count', 0), + mock.call('saml_management_command.slug_mismatch_count', 1), + mock.call('saml_management_command.null_config_count', 1), + mock.call('saml_management_command.error_count', 0), + mock.call('saml_management_command.total_requiring_attention', 1), + ] + mock_set_custom_attribute.assert_has_calls(expected_calls, any_order=False) + + @mock.patch('common.djangoapps.third_party_auth.management.commands.saml.set_custom_attribute') + def test_run_checks_null_configurations(self, mock_set_custom_attribute): + """ + Test the --run-checks command identifies providers with null configurations. + """ + SAMLProviderConfigFactory.create( + site=self.site, + slug='null-provider', + saml_configuration=None + ) + + output = self._run_checks_command() + + self.assertIn('[INFO]', output) + self.assertIn('null-provider', output) + self.assertIn('has no SAML configuration because a matching default was not found', output) + + # Check observability calls + expected_calls = [ + mock.call('saml_management_command.operation', 'run_checks'), + mock.call('saml_management_command.total_providers', 2), + mock.call('saml_management_command.outdated_count', 0), + mock.call('saml_management_command.site_mismatch_count', 0), + mock.call('saml_management_command.slug_mismatch_count', 0), + mock.call('saml_management_command.null_config_count', 2), + mock.call('saml_management_command.error_count', 0), + mock.call('saml_management_command.total_requiring_attention', 0), + ] + mock_set_custom_attribute.assert_has_calls(expected_calls, any_order=False) diff --git a/common/djangoapps/third_party_auth/signals/tests/test_handlers.py b/common/djangoapps/third_party_auth/signals/tests/test_handlers.py index 7875f0fcfa..1dd2fd6d7d 100644 --- a/common/djangoapps/third_party_auth/signals/tests/test_handlers.py +++ b/common/djangoapps/third_party_auth/signals/tests/test_handlers.py @@ -153,9 +153,12 @@ class TestSAMLConfigurationSignalHandlers(TestCase): current_provider = self._get_current_provider(provider_slug) - mock_set_custom_attribute.assert_any_call('saml_config_signal.enabled', True) - mock_set_custom_attribute.assert_any_call('saml_config_signal.new_config_id', new_saml_config.id) - mock_set_custom_attribute.assert_any_call('saml_config_signal.slug', signal_saml_slug) + expected_calls = [ + call('saml_config_signal.enabled', True), + call('saml_config_signal.new_config_id', new_saml_config.id), + call('saml_config_signal.slug', signal_saml_slug), + ] + mock_set_custom_attribute.assert_has_calls(expected_calls, any_order=False) if is_provider_updated: mock_set_custom_attribute.assert_any_call('saml_config_signal.updated_count', 1) From 6b48ff9470d69e601bad89f593064d8c279b3419 Mon Sep 17 00:00:00 2001 From: Kaustav Banerjee Date: Mon, 18 Aug 2025 18:14:00 +0530 Subject: [PATCH 09/27] feat: add ability to override middlewares for recurring nudges * feat: add ability to override middlewares for recurring nudges * feat: add ability to run command for all sites --- .../schedules/management/commands/__init__.py | 37 +++++++++++++++---- .../tests/test_send_email_base_command.py | 15 ++++++++ openedx/core/djangoapps/schedules/tasks.py | 18 ++++++--- openedx/core/lib/celery/task_utils.py | 2 +- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/openedx/core/djangoapps/schedules/management/commands/__init__.py b/openedx/core/djangoapps/schedules/management/commands/__init__.py index bd0082f533..0b72559765 100644 --- a/openedx/core/djangoapps/schedules/management/commands/__init__.py +++ b/openedx/core/djangoapps/schedules/management/commands/__init__.py @@ -29,12 +29,28 @@ class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand): # lint-amnes '--override-recipient-email', help='Send all emails to this address instead of the actual recipient' ) - parser.add_argument('site_domain_name') + parser.add_argument( + 'site_domain_name', + nargs='?', + default=None, + help=( + 'Domain name for the site to use. ' + 'Do not provide a domain if you wish to run this for all sites' + ) + ) parser.add_argument( '--weeks', type=int, help='Number of weekly emails to be sent', ) + parser.add_argument( + '--override-middlewares', + action='append', + help=( + 'Use this middleware when emulating http requests. ' + 'To use multiple middlewares, provide this argument multiple times' + ) + ) def handle(self, *args, **options): self.log_debug('Args = %r', options) @@ -49,19 +65,26 @@ class SendEmailBaseCommand(PrefixedDebugLoggerMixin, BaseCommand): # lint-amnes tzinfo=pytz.UTC ) self.log_debug('Current date = %s', current_date.isoformat()) - - site = Site.objects.get(domain__iexact=options['site_domain_name']) - self.log_debug('Running for site %s', site.domain) - override_recipient_email = options.get('override_recipient_email') - self.send_emails(site, current_date, override_recipient_email) + override_middlewares = options.get('override_middlewares') - def enqueue(self, day_offset, site, current_date, override_recipient_email=None): + site_domain_name = options['site_domain_name'] + sites = Site.objects.filter(domain__iexact=site_domain_name) if site_domain_name else Site.objects.all() + + if sites: + for site in sites: + self.log_debug('Running for site %s', site.domain) + self.send_emails(site, current_date, override_recipient_email, override_middlewares) + else: + self.log_info("No matching site found") + + def enqueue(self, day_offset, site, current_date, override_recipient_email=None, override_middlewares=None): self.async_send_task.enqueue( site, current_date, day_offset, override_recipient_email, + override_middlewares, ) def send_emails(self, *args, **kwargs): diff --git a/openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py b/openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py index 47ba67cc09..11b33d5851 100644 --- a/openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py +++ b/openedx/core/djangoapps/schedules/management/commands/tests/test_send_email_base_command.py @@ -10,6 +10,7 @@ from unittest.mock import DEFAULT, Mock, patch import ddt import pytz from django.conf import settings +from django.contrib.sites.models import Site from openedx.core.djangoapps.schedules.management.commands import SendEmailBaseCommand from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory @@ -33,9 +34,23 @@ class TestSendEmailBaseCommand(CacheIsolationTestCase): # lint-amnesty, pylint: send_emails.assert_called_once_with( self.site, datetime.datetime(2017, 9, 29, tzinfo=pytz.UTC), + None, None ) + def test_handle_all_sites(self): + with patch.object(self.command, 'send_emails') as send_emails: + self.command.handle(site_domain_name=None, date='2017-09-29') + expected_sites = Site.objects.all() + for expected_site in expected_sites: + send_emails.assert_any_call( + expected_site, + datetime.datetime(2017, 9, 29, tzinfo=pytz.UTC), + None, + None + ) + assert send_emails.call_count == len(expected_sites) + def test_weeks_option(self): with patch.object(self.command, 'enqueue') as enqueue: self.command.handle(site_domain_name=self.site.domain, date='2017-09-29', weeks=12) diff --git a/openedx/core/djangoapps/schedules/tasks.py b/openedx/core/djangoapps/schedules/tasks.py index 628276dc22..d4685a1e46 100644 --- a/openedx/core/djangoapps/schedules/tasks.py +++ b/openedx/core/djangoapps/schedules/tasks.py @@ -20,6 +20,7 @@ from edx_django_utils.monitoring import ( set_custom_attribute ) from eventtracking import tracker +from importlib import import_module from opaque_keys.edx.keys import CourseKey from openedx.core.djangoapps.content.course_overviews.models import CourseOverview @@ -103,7 +104,7 @@ class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask): task_instance = None @classmethod - def enqueue(cls, site, current_date, day_offset, override_recipient_email=None): # lint-amnesty, pylint: disable=missing-function-docstring + def enqueue(cls, site, current_date, day_offset, override_recipient_email=None, override_middlewares=None): # lint-amnesty, pylint: disable=missing-function-docstring set_code_owner_attribute_from_module(__name__) current_date = resolvers._get_datetime_beginning_of_day(current_date) # lint-amnesty, pylint: disable=protected-access @@ -120,6 +121,7 @@ class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask): day_offset, bin, override_recipient_email, + override_middlewares, ) cls.log_info('Launching task with args = %r', task_args) cls.task_instance.apply_async( @@ -128,16 +130,17 @@ class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask): ) def run( # lint-amnesty, pylint: disable=arguments-differ - self, site_id, target_day_str, day_offset, bin_num, override_recipient_email=None, + self, site_id, target_day_str, day_offset, bin_num, override_recipient_email=None, override_middlewares=None, ): set_code_owner_attribute_from_module(__name__) site = Site.objects.select_related('configuration').get(id=site_id) - with emulate_http_request(site=site): + middlewares = [self.class_from_classpath(cls) for cls in override_middlewares] if override_middlewares else None + with emulate_http_request(site=site, middleware_classes=middlewares) as request: msg_type = self.make_message_type(day_offset) - _annotate_for_monitoring(msg_type, site, bin_num, target_day_str, day_offset) + _annotate_for_monitoring(msg_type, request.site, bin_num, target_day_str, day_offset) return self.resolver( # lint-amnesty, pylint: disable=not-callable self.async_send_task, - site, + request.site, deserialize(target_day_str), day_offset, bin_num, @@ -147,6 +150,11 @@ class BinnedScheduleMessageBaseTask(ScheduleMessageBaseTask): def make_message_type(self, day_offset): raise NotImplementedError + def class_from_classpath(self, class_path): + module_name, klass = class_path.rsplit('.', 1) + module = import_module(module_name) + return getattr(module, klass) + @shared_task(base=LoggedTask, ignore_result=True) @set_code_owner_attribute diff --git a/openedx/core/lib/celery/task_utils.py b/openedx/core/lib/celery/task_utils.py index 9a54f1b3a5..10a3809fa6 100644 --- a/openedx/core/lib/celery/task_utils.py +++ b/openedx/core/lib/celery/task_utils.py @@ -45,7 +45,7 @@ def emulate_http_request(site=None, user=None, middleware_classes=None): _run_method_if_implemented(middleware, 'process_request', request) try: - yield + yield request except Exception as exc: for middleware in reversed(middleware_instances): _run_method_if_implemented(middleware, 'process_exception', request, exc) From 5972b40b2fd28745c56ef54adfeb1278140da30c Mon Sep 17 00:00:00 2001 From: ihor-romaniuk Date: Mon, 13 May 2024 08:49:30 +0200 Subject: [PATCH 10/27] fix: styles for share video functionality of the video xblock --- lms/templates/video.html | 47 ++++------ .../css-builtin-blocks/VideoBlockDisplay.css | 86 +++++++++++++++++++ 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/lms/templates/video.html b/lms/templates/video.html index ae984f3197..f2506e3613 100644 --- a/lms/templates/video.html +++ b/lms/templates/video.html @@ -66,31 +66,23 @@ from openedx.core.djangolib.js_utils import ( % endif % if sharing_sites_info: