Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9e299f7
temp: report FEATURES-as-dict warning at the real caller
feanil Jun 16, 2026
468f154
feat: bridge FEATURES proxy reads to @override_settings overrides
feanil Jun 17, 2026
a208145
refactor: migrate ENABLE_AUTHN_MICROFRONTEND off FEATURES-as-dict
feanil Jun 17, 2026
86d1e54
refactor: migrate ENABLE_CREATOR_GROUP off FEATURES-as-dict
feanil Jun 17, 2026
9f55e99
refactor: migrate CERTIFICATES_HTML_VIEW off FEATURES-as-dict
feanil Jun 17, 2026
2b7aec8
refactor: migrate ENABLE_PROCTORED_EXAMS off FEATURES-as-dict
feanil Jun 17, 2026
2f81b22
refactor: migrate ENABLE_COURSE_OLX_VALIDATION off FEATURES-as-dict
feanil Jun 17, 2026
0e222bb
refactor: migrate ENABLE_DISCUSSION_SERVICE off FEATURES-as-dict
feanil Jun 17, 2026
189a973
refactor: migrate ENABLE_SPECIAL_EXAMS off FEATURES-as-dict
feanil Jun 18, 2026
6561a02
refactor: migrate ENABLE_INTEGRITY_SIGNATURE off FEATURES-as-dict
feanil Jun 18, 2026
6349908
refactor: migrate CUSTOM_COURSES_EDX off FEATURES-as-dict
feanil Jun 18, 2026
d61cad2
refactor: migrate EMBARGO off FEATURES-as-dict
feanil Jun 18, 2026
f89578b
refactor: bind embargo.api.check_course_access at call time
feanil Jun 23, 2026
4a81cd1
refactor: migrate ENTRANCE_EXAMS off FEATURES-as-dict
feanil Jun 23, 2026
be40316
refactor: migrate STUDIO_REQUEST_EMAIL off FEATURES-as-dict
feanil Jun 23, 2026
9003fac
refactor: migrate ENABLE_UNICODE_USERNAME off FEATURES-as-dict
feanil Jun 23, 2026
1d0e975
refactor: migrate ENABLE_TEAMS off FEATURES-as-dict
feanil Jun 23, 2026
2e574ad
refactor: migrate DISABLE_COURSE_CREATION off FEATURES-as-dict
feanil Jun 23, 2026
7c53dda
refactor: migrate ALLOW_UNICODE_COURSE_ID off FEATURES-as-dict
feanil Jun 23, 2026
a7a08ce
refactor: migrate XQA_SERVER off FEATURES-as-dict
feanil Jun 23, 2026
645d56f
refactor: migrate LICENSING off FEATURES-as-dict
feanil Jun 23, 2026
f8647dd
refactor: migrate ENABLE_LMS_MIGRATION off FEATURES-as-dict
feanil Jun 23, 2026
d3919c5
refactor: migrate ENABLE_THIRD_PARTY_AUTH off FEATURES-as-dict
feanil Jun 23, 2026
6e11ae4
refactor: migrate ENABLE_LTI_PII_ACKNOWLEDGEMENT off FEATURES-as-dict
feanil Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions cms/djangoapps/cms_user_tasks/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import json
import logging
from unittest import mock
from unittest.mock import patch
from uuid import uuid4

import botocore
Expand Down Expand Up @@ -254,7 +253,7 @@ def test_email_sent_with_olx_validations_with_config_enabled(self):
*self.olx_validations['warnings']
]

with patch.dict(settings.FEATURES, ENABLE_COURSE_OLX_VALIDATION=True):
with override_settings(ENABLE_COURSE_OLX_VALIDATION=True):
user_task_stopped.send(sender=UserTaskStatus, status=self.status)
msg = mail.outbox[0]

Expand All @@ -277,12 +276,12 @@ def test_email_sent_with_olx_validations_with_default_config(self):
msg = mail.outbox[0]

# Verify olx validation is not enabled out of the box.
self.assertFalse(settings.FEATURES.get('ENABLE_COURSE_OLX_VALIDATION')) # noqa: PT009
self.assertFalse(getattr(settings, 'ENABLE_COURSE_OLX_VALIDATION', False)) # noqa: PT009
self.assertEqual(len(mail.outbox), 1) # noqa: PT009
self.assert_msg_subject(msg)
self.assert_msg_body_fragments(msg, body_fragments)

