Repository navigation
feat: call filters for learner block binds and course API responses - #39269
asadali145 wants to merge 7 commits into
Conversation
|
Thanks for the pull request, @asadali145! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. 🔘 Update the status of your PRYour PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate. Where can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
bc9f157 to
da63b4a
Compare
a5c06c6 to
da63b4a
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Dependency compatibility, shared-cache isolation, and repeated block-filter mutations remain unresolved.
3 open issues
What changed in this PR
Adds plugin extension points for learner-specific course presentation beyond render-time filtering.
Changes:
- Adds filters for bound XBlocks and transformed course structures.
- Filters serialized outline and metadata API responses.
- Separates navigation sidebar cache entries by active language.
| File | Description |
|---|---|
openedx/core/djangoapps/courseware_api/views.py |
Filters serialized courseware metadata. |
openedx/core/djangoapps/content/learning_sequences/views.py |
Filters serialized learner outlines. |
lms/djangoapps/courseware/block_render.py |
Filters blocks after binding and access handling. |
lms/djangoapps/course_home_api/outline/views.py |
Adds language to sidebar cache keys. |
lms/djangoapps/course_home_api/course_metadata/views.py |
Filters course-home metadata responses. |
lms/djangoapps/course_blocks/api.py |
Filters transformed course structures. |
Review effort: Balanced
Run new openedx-filters hooks so plugins can adjust how course content is presented to a learner on each request: - LearnerXBlockBound at the end of get_block_for_descriptor, after the block is bound and its access checked (renders, children, handlers, sequence metadata, course pages). - CourseBlocksTransformCompleted on the transformed block structure in get_course_blocks (a per-request copy). - CourseOutlineAPIRenderStarted, CourseHomeMetadataAPIRenderStarted and CoursewareMetadataAPIRenderStarted on the serialized API responses. The navigation sidebar cache key now includes the active request language, since block structure filter steps can vary block data by it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
da63b4a to
5365d88
Compare
… first bound while unknown Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Block structure collection and course overviews are shared by every user, but they could read block instances that the current request had already loaded and bound to a user (and that LearnerXBlockBound steps may have changed in memory), or load children through a runtime prepared for that user. - Add a fresh_block_reads(course_key) modulestore context manager: a no-op by default, delegated by the mixed and CCX stores, and in split it sets aside the bulk operation's cached blocks and the request's runtimes. - Use it in BlockStructureManager._update_collected and CourseOverview.load_from_module_store. - Restore user.known in a finally block after collection. - Run LearnerXBlockBound for anonymous users too (their id is None). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Cross-cutting runtime and shared-cache isolation needs maintainer validation, with configured-filter and language-cache regression coverage still missing.
6 open issues
Prevent learner-specific mutations entering shared block cache Add regression test for course block filter cache isolation · New Test metadata filter output and masqueraded user · New Test navigation cache partitioning by language · New Test outline filter output and cache isolation · New Test courseware retrieve override and masquerading · New
2 resolved since last review
Review effort: Balanced
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
XML-backed courses still reuse personalized block instances when rebuilding shared course overviews.
7 open issues
Prevent XML course personalization from leaking into shared metadata · New Prevent learner-specific mutations entering shared block cache Test courseware retrieve override and masquerading Test outline filter output and cache isolation Test navigation cache partitioning by language Test metadata filter output and masqueraded user Add regression test for course block filter cache isolation
Review effort: Balanced
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Cross-cutting changes to learner binding and shared-cache isolation require maintainer validation before approval.
No open issues 🎉
7 resolved since last review
Prevent XML course personalization from leaking into shared metadata Prevent learner-specific mutations entering shared block cache Test courseware retrieve override and masquerading Test outline filter output and cache isolation Test navigation cache partitioning by language Test metadata filter output and masqueraded user Add regression test for course block filter cache isolation
Review effort: Balanced
…lters Each view passes its serialized response through its new filter. The navigation sidebar runs it after the cache, so the cached blocks stay as built. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>


Description
Calls nine new openedx-filters so plugins can adjust how course content is presented to a learner on each request. Render-time filters alone do not reach handler requests, sequence metadata, course pages or the cached course structure and outline, so changes made there are lost on the next interaction.
User roles affected: Developer / Operator (new extension points). No behavior change unless a pipeline step is configured.
Changes:
lms/djangoapps/courseware/block_render.py—get_block_for_descriptorreturns the block throughLearnerXBlockBound.run_filter(block, user, course_key)after binding and the access check. This covers unit children, library and conditional children, XBlock handler calls, sequence metadata, static tabs, course info and about sections. It runs once each time a block is bound to a user, including anonymous users, and again when the same instance is bound to a different user. It does not run for unknown users (user.knownis False): block structure collection and no-auth callbacks.lms/djangoapps/course_blocks/api.py—get_course_blockspasses the transformed structure throughCourseBlocksTransformCompleted(per-request copy; feeds outline, sidebar, blocks API, dates, progress).openedx/core/djangoapps/content/learning_sequences/views.py—CourseOutlineAPIRenderStartedon the serialized outline (breadcrumbs, next up). The cached outline objects are not passed.lms/djangoapps/course_home_api/course_metadata/views.py—CourseHomeMetadataAPIRenderStartedon the serialized response (course title, tabs).openedx/core/djangoapps/courseware_api/views.py—CoursewareInformation.retrieverunsCoursewareMetadataAPIRenderStartedon the serialized response.lms/djangoapps/course_home_api/{progress,dates,outline}/views.py—ProgressTabView,DatesTabView,OutlineTabViewandCourseNavigationBlocksViewpass their serialized responses throughCourseHomeProgressAPIRenderStarted(with the learner whose progress is shown),CourseHomeDatesAPIRenderStarted,CourseHomeOutlineAPIRenderStartedandCourseNavigationAPIRenderStarted. The navigation filter runs after the sidebar cache, so the cached blocks stay as built.xmodule/modulestore— newfresh_block_reads(course_key)context manager. It is a no-op by default and delegated by the mixed store and the CCX wrapper. In split it sets aside the active bulk operation's cached block instances and the request's runtimes, then restores them.BlockStructureManager._update_collectedandCourseOverview.load_from_module_storeuse it, so data shared by every user is always built from fresh, unbound block instances. Before this, a cache miss in a request that had already bound blocks could collect those (possibly changed) instances, or load children through a runtime prepared for that user.user.knownis now restored in afinallyafter collection.lms/djangoapps/course_home_api/outline/views.py— the navigation sidebar cache key now includes the active request language. The sidebar caches the output ofCourseBlocksTransformCompletedfor an hour (shared by anonymous users), and block structure filter steps can vary block data by the request's active language.Supporting information
Testing instructions
pip install git+https://github.com/mitodl/openedx-filters@asad/xblock-handler-started.OPEN_EDX_FILTERS_CONFIG, for example:org.openedx.learning.xblock.bound.v1: prefixblock.display_namein memory;org.openedx.learning.course_blocks.transform.completed.v1:block_structure.override_xblock_field(key, "display_name", ...);data["title"]/data["name"].Then load a unit, submit a problem, and open the course outline, sidebar and breadcrumbs in the learning MFE, and check the changed values show everywhere.
Deadline
None.
Other information
LearnerXBlockBoundruns on every block bind. With no pipeline configured,run_filterreturns immediately; steps should return early when they have nothing to do.