Skip to content

feat: round-trip QTI AssessmentItem raw_data through save and sync#6028

Merged
rtibbles merged 3 commits into
learningequality:unstablefrom
rtibblesbot:issue-5999-a58df5
Jul 3, 2026
Merged

feat: round-trip QTI AssessmentItem raw_data through save and sync#6028
rtibbles merged 3 commits into
learningequality:unstablefrom
rtibblesbot:issue-5999-a58df5

Conversation

@rtibblesbot

@rtibblesbot rtibblesbot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends AssessmentItem to store and round-trip an editor-authored QTI item's full XML in raw_data (type='QTI'), rather than the structured question/answers/hints fields legacy types use.

  • Adds QTI as a valid AssessmentItem.type
  • Persists raw_data verbatim (no whitespace trimming) and skips the legacy answers/hints JSON-shape coercion for QTI items
  • Validates raw_data against the QTI 3.0 schema during sync save, rejecting invalid XML per-item through the existing sync error channel
  • Extracts src/srcset/href/data checksum references from the QTI XML and wires them into the existing set_files claim/create/delete flow (mirrors the markdown-scan path legacy types already use)
  • Rejects edits that mix the two field sets: a QTI item cannot edit question/answers/hints, and a non-QTI item cannot edit raw_data

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 within is_valid() (not create()/update()) specifically so a QTI schema error attributes to one item in the sync error channel instead of tripping the batch's broad except Exception — worth confirming that reasoning holds
  • get_filenames_from_qti_item parses raw_data with the QTI validator's existing secure_parser() (XXE/entity-expansion hardened) — reused rather than re-implemented
  • validate() now also rejects cross-type field edits (QTI item touching question/answers/hints, or non-QTI item touching raw_data), per review feedback asking to close that gap
  • test_qti_assessmentitem_full_round_trip covers byte-for-byte round-trip on create/update plus file extraction/removal
  • Full backend suite passes (no regressions)
  • 3 pre-existing FileThumbnailTestCase failures on unstable are unrelated to this change (reproduced against unmodified unstable)

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_data with leading whitespace, which placed content before the XML declaration and is itself invalid XML. Fixed the test to pad trailing whitespace only, which still exercises trim_whitespace=False without producing invalid input.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?
  • Ran pre-flight CI checks (lint, format, tests) and verified all pass
  • Rebased onto the target branch and resolved any conflicts
  • Reorganized commit history into clean, logical commits
  • Audited the diff to ensure only issue-relevant files are changed
  • Built PR body from the repository's PR template with evidence blocks

Status: 🟡 Waiting for feedback · updated 2026-07-03 16:16 UTC

@rtibblesbot rtibblesbot marked this pull request as ready for review July 3, 2026 06:28

@rtibbles rtibbles left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to do the QTI constant properly - upgrade to le-utils 0.2.18.

Comment thread contentcuration/contentcuration/models.py Outdated
Comment thread contentcuration/contentcuration/viewsets/assessmentitem.py Outdated
Comment thread contentcuration/contentcuration/viewsets/assessmentitem.py Outdated
Comment thread contentcuration/contentcuration/viewsets/assessmentitem.py Outdated
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

@rtibbles rtibbles left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good - just one final thought about strengthening the validation - assessments have two paths, the QTI + raw_data path, or the other.

Comment thread contentcuration/contentcuration/viewsets/assessmentitem.py
QTI items must only edit raw_data; non-QTI items must not edit
raw_data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@rtibbles rtibbles left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent!

@rtibbles rtibbles merged commit 817284d into learningequality:unstable Jul 3, 2026
13 checks passed
@rtibblesbot rtibblesbot deleted the issue-5999-a58df5 branch July 3, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[QTI] Allow API endpoint to handle AssessmentItem.raw_data to store QTI data

2 participants