@patch.dict(settings.FEATURES, ENABLE_COURSE_OLX_VALIDATION=True)
@override_settings(ENABLE_COURSE_OLX_VALIDATION=True)
@override_waffle_flag(BYPASS_OLX_FAILURE, active=True)
def test_email_sent_with_olx_validations_with_bypass_flag(self):
"""
Expand Down
4 changes: 1 addition & 3 deletions cms/djangoapps/contentstore/api/tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import ddt
import factory
from django.conf import settings
from django.contrib.auth import get_user_model
from django.test.utils import override_settings
from django.urls import reverse
Expand Down Expand Up @@ -106,8 +105,7 @@ def test_student_fails(self):
)
@ddt.unpack
def test_staff_succeeds(self, certs_html_view, with_modes):
features = dict(settings.FEATURES, CERTIFICATES_HTML_VIEW=certs_html_view)
with override_settings(FEATURES=features):
with override_settings(CERTIFICATES_HTML_VIEW=certs_html_view):
if with_modes:
CourseModeFactory.create_batch(
2,
Expand Down
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/exams.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def register_exams(course_key):
Likewise, if formerly registered exams are not included in the payload they will
be marked inactive by the exam service.
"""
if not settings.FEATURES.get('ENABLE_SPECIAL_EXAMS') or not exams_ida_enabled(course_key):
if not getattr(settings, 'ENABLE_SPECIAL_EXAMS', False) or not exams_ida_enabled(course_key):
# if feature is not enabled then do a quick exit
return

