fix: contract course asset URLs to /static/ before saving xblock data (LP-704)#377
Open
djoseph-apphelix wants to merge 1 commit into
Open
fix: contract course asset URLs to /static/ before saving xblock data (LP-704)#377djoseph-apphelix wants to merge 1 commit into
djoseph-apphelix wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR prevents Studio from persisting course-pinned (non-portable) absolute asset URLs inside stored XBlock data, by contracting same-course asset URLs back to portable /static/<name> before saving.
Changes:
- Added
contract_static_urls(text, course_id)to reversereplace_static_urlsfor same-course asset URLs (including versioned, host-qualified, and legacy/c4x/forms). - Updated
_save_xblockto contract URLs in stringdataprior to persistingxblock.data. - Added unit and integration tests to verify contraction behavior and that foreign-course URLs are preserved.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| common/djangoapps/static_replace/init.py | Adds contract_static_urls implementation for contracting same-course asset URLs back to /static/. |
| common/djangoapps/static_replace/test/test_static_replace.py | Adds unit tests covering contraction across multiple URL forms and passthrough behavior. |
| cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py | Contracts same-course asset URLs in string data before saving XBlock content. |
| cms/djangoapps/contentstore/views/tests/test_block.py | Adds integration tests ensuring saved data is portable and foreign-course URLs remain unchanged. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+263
to
+265
| if not text or not course_id: | ||
| return text | ||
|
|
e432c61 to
61748a7
Compare
61748a7 to
36f359d
Compare
abhalsod-sonata
approved these changes
Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes LP-704: Studio bakes course-pinned absolute asset URLs into stored xblock content, breaking JSInput problems and images after a course re-run or export/import.
Jira Ticket
LP-704
Root cause
The authoring read/write round-trip is asymmetric:
get_block_inforunsreplace_static_urls, expanding portable/static/<name>references into absoluteasset-v1:<Org>+<Course>+<Run>+...URLs pinned to the current course (cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py)._save_xblockpersists the POSTeddataverbatim — nothing contracts those URLs back.The client editor is the only safety net, and not every editor path contracts every attribute (e.g. the course-authoring MFE misses JSInput's
html_file). Any save through a non-contracting path permanently bakes the current course's id into storage. After a re-run, those URLs still point at the original course: assets 404 for learners (or silently serve stale content from the old course while it happens to be public).The fix
contract_static_urls(text, course_id)incommon/djangoapps/static_replace/__init__.py— the reverse ofreplace_static_urlsfor a course's own assets. It contracts all forms back to/static/<name>:…+type@asset+block@<name>and…+type@asset+block/<name>https://host/asset-v1:…) and protocol-relative/assets/courseware/v1/<digest>/asset-v1:…)/c4x/<org>/<course>/asset/<name>_save_xblocknow runs stringdatathroughcontract_static_urlsbefore persisting, so course-pinned URLs are never stored regardless of which editor performed the save.rewrite_nonportable_content_links(store_utilities.py) was considered for reuse but never matches split-mongo course ids — its pattern is notre.escaped, so the+separators act as regex quantifiers. That is a separate pre-existing bug.Testing
common/djangoapps/static_replace/test/test_static_replace.pycovering all URL forms, query-string preservation, foreign-course/external URLs left untouched, and non-string passthrough.cms/djangoapps/contentstore/views/tests/test_block.py: saving data with a baked same-course URL stores/static/<name>; another course's asset URL is preserved verbatim.html_fileURL heals on the next save, and re-saving portable content no longer bakes it.Screenshots
Before
After the fix
Companion frontend fix: edx/frontend-app-authoring#106