feat: round-trip QTI AssessmentItem raw_data through save and sync#6028
feat: round-trip QTI AssessmentItem raw_data through save and sync#6028rtibblesbot wants to merge 2 commits into
Conversation
rtibbles
left a comment
There was a problem hiding this comment.
Need to do the QTI constant properly - upgrade to le-utils 0.2.18.
| max_length=50, | ||
| choices=exercises.question_choices + (("true_false", "True/False"),), | ||
| choices=exercises.question_choices | ||
| + (("true_false", "True/False"), ("QTI", "QTI")), |
There was a problem hiding this comment.
This should be added by upgrading le-utils, not adding it manually.
There was a problem hiding this comment.
Fixed — bumped le-utils to 0.2.18 (ships exercises.QTI) and removed the manual "QTI" tuple entry.
| filenames = get_filenames_from_assessment(aitem) | ||
| filenames = ( | ||
| get_filenames_from_qti_item(aitem) | ||
| if aitem.type == "QTI" |
There was a problem hiding this comment.
This should be referencing the le utils constant, not hard coded.
There was a problem hiding this comment.
Fixed — now references exercises.QTI.
| @@ -135,14 +198,17 @@ def set_files(self, all_objects, all_validated_data=None): # noqa C901 | |||
| md_fields_modified = { | |||
There was a problem hiding this comment.
This variable name is now misleading "md_fields_modified" - should be changed to "editable_text_fields_modified"
There was a problem hiding this comment.
Fixed — renamed to editable_text_fields_modified.
| # except Exception in create_from_changes/update_from_changes and | ||
| # reported as "Internal server error" for the whole batch. | ||
| data = super(AssessmentItemSerializer, self).validate(data) | ||
| if self._item_type == "QTI": |
There was a problem hiding this comment.
Fixed — now references exercises.QTI.
Ships the QTI question type constant so AssessmentItem code can reference exercises.QTI instead of a hardcoded string.
Store the editor's authored QTI XML on AssessmentItem (type='QTI', full item XML in raw_data) and round-trip it byte-for-byte through the save and sync API paths. - Persist raw_data verbatim, bypassing legacy answers/hints JSON coercion for QTI items - Validate raw_data against the QTI 3.0 schema on save, rejecting invalid items through the sync error channel - Extract src/srcset/href/data file references from QTI XML into the existing File claim/create/delete flow Fixes learningequality#5999
caa1817 to
397f1f7
Compare
Summary
Extends
AssessmentItemto store and round-trip an editor-authored QTI item's full XML inraw_data(type='QTI'), rather than the structuredquestion/answers/hintsfields legacy types use.QTIas a validAssessmentItem.typeraw_dataverbatim (no whitespace trimming) and skips the legacyanswers/hintsJSON-shape coercion for QTI itemsraw_dataagainst the QTI 3.0 schema during sync save, rejecting invalid XML per-item through the existing sync error channelsrc/srcset/href/datachecksum references from the QTI XML and wires them into the existingset_filesclaim/create/delete flow (mirrors the markdown-scan path legacy types already use)Scoped to the sync API save path only, per the issue — ricecooker/internal API and QTI export/publish are unaffected.
References
Fixes #5999
Related: #5970 (QTI editor), #4877 (existing type validation)
Reviewer guidance
contentcuration/contentcuration/viewsets/assessmentitem.py:validate()raises from withinis_valid()(notcreate()/update()) specifically so a QTI schema error attributes to one item in the sync error channel instead of tripping the batch's broadexcept Exception— worth confirming that reasoning holdsget_filenames_from_qti_itemparsesraw_datawith the QTI validator's existingsecure_parser()(XXE/entity-expansion hardened) — reused rather than re-implementedtest_qti_assessmentitem_full_round_tripcovers byte-for-byte round-trip on create/update plus file extraction/removalFileThumbnailTestCasefailures onunstableare unrelated to this change (reproduced against unmodifiedunstable)AI usage
Implemented with Claude Code from a maintainer-approved plan, using TDD per task. Wiring up schema validation broke an earlier whitespace round-trip test — the test padded
raw_datawith leading whitespace, which placed content before the XML declaration and is itself invalid XML. Fixed the test to pad trailing whitespace only, which still exercisestrim_whitespace=Falsewithout producing invalid input.@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Status: 🟡 Waiting for feedback · updated 2026-07-03 08:11 UTC