Expand Down
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/proctoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def register_special_exams(course_key):
subsystem. Likewise, if formerly registered exams are unmarked, then those
registered exams are marked as inactive
"""
if not settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
if not getattr(settings, 'ENABLE_SPECIAL_EXAMS', False):
# if feature is not enabled then do a quick exit
return

Expand Down
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/rest_api/v1/views/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def get(self, request: Request):
),
'studio_name': settings.STUDIO_NAME,
'studio_short_name': settings.STUDIO_SHORT_NAME,
'studio_request_email': settings.FEATURES.get('STUDIO_REQUEST_EMAIL', ''),
'studio_request_email': getattr(settings, 'STUDIO_REQUEST_EMAIL', ''),
'tech_support_email': settings.TECH_SUPPORT_EMAIL,
'platform_name': settings.PLATFORM_NAME,
'user_is_active': request.user.is_active,
Expand Down
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/rest_api/v1/views/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def get(self, request: Request, course_id: str):
'course_display_name': course_block.display_name,
'course_display_name_with_default': course_block.display_name_with_default,
'platform_name': settings.PLATFORM_NAME,
'licensing_enabled': settings.FEATURES.get("LICENSING", False),
'licensing_enabled': getattr(settings, 'LICENSING', False),
})

serializer = CourseSettingsSerializer(settings_context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from unittest.mock import patch

import ddt
from django.conf import settings
from django.test.utils import override_settings
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
Expand Down Expand Up @@ -272,9 +271,7 @@ def test_update_exam_settings_excluded_field(self):
)
def test_update_exam_settings_invalid_value(self):
self.client.login(username=self.global_staff.username, password=self.password)
PROCTORED_EXAMS_ENABLED_FEATURES = settings.FEATURES
PROCTORED_EXAMS_ENABLED_FEATURES["ENABLE_PROCTORED_EXAMS"] = True
with override_settings(FEATURES=PROCTORED_EXAMS_ENABLED_FEATURES):
with override_settings(ENABLE_PROCTORED_EXAMS=True):
data = self.get_request_data(
enable_proctored_exams=True,
proctoring_provider="notvalidprovider",
Expand Down Expand Up @@ -366,42 +363,38 @@ def test_nonadmin_with_zendesk_ticket(
@override_waffle_flag(EXAMS_IDA, active=True)
def test_200_for_lti_provider(self):
self.client.login(username=self.global_staff.username, password=self.password)
PROCTORED_EXAMS_ENABLED_FEATURES = settings.FEATURES
PROCTORED_EXAMS_ENABLED_FEATURES["ENABLE_PROCTORED_EXAMS"] = True
with override_settings(FEATURES=PROCTORED_EXAMS_ENABLED_FEATURES):
with override_settings(ENABLE_PROCTORED_EXAMS=True):
data = self.get_request_data(
enable_proctored_exams=True,
proctoring_provider="lti_external",
)
response = self.make_request(data=data)

# response is correct
assert response.status_code == status.HTTP_200_OK

self.assertDictEqual( # noqa: PT009
response.data,
{
"proctored_exam_settings": {
"enable_proctored_exams": True,
"allow_proctoring_opt_out": True,
"proctoring_provider": "lti_external",
"proctoring_escalation_email": None,
"create_zendesk_tickets": True,
}
},
)
# response is correct
assert response.status_code == status.HTTP_200_OK

self.assertDictEqual( # noqa: PT009
response.data,
{
"proctored_exam_settings": {
"enable_proctored_exams": True,
"allow_proctoring_opt_out": True,
"proctoring_provider": "lti_external",
"proctoring_escalation_email": None,
"create_zendesk_tickets": True,
}
},
)

# course settings have been updated
updated = modulestore().get_item(self.course.location)
assert updated.enable_proctored_exams is True
assert updated.proctoring_provider == "lti_external"
# course settings have been updated
updated = modulestore().get_item(self.course.location)
assert updated.enable_proctored_exams is True
assert updated.proctoring_provider == "lti_external"

@override_waffle_flag(EXAMS_IDA, active=False)
def test_400_for_disabled_lti(self):
self.client.login(username=self.global_staff.username, password=self.password)
PROCTORED_EXAMS_ENABLED_FEATURES = settings.FEATURES
PROCTORED_EXAMS_ENABLED_FEATURES["ENABLE_PROCTORED_EXAMS"] = True
with override_settings(FEATURES=PROCTORED_EXAMS_ENABLED_FEATURES):
with override_settings(ENABLE_PROCTORED_EXAMS=True):
data = self.get_request_data(
enable_proctored_exams=True,
proctoring_provider="lti_external",
Expand Down
58 changes: 29 additions & 29 deletions cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -1333,50 +1333,50 @@ def test_create_course_with_bad_organization(self):
self.course_data['org'] = 'University of California, Berkeley'
self.assert_course_creation_failed(r"(?s)Unable to create course 'Robot Super Course'.*")

@override_settings(DISABLE_COURSE_CREATION=True)
def test_create_course_with_course_creation_disabled_staff(self):
"""Test new course creation -- course creation disabled, but staff access."""
with mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_COURSE_CREATION': True}):
self.assert_created_course()
self.assert_created_course()

@override_settings(DISABLE_COURSE_CREATION=True)
def test_create_course_with_course_creation_disabled_not_staff(self):
"""Test new course creation -- error path for course creation disabled, not staff access."""
with mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_COURSE_CREATION': True}):
self.user.is_staff = False
self.user.save()
self.assert_course_permission_denied()
self.user.is_staff = False
self.user.save()
self.assert_course_permission_denied()

@override_settings(ENABLE_CREATOR_GROUP=True)
def test_create_course_no_course_creators_staff(self):
"""Test new course creation -- course creation group enabled, staff, group is empty."""
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
self.assert_created_course()
self.assert_created_course()

@override_settings(ENABLE_CREATOR_GROUP=True)
def test_create_course_no_course_creators_not_staff(self):
"""Test new course creation -- error path for course creator group enabled, not staff, group is empty."""
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
self.user.is_staff = False
self.user.save()
self.assert_course_permission_denied()
self.user.is_staff = False
self.user.save()
self.assert_course_permission_denied()

@override_settings(ENABLE_CREATOR_GROUP=True)
def test_create_course_with_course_creator(self):
"""Test new course creation -- use course creator group"""
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
auth.add_users(self.user, CourseCreatorRole(), self.user)
self.assert_created_course()
auth.add_users(self.user, CourseCreatorRole(), self.user)
self.assert_created_course()

@override_settings(ALLOW_UNICODE_COURSE_ID=False)
def test_create_course_with_unicode_in_id_disabled(self):
"""
Test new course creation with feature setting: ALLOW_UNICODE_COURSE_ID disabled.
"""
with mock.patch.dict('django.conf.settings.FEATURES', {'ALLOW_UNICODE_COURSE_ID': False}):
error_message = "Special characters not allowed in organization, course number, and course run."
self.course_data['org'] = '��������������'
self.assert_create_course_failed(error_message)
error_message = "Special characters not allowed in organization, course number, and course run."
self.course_data['org'] = '��������������'
self.assert_create_course_failed(error_message)

self.course_data['number'] = '��chantillon'
self.assert_create_course_failed(error_message)
self.course_data['number'] = '��chantillon'
self.assert_create_course_failed(error_message)

self.course_data['run'] = '����������'
self.assert_create_course_failed(error_message)
self.course_data['run'] = '����������'
self.assert_create_course_failed(error_message)

def assert_course_permission_denied(self):
"""
Expand Down Expand Up @@ -2014,13 +2014,13 @@ def test_rerun_course_fail_duplicate_course(self):
# Verify that the existing course continues to be in the course listing
self.assertInCourseListing(existent_course_key)

