Skip to content

[R&D] Drop the addition of XModuleMixin for the xblocks-contrib xblocks #223

Closed

Description

@farhan

Drop the addition of XModuleMixin for the xblocks-contrib xblocks here

It's the R&D story to find the implementing and testing way to drop the addition of XModuleMixin for the xblocks-contrib xblocks

Activity

  1. self-assigned this
    on Mar 31, 2026
  2. moved this to 🏗 In progress in Aximprovements Teamon Mar 31, 2026
  3. farhan commented on Mar 31, 2026

    @farhan
    ContributorAuthor

    R&D Summary

    Main PR after R&D: openedx/openedx-platform#38264

    We can follow these steps per block (or in common branch) to drop the XModuleMixin support from the xblocks-contrib blocks (or even others can make a document How to standardize the xblock)

    • Study the given main PR
    • Add the required block name in STANDARD_COMPLIANT_XBLOCKS list
    • Use the corresponding xblocks branch for the testing or example via tutor mount or as used in the above PR
    • Run the block on lms/cms and fix the issues or place the required methods to the block for the support. Example PR
    • Fix the required test cases on the edx-platform
    • Make a list of all the methods that are needed to add in the xblocks-contrib blocks
    • Discuss the list with the team and brainstorm the best ways of add them
    • Make a list of the methods that should be moved to xblocks package upstream
  4. changed the title [-]Drop the addition of `XModuleMixin` for the `xblocks-contrib` xblocks[/-] [+][R&D] Drop the addition of `XModuleMixin` for the `xblocks-contrib` xblocks[/+] on Apr 1, 2026
  5. farhan commented on Apr 2, 2026

    @farhan
    ContributorAuthor

    @feanil @kdmccormick @openedx/axim-aximprovements

    Here is the output of the activity discussed in the yesterdays team meeting

    I have done testing on extracted WordCloudBlock, VideoBlock, LTIBlock, HtmlBlock, AnnotatableBlock, ProblemBlock and as per testing following methods and fields are required to make these XBlock compatible with openedx-platform runtime if we don't attach the XmoduleMixin mixin.

        has_score = False

    crash logs without it

    File "/openedx/edx-platform/lms/djangoapps/courseware/model_data.py", line 776, in cache_for_block_descendents
    cache.add_block_descendents(block, depth, block_filter)
    File "/openedx/edx-platform/lms/djangoapps/courseware/model_data.py", line 760, in add_block_descendents
    self.add_blocks_to_cache(blocks)
    File "/openedx/edx-platform/lms/djangoapps/courseware/model_data.py", line 715, in add_blocks_to_cache
    self.scorable_locations.update(block.location for block in blocks if block.has_score)
    File "/openedx/edx-platform/lms/djangoapps/courseware/model_data.py", line 715, in
    self.scorable_locations.update(block.location for block in blocks if block.has_score)
    ^^^^^^^^^^^^^^^
    AttributeError: 'WordCloudBlockWithMixins' object has no attribute 'has_score'

        @property
        def course_id(self):
            """
            Return the key of the block to which this Course belongs.
    
            New code should always used `context_key`, which is the key of the Learning Context to which
            this block belongs. "Learning Context" is a generalized notion of Courses which is inclusive
            of Content Libraries, et al.
            """
            return self.context_key
    
        @property
        def category(self):
            """
            Return the block type, formerly known as "category".
    
            Preferred forms for new code: `self.usage_key.block_type` or `self.scope_ids.blocks_type`
            """
            return self.scope_ids.block_type
    
        @property
        def location(self):
            """
            Return the usage key identifying this block instance, formerly called the "location".
    
            `self.usage_key` is always preferred in new code.
            """
            return self.usage_key
    
        @location.setter
        def location(self, value):
            from opaque_keys.edx.keys import UsageKey
            assert isinstance(value, UsageKey)
            self.scope_ids = self.scope_ids._replace(
                def_id=value,  # Note: assigning a UsageKey as def_id is OK in old mongo / import system but wrong in split
                usage_id=value,
            )
    
        @property
        def url_name(self):
            """
            Return the URL-friendly name for this block.
    
            Preferred forms for new code: `self.usage_key.block_id`
            """
            return self.usage_key.block_id

    These 4 properties have been using at lots of code places within openedx-platform

        def get_icon_class(self):
            """
            Return a css class identifying this module in the context of an icon
            """
            return 'other'
            # return self.icon_class

    crash logs without it

    File "/openedx/edx-platform/openedx/core/lib/xblock_utils/init.py", line 522, in get_icon
    return block.get_icon_class()
    ^^^^^^^^^^^^^^^^^^^^^^
    File "/openedx/edx-platform/xmodule/vertical_block.py", line 241, in get_icon_class
    child_classes = {child.get_icon_class() for child in self.get_children()}
    ^^^^^^^^^^^^^^^^^^^^

        @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 (
                self.display_name if self.display_name is not None
                else self.usage_key.block_id.replace('_', ' ')
            )

    crash logs without it

    File "/mnt/xblock/xblock/runtime.py", line 829, in render
    updated_frag = self.wrap_xblock(block, view_name, frag, context)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/openedx/edx-platform/xmodule/x_module.py", line 964, in wrap_xblock
    frag = wrapper(block, view, frag, context)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/openedx/edx-platform/openedx/core/lib/xblock_utils/init.py", line 147, in wrap_xblock
    'display_name': block.display_name_with_default, # xss-lint: disable=python-deprecated-display-name
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    AttributeError: 'HtmlBlockWithMixins' object has no attribute 'display_name_with_default'

        def bind_for_student(self, user_id, wrappers=None):
            """
            Set up this XBlock to act as an XModule instead of an XModuleDescriptor.
    
            Arguments:
                user_id: The user_id to set in scope_ids
                wrappers: These are a list functions that put a wrapper, such as
                          LmsFieldData or OverrideFieldData, around the field_data.
                          Note that the functions will be applied in the order in
                          which they're listed. So [f1, f2] -> f2(f1(field_data))
            """
    
            # Skip rebinding if we're already bound a user, and it's this user.
            if self.scope_ids.user_id is not None and user_id == self.scope_ids.user_id:
                if getattr(self.runtime, "position", None):
                    self.position = self.runtime.position  # update the position of the tab
                return
    
            # If we are switching users mid-request, save the data from the old user.
            self.save()
    
            # Update scope_ids to point to the new user.
            self.scope_ids = self.scope_ids._replace(user_id=user_id)
    
            # Clear out any cached instantiated children.
            self.clear_child_cache()
    
            # Clear out any cached field data scoped to the old user.
            for field in self.fields.values():
                if field.scope in (Scope.parent, Scope.children):
                    continue
    
                if field.scope.user == UserScope.ONE:
                    field._del_cached_value(self)  # pylint: disable=protected-access
                    # not the most elegant way of doing this, but if we're removing
                    # a field from the module's field_data_cache, we should also
                    # remove it from its _dirty_fields
                    if field in self._dirty_fields:
                        del self._dirty_fields[field]
    
            if wrappers:
                # Put user-specific wrappers around the field-data service for this block.
                # Note that these are different from modulestore.xblock_field_data_wrappers, which are not user-specific.
                wrapped_field_data = self.runtime.service(self, "field-data-unbound")
                for wrapper in wrappers:
                    wrapped_field_data = wrapper(wrapped_field_data)
                self._bound_field_data = wrapped_field_data
                if getattr(self.runtime, "uses_deprecated_field_data", False):
                    # This approach is deprecated but OldModuleStoreRuntime still requires it.
                    # For SplitModuleStoreRuntime, don't set ._field_data this way.
                    self._field_data = wrapped_field_data

    crash logs without it

    File "/mnt/xblock/xblock/core.py", line 866, in get_child
    child_block = self.runtime.get_block(usage_id, for_parent=self)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/openedx/edx-platform/xmodule/x_module.py", line 1478, in get_block
    return self.get_block_for_descriptor(block)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/openedx/edx-platform/cms/djangoapps/contentstore/views/preview.py", line 288, in _load_preview_block
    block.bind_for_student(
    ^^^^^^^^^^^^^^^^^^^^^^
    AttributeError: 'WordCloudBlockWithMixins' object has no attribute 'bind_for_student'

        def get_required_block_descriptors(self):
            """
            Return a list of XBlock instances upon which this block depends but are
            not children of this block.
    
            TODO: Move this method directly to the ConditionalBlock.
            """
            return []

    crash logs without it

    File "/openedx/edx-platform/lms/djangoapps/courseware/model_data.py", line 753, in get_child_blocks
    blocks.extend(get_child_blocks(child, new_depth, block_filter))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/openedx/edx-platform/lms/djangoapps/courseware/model_data.py", line 753, in get_child_blocks
    blocks.extend(get_child_blocks(child, new_depth, block_filter))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/openedx/edx-platform/lms/djangoapps/courseware/model_data.py", line 752, in get_child_blocks
    for child in block.get_children() + block.get_required_block_descriptors():
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    AttributeError: 'WordCloudBlockWithMixins' object has no attribute 'get_required_block_descriptors'

  6. moved this from 🏗 In progress to 👀 In review in Aximprovements Teamon Apr 2, 2026
  7. moved this from 👀 In review to ✅ Done in Aximprovements Teamon Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions