From c39452184db18ab12a98ead435510ee8589e1097 Mon Sep 17 00:00:00 2001 From: Irtaza Akram Date: Tue, 14 Apr 2026 13:21:14 +0500 Subject: [PATCH 1/5] fix: remove legacy xmodulemixin from xblocks-contrib xblocks --- .../course_home_api/outline/tests/test_view.py | 2 +- .../courseware/tests/test_block_render.py | 12 ++++++++---- openedx/envs/common.py | 4 ++-- requirements/edx/base.txt | 4 ++-- requirements/edx/development.txt | 4 ++-- requirements/edx/doc.txt | 4 ++-- requirements/edx/testing.txt | 4 ++-- xmodule/modulestore/tests/test_xml.py | 10 ++++++---- xmodule/tests/test_conditional.py | 4 ++-- xmodule/tests/test_import.py | 16 ++++++++-------- 10 files changed, 35 insertions(+), 29 deletions(-) diff --git a/lms/djangoapps/course_home_api/outline/tests/test_view.py b/lms/djangoapps/course_home_api/outline/tests/test_view.py index c744a33ee048..e9a3570ea6bd 100644 --- a/lms/djangoapps/course_home_api/outline/tests/test_view.py +++ b/lms/djangoapps/course_home_api/outline/tests/test_view.py @@ -881,7 +881,7 @@ def test_vertical_icon(self, block_categories, expected_icon): assert vertical_data['icon'] == expected_icon - @patch('xmodule.html_block.HtmlBlock.icon_class', 'video') + @patch('xmodule.x_module.XModuleMixin.icon_class', 'video') def test_vertical_icon_determined_by_icon_class(self): """Test that the API checks the children `icon_class` to determine the icon for the unit.""" self.add_blocks_to_course() diff --git a/lms/djangoapps/courseware/tests/test_block_render.py b/lms/djangoapps/courseware/tests/test_block_render.py index a55ac3298b2e..d7d13141ef98 100644 --- a/lms/djangoapps/courseware/tests/test_block_render.py +++ b/lms/djangoapps/courseware/tests/test_block_render.py @@ -45,7 +45,11 @@ from xblock.exceptions import NoSuchServiceError from xblock.field_data import FieldData # lint-amnesty, pylint: disable=wrong-import-order from xblock.fields import ScopeIds # lint-amnesty, pylint: disable=wrong-import-order -from xblock.runtime import DictKeyValueStore, KvsFieldData # lint-amnesty, pylint: disable=wrong-import-order +from xblock.runtime import ( # lint-amnesty, pylint: disable=wrong-import-order + DictKeyValueStore, + KvsFieldData, + Mixologist, # lint-amnesty, pylint: disable=wrong-import-order +) from xblock.test.tools import TestRuntime # lint-amnesty, pylint: disable=wrong-import-order from xblocks_contrib.problem.capa.tests.response_xml_factory import ( OptionResponseXMLFactory, # lint-amnesty, pylint: disable=reimported @@ -1931,8 +1935,9 @@ def setUp(self): @patch('lms.djangoapps.courseware.block_render.has_access', Mock(return_value=True, autospec=True)) def _get_anonymous_id(self, course_id, xblock_class, should_get_deprecated_id: bool): # lint-amnesty, pylint: disable=missing-function-docstring location = course_id.make_usage_key('dummy_category', 'dummy_name') + mixed_class = Mixologist(settings.XBLOCK_MIXINS).mix(xblock_class) block = Mock( - spec=xblock_class, + spec=mixed_class, _field_data=Mock(spec=FieldData, name='field_data'), location=location, static_asset_path=None, @@ -1951,8 +1956,7 @@ def _get_anonymous_id(self, course_id, xblock_class, should_get_deprecated_id: b days_early_for_beta=None, ) block.runtime = ModuleStoreRuntime(None, None, None) - # Use the xblock_class's bind_for_student method - block.bind_for_student = partial(xblock_class.bind_for_student, block) + block.bind_for_student = partial(mixed_class.bind_for_student, block) if hasattr(xblock_class, 'module_class'): block.module_class = xblock_class.module_class diff --git a/openedx/envs/common.py b/openedx/envs/common.py index c7ede8e98481..e29253f6c012 100644 --- a/openedx/envs/common.py +++ b/openedx/envs/common.py @@ -2087,7 +2087,7 @@ def add_optional_apps(optional_apps, installed_apps): # .. toggle_warning: Not production-ready until relevant subtask https://github.com/openedx/edx-platform/issues/34827 is done. # .. toggle_creation_date: 2024-11-10 # .. toggle_target_removal_date: 2026-04-10 -USE_EXTRACTED_DISCUSSION_BLOCK = False +USE_EXTRACTED_DISCUSSION_BLOCK = True # .. toggle_name: USE_EXTRACTED_PROBLEM_BLOCK # .. toggle_default: False @@ -2097,7 +2097,7 @@ def add_optional_apps(optional_apps, installed_apps): # .. toggle_warning: Not production-ready until relevant subtask https://github.com/openedx/edx-platform/issues/34827 is done. # .. toggle_creation_date: 2024-11-10 # .. toggle_target_removal_date: 2026-04-10 -USE_EXTRACTED_PROBLEM_BLOCK = False +USE_EXTRACTED_PROBLEM_BLOCK = True # .. toggle_name: USE_EXTRACTED_VIDEO_BLOCK # .. toggle_default: True diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index a15d4f6966c7..9ad4dd1c69be 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -1283,7 +1283,7 @@ wrapt==2.1.2 # via # -r requirements/edx/kernel.in # xblocks-contrib -xblock[django]==6.0.0 +xblock[django] @ git+https://github.com/openedx/xblock.git@add-required-attrs # via # -r requirements/edx/kernel.in # acid-xblock @@ -1309,7 +1309,7 @@ xblock-utils==4.0.0 # via # edx-sga # xblock-poll -xblocks-contrib==0.15.3 +git+https://github.com/openedx/xblocks-contrib.git@remove-legacy-mixins#egg=xblocks-contrib # via -r requirements/edx/bundled.in xmlsec==1.3.14 # via diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index de4aa9bdda23..466785751400 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -2304,7 +2304,7 @@ wrapt==2.1.2 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # xblocks-contrib -xblock[django]==6.0.0 +xblock[django] @ git+https://github.com/openedx/xblock.git@add-required-attrs # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -2339,7 +2339,7 @@ xblock-utils==4.0.0 # -r requirements/edx/testing.txt # edx-sga # xblock-poll -xblocks-contrib==0.15.3 +git+https://github.com/openedx/xblocks-contrib.git@remove-legacy-mixins#egg=xblocks-contrib # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 11dacbbedfb9..67d276f10fae 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -1621,7 +1621,7 @@ wrapt==2.1.2 # via # -r requirements/edx/base.txt # xblocks-contrib -xblock[django]==6.0.0 +xblock[django] @ git+https://github.com/openedx/xblock.git@add-required-attrs # via # -r requirements/edx/base.txt # acid-xblock @@ -1648,7 +1648,7 @@ xblock-utils==4.0.0 # -r requirements/edx/base.txt # edx-sga # xblock-poll -xblocks-contrib==0.15.3 +git+https://github.com/openedx/xblocks-contrib.git@remove-legacy-mixins#egg=xblocks-contrib # via -r requirements/edx/base.txt xmlsec==1.3.14 # via diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 2103c5405005..f5a314cdf1b5 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1716,7 +1716,7 @@ wrapt==2.1.2 # via # -r requirements/edx/base.txt # xblocks-contrib -xblock[django]==6.0.0 +xblock[django] @ git+https://github.com/openedx/xblock.git@add-required-attrs # via # -r requirements/edx/base.txt # acid-xblock @@ -1743,7 +1743,7 @@ xblock-utils==4.0.0 # -r requirements/edx/base.txt # edx-sga # xblock-poll -xblocks-contrib==0.15.3 +git+https://github.com/openedx/xblocks-contrib.git@remove-legacy-mixins#egg=xblocks-contrib # via -r requirements/edx/base.txt xmlsec==1.3.14 # via diff --git a/xmodule/modulestore/tests/test_xml.py b/xmodule/modulestore/tests/test_xml.py index 5627ea451b2f..608ebad7b66d 100644 --- a/xmodule/modulestore/tests/test_xml.py +++ b/xmodule/modulestore/tests/test_xml.py @@ -64,7 +64,7 @@ def test_get_courses_for_wiki(self): """ Test the get_courses_for_wiki method """ - store = XMLModuleStore(DATA_DIR, source_dirs=['toy', 'simple']) + store = XMLModuleStore(DATA_DIR, source_dirs=['toy', 'simple'], xblock_mixins=(XModuleMixin,)) for course in store.get_courses(): course_locations = store.get_courses_for_wiki(course.wiki_slug) assert len(course_locations) == 1 @@ -90,7 +90,7 @@ def test_has_course(self): Test the has_course method """ check_has_course_method( - XMLModuleStore(DATA_DIR, source_dirs=['toy', 'simple']), + XMLModuleStore(DATA_DIR, source_dirs=['toy', 'simple'], xblock_mixins=(XModuleMixin,)), CourseKey.from_string('edX/toy/2012_Fall'), locator_key_fields=CourseLocator.KEY_FIELDS ) @@ -99,7 +99,7 @@ def test_branch_setting(self): """ Test the branch setting context manager """ - store = XMLModuleStore(DATA_DIR, source_dirs=['toy']) + store = XMLModuleStore(DATA_DIR, source_dirs=['toy'], xblock_mixins=(XModuleMixin,)) course = store.get_courses()[0] # XML store allows published_only branch setting @@ -152,7 +152,9 @@ def setUp(self): @patch("xmodule.modulestore.xml.glob.glob", side_effect=glob_tildes_at_end) def test_tilde_files_ignored(self, _fake_glob): # noqa: PT019 - modulestore = XMLModuleStore(DATA_DIR, source_dirs=['course_ignore'], load_error_blocks=False) + modulestore = XMLModuleStore( + DATA_DIR, source_dirs=["course_ignore"], xblock_mixins=(XModuleMixin,), load_error_blocks=False + ) about_location = CourseKey.from_string('edX/course_ignore/2014_Fall').make_usage_key( 'about', 'index', ) diff --git a/xmodule/tests/test_conditional.py b/xmodule/tests/test_conditional.py index 6ddb2b44a0fb..d096b186986d 100644 --- a/xmodule/tests/test_conditional.py +++ b/xmodule/tests/test_conditional.py @@ -20,7 +20,7 @@ from xmodule.tests.xml import XModuleXmlImportTest from xmodule.tests.xml import factories as xml from xmodule.validation import StudioValidationMessage -from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW +from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW, XModuleMixin ORG = 'test_org' COURSE = 'conditional' # name of directory with course data @@ -227,7 +227,7 @@ def add_block_as_child_node(block, node): block.add_xml_to_node(child) self.test_system = get_test_system(add_get_block_overrides=True) self.test_system.add_block_as_child_node = add_block_as_child_node - self.modulestore = XMLModuleStore(DATA_DIR, source_dirs=['conditional_and_poll']) + self.modulestore = XMLModuleStore(DATA_DIR, source_dirs=['conditional_and_poll'], xblock_mixins=(XModuleMixin,)) courses = self.modulestore.get_courses() assert len(courses) == 1 self.course = courses[0] diff --git a/xmodule/tests/test_import.py b/xmodule/tests/test_import.py index 3c92d924b64f..126914c5bb43 100644 --- a/xmodule/tests/test_import.py +++ b/xmodule/tests/test_import.py @@ -69,7 +69,7 @@ def get_course(self, name): modulestore = XMLModuleStore( DATA_DIR, source_dirs=[name], - xblock_mixins=(InheritanceMixin,), + xblock_mixins=(InheritanceMixin, XModuleMixin), ) courses = modulestore.get_courses() assert len(courses) == 1 @@ -461,7 +461,7 @@ def test_policy_loading(self): def test_static_tabs_import(self): """Make sure that the static tabs are imported correctly""" - modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy']) + modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy'], xblock_mixins=(XModuleMixin,)) location_tab_syllabus = BlockUsageLocator(CourseLocator("edX", "toy", "2012_Fall", deprecated=True), "static_tab", "syllabus", deprecated=True) @@ -483,7 +483,7 @@ def test_definition_loading(self): happen--locations should uniquely name definitions. But in our imperfect XML world, it can (and likely will) happen.""" - modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy', 'two_toys']) + modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy', 'two_toys'], xblock_mixins=(XModuleMixin,)) location = BlockUsageLocator(CourseLocator("edX", "toy", "2012_Fall", deprecated=True), "video", "Welcome", deprecated=True) @@ -499,7 +499,7 @@ def test_colon_in_url_name(self): print("Starting import") # Not using get_courses because we need the modulestore object too afterward - modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy']) + modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy'], xblock_mixins=(XModuleMixin,)) courses = modulestore.get_courses() assert len(courses) == 1 course = courses[0] @@ -533,7 +533,7 @@ def test_unicode(self): exceptions/errors to that effect.""" print("Starting import") - modulestore = XMLModuleStore(DATA_DIR, source_dirs=['test_unicode']) + modulestore = XMLModuleStore(DATA_DIR, source_dirs=['test_unicode'], xblock_mixins=(XModuleMixin,)) courses = modulestore.get_courses() assert len(courses) == 1 course = courses[0] @@ -553,7 +553,7 @@ def test_url_name_mangling(self): Make sure that url_names are only mangled once. """ - modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy']) + modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy'], xblock_mixins=(XModuleMixin,)) toy_id = CourseKey.from_string('edX/toy/2012_Fall') @@ -571,7 +571,7 @@ def test_url_name_mangling(self): assert len(video.usage_key.block_id) == (len('video_') + 12) def test_poll_and_conditional_import(self): - modulestore = XMLModuleStore(DATA_DIR, source_dirs=['conditional_and_poll']) + modulestore = XMLModuleStore(DATA_DIR, source_dirs=['conditional_and_poll'], xblock_mixins=(XModuleMixin,)) course = modulestore.get_courses()[0] chapters = course.get_children() @@ -624,7 +624,7 @@ def test_cohort_config(self): Note: The cohort config on the CourseBlock is no longer used. See openedx.core.djangoapps.course_groups.models.CourseCohortSettings. """ - modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy']) + modulestore = XMLModuleStore(DATA_DIR, source_dirs=['toy'], xblock_mixins=(XModuleMixin,)) toy_id = CourseKey.from_string('edX/toy/2012_Fall') From 512b2c94a2e1d8a245e4e6b615c2a6c6baf9033f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:29:01 +0500 Subject: [PATCH 2/5] feat: Upgrade Python dependency xblocks-contrib (#38399) Commit generated by workflow `openedx/openedx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` Co-authored-by: irtazaakram <51848298+irtazaakram@users.noreply.github.com> --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 67c5b4132e35..ebc81825bbb4 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -1316,7 +1316,7 @@ xblock-utils==4.0.0 # via # edx-sga # xblock-poll -git+https://github.com/openedx/xblocks-contrib.git@remove-legacy-mixins#egg=xblocks-contrib +xblocks-contrib==0.16.0 # via -r requirements/edx/bundled.in xmlsec==1.3.14 # via diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index b99456c41e8c..8ee0437420c9 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -2350,7 +2350,7 @@ xblock-utils==4.0.0 # -r requirements/edx/testing.txt # edx-sga # xblock-poll -git+https://github.com/openedx/xblocks-contrib.git@remove-legacy-mixins#egg=xblocks-contrib +xblocks-contrib==0.16.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 4b1ca79f3353..084c0c5a3404 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -1655,7 +1655,7 @@ xblock-utils==4.0.0 # -r requirements/edx/base.txt # edx-sga # xblock-poll -git+https://github.com/openedx/xblocks-contrib.git@remove-legacy-mixins#egg=xblocks-contrib +xblocks-contrib==0.16.0 # via -r requirements/edx/base.txt xmlsec==1.3.14 # via diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 4a69eb8dc0cd..3f4db6ef0dba 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1753,7 +1753,7 @@ xblock-utils==4.0.0 # -r requirements/edx/base.txt # edx-sga # xblock-poll -git+https://github.com/openedx/xblocks-contrib.git@remove-legacy-mixins#egg=xblocks-contrib +xblocks-contrib==0.16.0 # via -r requirements/edx/base.txt xmlsec==1.3.14 # via From c6a2a909fa6e2c67fee103509c5f20a14ed1d08b Mon Sep 17 00:00:00 2001 From: Irtaza Akram Date: Tue, 21 Apr 2026 13:36:24 +0500 Subject: [PATCH 3/5] fix: revert extracted problem toggle --- openedx/envs/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openedx/envs/common.py b/openedx/envs/common.py index 6edb8541fe08..79dcda48ed5e 100644 --- a/openedx/envs/common.py +++ b/openedx/envs/common.py @@ -2097,7 +2097,7 @@ def add_optional_apps(optional_apps, installed_apps): # .. toggle_warning: Not production-ready until relevant subtask https://github.com/openedx/edx-platform/issues/34827 is done. # .. toggle_creation_date: 2024-11-10 # .. toggle_target_removal_date: 2026-04-10 -USE_EXTRACTED_PROBLEM_BLOCK = True +USE_EXTRACTED_PROBLEM_BLOCK = False # .. toggle_name: USE_EXTRACTED_VIDEO_BLOCK # .. toggle_default: True From 68a5285b1defe164711815c28decebada10d6edb Mon Sep 17 00:00:00 2001 From: Irtaza Akram Date: Wed, 22 Apr 2026 14:30:06 +0500 Subject: [PATCH 4/5] fix: remove migrated attributes from xmodulemixin --- xmodule/x_module.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/xmodule/x_module.py b/xmodule/x_module.py index f1d574155e80..084c367b1431 100644 --- a/xmodule/x_module.py +++ b/xmodule/x_module.py @@ -355,14 +355,6 @@ def url_name(self): """ return self.usage_key.block_id - @property - def display_name_with_default(self): - """ - Return a display name for the module: use display_name if defined in - metadata, otherwise convert the url name. - """ - return block_metadata_utils.display_name_with_default(self) - @property def display_name_with_default_escaped(self): """ @@ -409,21 +401,6 @@ def get_asides(self): """ return self._asides - def get_explicitly_set_fields_by_scope(self, scope=Scope.content): - """ - Get a dictionary of the fields for the given scope which are set explicitly on this xblock. (Including - any set to None.) - """ - result = {} - for field in self.fields.values(): - if field.scope == scope and field.is_set_on(self): - try: - result[field.name] = field.read_json(self) - except TypeError as exception: - exception_message = f"{exception}, Block-location:{self.location}, Field-name:{field.name}" - raise TypeError(exception_message) from exception - return result - def has_children_at_depth(self, depth): r""" Returns true if self has children at the given depth. depth==0 returns @@ -519,12 +496,6 @@ def get_child_by(self, selector): return child return None - def get_icon_class(self): - """ - Return a css class identifying this module in the context of an icon - """ - return self.icon_class - def has_dynamic_children(self): """ Returns True if this block has dynamic children for a given From e74c0d9399a26e1ad601b85653e189ab17ccd961 Mon Sep 17 00:00:00 2001 From: Irtaza Akram Date: Wed, 22 Apr 2026 15:33:54 +0500 Subject: [PATCH 5/5] fix: add back display_name_with_default --- xmodule/x_module.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/xmodule/x_module.py b/xmodule/x_module.py index 084c367b1431..8fa466cc1f6c 100644 --- a/xmodule/x_module.py +++ b/xmodule/x_module.py @@ -355,6 +355,14 @@ def url_name(self): """ return self.usage_key.block_id + @property + def display_name_with_default(self): + """ + Return a display name for the module: use display_name if defined in + metadata, otherwise convert the url name. + """ + return block_metadata_utils.display_name_with_default(self) + @property def display_name_with_default_escaped(self): """