@override_settings(ENABLE_CREATOR_GROUP=True)
def test_rerun_with_permission_denied(self):
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
source_course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
auth.add_users(self.user, CourseCreatorRole(), self.user)
self.user.is_staff = False
self.user.save()
self.post_rerun_request(source_course.id, response_code=403, expect_error=True)
source_course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
auth.add_users(self.user, CourseCreatorRole(), self.user)
self.user.is_staff = False
self.user.save()
self.post_rerun_request(source_course.id, response_code=403, expect_error=True)

def test_rerun_error(self):
error_message = "Mock Error Message"
Expand Down
16 changes: 8 additions & 8 deletions cms/djangoapps/contentstore/tests/test_course_create_rerun.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def test_course_creation_for_known_organization(self, organizations_autocreate):
self.assertEqual(len(course_orgs), 1) # noqa: PT009
self.assertEqual(course_orgs[0]['short_name'], 'orgX') # noqa: PT009

@override_settings(FEATURES={'ENABLE_CREATOR_GROUP': True})
@override_settings(ENABLE_CREATOR_GROUP=True)
def test_course_creation_when_user_not_in_org(self):
"""
Tests course creation when user doesn't have the required role.
Expand All @@ -208,7 +208,7 @@ def test_course_creation_when_user_not_in_org(self):
})
self.assertEqual(response.status_code, 403) # noqa: PT009

@override_settings(FEATURES={'ENABLE_CREATOR_GROUP': True})
@override_settings(ENABLE_CREATOR_GROUP=True)
@mock.patch(
'cms.djangoapps.course_creators.admin.render_to_string',
mock.Mock(side_effect=mock_render_to_string, autospec=True)
Expand All @@ -235,7 +235,7 @@ def test_course_creation_when_user_in_org_with_creator_role(self):
})
self.assertEqual(response.status_code, 200) # noqa: PT009

@override_settings(FEATURES={'ENABLE_CREATOR_GROUP': True})
@override_settings(ENABLE_CREATOR_GROUP=True)
@mock.patch(
'cms.djangoapps.course_creators.admin.render_to_string',
mock.Mock(side_effect=mock_render_to_string, autospec=True)
Expand All @@ -262,7 +262,7 @@ def test_course_creation_with_all_org_checked(self):
})
self.assertEqual(response.status_code, 200) # noqa: PT009

@override_settings(FEATURES={'ENABLE_CREATOR_GROUP': True})
@override_settings(ENABLE_CREATOR_GROUP=True)
@mock.patch(
'cms.djangoapps.course_creators.admin.render_to_string',
mock.Mock(side_effect=mock_render_to_string, autospec=True)
Expand Down Expand Up @@ -291,7 +291,7 @@ def test_course_creation_with_permission_for_specific_organization(self):
})
self.assertEqual(response.status_code, 200) # noqa: PT009

@override_settings(FEATURES={'ENABLE_CREATOR_GROUP': True})
@override_settings(ENABLE_CREATOR_GROUP=True)
@mock.patch(
'cms.djangoapps.course_creators.admin.render_to_string',
mock.Mock(side_effect=mock_render_to_string, autospec=True)
Expand Down Expand Up @@ -420,7 +420,7 @@ def setUp(self):
# ------------------------------------------------------------
# CREATE COURSE -- Non-staff users and existing Organization
# ------------------------------------------------------------
@override_settings(FEATURES={"DISABLE_COURSE_CREATION": False})
@override_settings(DISABLE_COURSE_CREATION=False)
def test_create_course_unauthorized(self):
"""
User without role cannot create course.
Expand All @@ -435,7 +435,7 @@ def test_create_course_unauthorized(self):

