Summary
Running a full course reindex on the Teak instance raises a PluginError for the tab type wikimedia_progress_tab:
tutor k8s exec cms -- ./manage.py cms reindex_course --all
edx_django_utils.plugins.plugin_manager.PluginError:
No such plugin wikimedia_progress_tab for entry point openedx.course_tab
Root cause
The course data references a course tab whose plugin is no longer installed.
- The
wikimedia_progress_tab plugin was registered on the old develop (Maple) branch via the openedx.course_tab entry point.
- It is not present on
develop-teak, the branch Stage builds from. The plugin class and its wikimedia_features.progress_tab package were never carried forward.
- Courses authored on Maple still carry a tab of type
wikimedia_progress_tab in their stored tabs field. On Teak, CourseTab.from_json can't resolve the type, logs the error, and drops the tab from the in-memory list — but the stale entry remains in the modulestore.
It is a data-vs-code mismatch, not corruption.
Scope
Only one course is affected (found on Stage, running week-old production data):
course-v1:Wikimedia-Israel+WMIL_001+2024
Production may carry the tab on a different course, though this is unlikely. The diagnostic below scans by course id, so re-run it against any suspect course on prod.
Resolution
Strip the stale wikimedia_progress_tab entry from the affected course's tabs field via the modulestore API. The stock progress tab is already registered on Teak, so the course keeps a working Progress tab after the fix.
1. Open a CMS shell
tutor k8s exec cms -- ./manage.py cms shell
2. Diagnose (dry run, no writes)
Paste the block below with APPLY = False. It reads the raw stored tabs field (the deserialized course.tabs has already silently dropped the bad entry, so it must be read at the field-data layer).
exec('''
from xmodule.modulestore.django import modulestore
from opaque_keys.edx.keys import CourseKey
from django.contrib.auth import get_user_model
APPLY = False # flip to True for the real run
TAB = "wikimedia_progress_tab"
CID = "course-v1:Wikimedia-Israel+WMIL_001+2024"
store = modulestore()
key = CourseKey.from_string(CID)
course = store.get_course(key)
raw = course._field_data.get(course, "tabs")
before = list(raw)
kept = [t for t in before if not (isinstance(t, dict) and t.get("type") == TAB)]
removed = len(before) - len(kept)
print(f"course : {CID}")
print(f"tabs : {len(before)} -> {len(kept)} (removing {removed})")
print("removed types:", [t.get("type") for t in before if t not in kept])
print("kept types :", [t.get("type") for t in kept])
if APPLY and removed:
u = get_user_model().objects.filter(is_superuser=True).order_by("id").first()
assert u, "no superuser to attribute the edit to"
course._field_data.set(course, "tabs", kept)
store.update_item(course, u.id)
print(f"SAVED. removed {removed} tab(s) as {u.username}")
elif not APPLY:
print("DRY-RUN only. Set APPLY=True and re-run to persist.")
else:
print("nothing to remove.")
''')
Expected dry-run output:
course : course-v1:Wikimedia-Israel+WMIL_001+2024
tabs : 9 -> 8 (removing 1)
removed types: ['wikimedia_progress_tab']
kept types : ['course_info', 'courseware', 'textbooks', 'discussion', 'wiki', 'progress', 'static_tab', 'dates']
DRY-RUN only. Set APPLY=True and re-run to persist.
A single from_json traceback prints when get_course() deserializes the not-yet-fixed data. It is harmless log noise, not a failure — the script runs past it.
3. Apply
Confirm the dry-run output removes only wikimedia_progress_tab, then change one line:
Paste the block again. The final line should read SAVED. removed 1 tab(s) as <username>.
4. Verify
Reindex only the affected course:
tutor k8s exec cms -- ./manage.py cms reindex_course course-v1:Wikimedia-Israel+WMIL_001+2024
A clean run with no Unknown tab type line confirms the fix.
Notes
- Writes go through
store.update_item(...), not raw Mongo, so split versioning and caching stay consistent.
- If the verification reindex still logs the error after a successful
SAVED, the tab exists on both the published and draft branches and only one was fixed. A branch-aware variant is needed in that case.
Summary
Running a full course reindex on the Teak instance raises a
PluginErrorfor the tab typewikimedia_progress_tab:Root cause
The course data references a course tab whose plugin is no longer installed.
wikimedia_progress_tabplugin was registered on the olddevelop(Maple) branch via theopenedx.course_tabentry point.develop-teak, the branch Stage builds from. The plugin class and itswikimedia_features.progress_tabpackage were never carried forward.wikimedia_progress_tabin their storedtabsfield. On Teak,CourseTab.from_jsoncan't resolve the type, logs the error, and drops the tab from the in-memory list — but the stale entry remains in the modulestore.It is a data-vs-code mismatch, not corruption.
Scope
Only one course is affected (found on Stage, running week-old production data):
Resolution
Strip the stale
wikimedia_progress_tabentry from the affected course'stabsfield via the modulestore API. The stockprogresstab is already registered on Teak, so the course keeps a working Progress tab after the fix.1. Open a CMS shell
tutor k8s exec cms -- ./manage.py cms shell2. Diagnose (dry run, no writes)
Paste the block below with
APPLY = False. It reads the raw storedtabsfield (the deserializedcourse.tabshas already silently dropped the bad entry, so it must be read at the field-data layer).Expected dry-run output:
3. Apply
Confirm the dry-run output removes only
wikimedia_progress_tab, then change one line:Paste the block again. The final line should read
SAVED. removed 1 tab(s) as <username>.4. Verify
Reindex only the affected course:
tutor k8s exec cms -- ./manage.py cms reindex_course course-v1:Wikimedia-Israel+WMIL_001+2024A clean run with no
Unknown tab typeline confirms the fix.Notes
store.update_item(...), not raw Mongo, so split versioning and caching stay consistent.SAVED, the tab exists on both the published and draft branches and only one was fixed. A branch-aware variant is needed in that case.