Skip to content

[DEPR]: static_asset_path XBlock setting #39236

Open

Description

@kdmccormick

Draft. Written by Claude; to be edited by @kdmccormick before being submitted for RFC.
Dates below are placeholders.

We believe that this feature is effectively un-useable. There is a 4-week comment period to collect feedback on whether anyone is using it. If we confirm the feature is useless, then we will remove immediately. If the feature turns out to have usage, we will allow the standard 1-release-cycle grace period before removing.

RFC Start Date

2026-10-21

Target Plan Accepted Date

2026-11-21

Target Transition Unblocked Date

2026-11-21

Earliest Breaking Changes Unblocked Date

2026-11-21

Earliest Open edX Named Release with Breaking Changes

Willow - 2027-01

Rationale

static_asset_path changes how /static/ references resolve. When it is set, /static/foo.png resolves against Django staticfiles (STATIC_ROOT) instead of the contentstore, and the contentstore is never consulted.

It is defined on InheritanceMixin, so it exists on every block and is inherited down the hierarchy. In practice it is set at the course level and applies to the whole course, but a per-block override is possible via OLX import or direct modulestore writes.

It was built for the era when courses were loaded from disk by XMLModuleStore. That store is no longer in the default MODULESTORE configuration, so on a modern instance there is no supported way to put files where this setting points. Uploading through Files & Uploads writes to the contentstore, which this code path does not read. Placing files in STATIC_ROOT requires operator filesystem access or a custom image build, and the result would be world-readable, globally namespaced, and invisible to the Files page.

Meanwhile the setting is editable by any course author in Studio → Advanced Settings. It is not in FIELDS_EXCLUDE_LIST and no feature flag hides it. Setting it silently breaks every /static/ reference in the course, along with course_image and banner_image, while the Files page continues to list the now-unreachable assets. There is no error; the final except branch returns a path that 404s.

It is also threaded as a parameter through the block rendering path, which adds maintenance cost to code that is otherwise being actively reworked.

Removal

The field itself:

The branches that honor it:

The parameter plumbing:

One behavior change outside the field itself:

  • xmodule/modulestore/xml_importer.py:425-429 sets static_asset_path = data_dir when a course is imported with do_import_static=False (the --nostatic flag on ./manage.py cms import). This assignment should be dropped. Whether --nostatic itself should stay is a separate question — it has a second effect (skipping the static import) that is independent of this setting.

Replacement

None. Course static assets are served from the contentstore today, and are being migrated to openedx_content under the ADRs in openedx-core#822. Nothing in that plan depends on static_asset_path, and removing it eliminates an asset-resolution mode those ADRs do not account for.

Courses that genuinely need to reference externally hosted files can use absolute URLs.

Deprecation

Before removal:

  1. Add static_asset_path to FIELDS_EXCLUDE_LIST in cms/djangoapps/models/settings/course_metadata.py so it stops appearing in Studio Advanced Settings. This is a small change worth making on its own merits, independent of the DEPR outcome.
  2. Log a warning when a course is rendered with a non-empty static_asset_path.

Additional Info

No database migration is required — the value lives in the modulestore course block, and an unread field is inert.

If usage turns out to be zero, the 6-month default before breaking changes is worth shortening so this can land in Xylon (2027-04) rather than Y (2027-10).

The field is read inconsistently today: the XBlock rendering path reads the block's value (static_replace/services.py:57, xblock_utils/__init__.py:299) while course images and textbooks read the course's. Inheritance makes these agree unless the field is overridden on a sub-block, in which case /static/ rewriting and course-image resolution diverge. Removal resolves this.

Operators: please check whether any of your courses have this set, and report back on the Discourse thread. Courses with a non-empty static_asset_path are very likely already broken, so the results are worth looking at regardless of this proposal.

# ./manage.py lms shell
from xmodule.modulestore.django import modulestore
for c in modulestore().get_course_summaries():
    course = modulestore().get_course(c.id)
    if getattr(course, 'static_asset_path', ''):
        print(c.id, repr(course.static_asset_path))

Note that a course can have this set without anyone choosing it, via the --nostatic import path described above. The query above checks course blocks only; a per-block override would not show up, but there is no UI that produces one.

Transition Unblocked Tasks

  • Add static_asset_path to FIELDS_EXCLUDE_LIST
  • Add a warning log for courses with a non-empty value
  • Collect usage reports from operators on the Discourse thread

Feature Removal Tasks

  • Remove the field from InheritanceMixin
  • Remove the filesystem branch from replace_static_urls and the static_asset_path parameter from its signature
  • Remove the parameter from static_replace/services.py
  • Remove the branch from course_image_url
  • Remove usage in xblock_utils, staticbook/views.py
  • Remove the parameter plumbing from block_render.py, courseware/courses.py, courseware/views/views.py
  • Drop the static_asset_path assignment in xml_importer.py
  • Update tests that set static_asset_path (lms/djangoapps/staticbook/tests.py, lms/djangoapps/mobile_api/testutils.py)

Metadata

Metadata

Assignees

No one assigned

    Labels

    deprProposal for deprecation & removal per OEP-21

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions