feat: land ricecooker assessment items as QTI#6032
Conversation
Refactor create_exercises to delegate item construction and schema validation to AssessmentItemSerializer instead of duplicating that logic. Native QTI (editor-shaped or arbitrary) is persisted and schema-validated directly; legacy/structured questions are converted to QTI on ingest; raw Perseus passes through unchanged; a Perseus custom interaction is unwrapped back to its raw perseus_question by reading its data-perseus-path payload. Referenced media is mapped via the existing map_files_to_assessment_item helper. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rtibbles
left a comment
There was a problem hiding this comment.
This is broadly looking sensible - I think the spec was overly generous, and we should tighten to just rejecting Perseus-in-QTI questions as an error, not trying to handle it.
Also, we should do a better attempt to reuse existing file objects, rather than do risky time based watermarking.
|
|
||
| if question.get("type") == exercises.QTI: | ||
| raw_data = question.get("raw_data") | ||
| perseus_path = find_perseus_custom_interaction_path(raw_data) |
There was a problem hiding this comment.
Actually - I am thinking rather than trying to handle this, we should just reject it outright. Uploading this in this way is a categorical mistake. Ricecooker should be preventing this on its side.
There was a problem hiding this comment.
Agreed, done — QTI items wrapping a Perseus custom interaction are now rejected outright (raises a ValueError → NodeValidationError) rather than unwrapped. See 57f555e.
| ) | ||
| ) | ||
|
|
||
| # AssessmentItemSerializer.set_files() auto-creates a File row per checksum |
There was a problem hiding this comment.
This feels like a lot of extra bookkeeping - also, why wouldn't ricecooker create one? It would have to have done so in order to upload the file in the first place for it to exist in storage to be referenced here.
We should, wherever possible, defer to the file object created during the file_upload_url request. If the "auto extract" logic is failing at this, we need to improve that too, rather than having an essay of text explaining why we need to create three file objects then clean up 2 of them.
There was a problem hiding this comment.
Done — dropped the create-then-cleanup dance. AssessmentItemSerializer.set_files() already reuses the pre-staged File row from ricecooker's file_upload_url request for a referenced checksum (that's exactly the reuse path it was built for), so serializer.save() now just attaches it directly. If a checksum has no matching upload, that surfaces as a storage-layer error (OSError), which we catch and turn into a NodeValidationError instead of a 500. Test now asserts the attached file is the pre-staged one (same id/original_filename), not a newly-created duplicate. See 57f555e.
…ed File Per review: uploading a QTI item that wraps a Perseus custom interaction is a categorical mistake ricecooker should prevent on its side, so reject it instead of unwrapping it. Also drop the create-three-files-then-clean- up-two watermarking dance for QTI media - AssessmentItemSerializer.set_files() already reuses the File row ricecooker's file_upload_url request created, so just let it, and surface any storage-layer lookup failure as a NodeValidationError. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…I XML builder Merge the two try/except blocks around serializer validation and save into one, and factor the custom-interaction test fixture to reuse _item_xml instead of duplicating the QTI item XML template. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rtibbles
left a comment
There was a problem hiding this comment.
All changes look correct - tests give confidence, and the tighter scoping reduces the potential for weirdness in here.
Summary
Ricecooker's internal ingest endpoint built
AssessmentItems directly and validated onlyassessment_iduniqueness, so malformed QTI, unconverted legacy question types, and Perseus content wrapped for republishing could all land in Studio unchecked.create_exercisesnow delegates item construction and schema validation toAssessmentItemSerializer: native QTI is validated and persisted as-is, legacy/structured questions are converted to schema-valid QTI on ingest, and raw Perseus passes through unchanged. A QTI item wrapping a Perseus custom interaction is rejected outright as a categorical upload mistake, rather than unwrapped. Referenced media is mapped for every resulting item, reusing theFilerow ricecooker'sfile_upload_urlrequest already created instead of any extra bookkeeping.References
Fixes #6002.
Reviewer guidance
views/internal.py:963-978— DRF'sValidationError(bad QTI/legacy data) andOSError(a referenced checksum was never uploaded) are both caught and re-raised asNodeValidationError; confirm the wrapped message still gives ricecooker callers an actionable error in both cases.views/internal.py:996-1005— a QTI item wrapping a Perseus custom interaction is now rejected outright rather than unwrapped, per review feedback that this is a categorical ricecooker-side mistake to prevent, not handle.viewsets/assessmentitem.py:147— widened theraw_datavalidation to also acceptPERSEUS_QUESTION, not justQTI. Confirm this can't be used to smuggleraw_dataedits into aPERSEUS_QUESTIONitem through the browser-editor sync API in a way that wasn't intended.AI usage
Used Claude Code to implement the refactor from an existing task plan and write the accompanying tests. Verified with the full affected test suite and the repo's pre-commit hooks.
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
🟡 Waiting for feedback
Last updated: 2026-07-08 18:53 UTC