assert response.status_code == 403

@override_settings(FEATURES={"DISABLE_COURSE_CREATION": False})
@override_settings(DISABLE_COURSE_CREATION=False)
def test_create_course_unauthorized_with_role(self):
"""
User with role but without required permission cannot create course.
Expand Down Expand Up @@ -477,7 +477,7 @@ def test_create_course_staff(self):
# ------------------------------------------------------------
# FEATURE FLAG
# ------------------------------------------------------------
@override_settings(FEATURES={"DISABLE_COURSE_CREATION": True})
@override_settings(DISABLE_COURSE_CREATION=True)
def test_create_course_disabled_by_flag(self):
"""
Even authorized users cannot create course if feature flag is off.
Expand Down
6 changes: 3 additions & 3 deletions cms/djangoapps/contentstore/tests/test_course_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ def test_invalid_pre_requisite_course(self):
response = self.client.ajax_post(url, course_detail_json)
self.assertEqual(400, response.status_code) # noqa: PT009

@unittest.skipUnless(settings.FEATURES.get('ENTRANCE_EXAMS', False), True)
@unittest.skipUnless(getattr(settings, 'ENTRANCE_EXAMS', False), True)
def test_entrance_exam_created_updated_and_deleted_successfully(self):
"""
This tests both of the entrance exam settings and the `any_unfulfilled_milestones` helper.
Expand Down Expand Up @@ -396,7 +396,7 @@ def test_entrance_exam_created_updated_and_deleted_successfully(self):
self.assertFalse(milestones_helpers.any_unfulfilled_milestones(self.course.id, self.user.id), # noqa: PT009
msg='The entrance exam should not be required anymore')

@unittest.skipUnless(settings.FEATURES.get('ENTRANCE_EXAMS', False), True)
@unittest.skipUnless(getattr(settings, 'ENTRANCE_EXAMS', False), True)
def test_entrance_exam_store_default_min_score(self):
"""
test that creating an entrance exam should store the default value, if key missing in json request
Expand Down Expand Up @@ -435,7 +435,7 @@ def test_entrance_exam_store_default_min_score(self):
self.assertTrue(course.entrance_exam_enabled) # noqa: PT009
self.assertEqual(course.entrance_exam_minimum_score_pct, .5) # noqa: PT009

@unittest.skipUnless(settings.FEATURES.get('ENTRANCE_EXAMS', False), True)
@unittest.skipUnless(getattr(settings, 'ENTRANCE_EXAMS', False), True)
@mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True})
def test_entrance_after_changing_other_setting(self):
"""
Expand Down
6 changes: 3 additions & 3 deletions cms/djangoapps/contentstore/tests/test_exams.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import ddt
from django.conf import settings
from django.test.utils import override_settings
from edx_toggles.toggles.testutils import override_waffle_flag
from freezegun import freeze_time
from pytz import utc
Expand All @@ -19,8 +20,7 @@

@ddt.ddt
@override_waffle_flag(EXAMS_IDA, active=True)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PROCTORED_EXAMS': True})
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True})
@override_settings(ENABLE_PROCTORED_EXAMS=True, ENABLE_SPECIAL_EXAMS=True)
@patch('cms.djangoapps.contentstore.exams._patch_course_exams')
@patch('cms.djangoapps.contentstore.signals.handlers.transaction.on_commit',
new=Mock(side_effect=lambda func: func()),) # run right away
Expand Down Expand Up @@ -160,7 +160,7 @@ def test_dangling_exam(self, mock_patch_course_exams):
listen_for_course_publish(self, self.course.id)
mock_patch_course_exams.assert_called_once_with([], self.course_key)

@patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': False})
@override_settings(ENABLE_SPECIAL_EXAMS=False)
def test_feature_flag_off(self, mock_patch_course_exams):
"""
Make sure the feature flag is honored
Expand Down
Loading
Loading