Skip to content

Restore QTI exercises when restoring a channel - #6243

Merged
rtibbles merged 4 commits into
learningequality:unstablefrom
rtibblesbot:issue-6211-939039
Oct 7, 2026
Merged

rtibbles merged 4 commits into
learningequality:unstablefrom
rtibblesbot:issue-6211-939039

Conversation

@rtibblesbot

@rtibblesbot rtibblesbot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

restore_channel rebuilt questions only from an exercise's Perseus archive. QTI-only exercises restored with no questions; dual-published ones restored QTI item ids as assessment_ids, which broke publishing.

References

Fixes #6211

Reviewer guidance

  • When a node has a qti_zip file, questions restore from it and the exercise archive is ignored. Nodes with only an exercise archive restore as before.
  • Package items restore as type=qti with XML in raw_data; item images are attached as EXERCISE_IMAGE files.
  • A converted legacy item gets back the assessment_id its K… identifier encodes; a QTI editor item, whose id is in neither archive, gets a fresh one.
  • Items wrapping a Perseus custom interaction restore as perseus_question, keyed by the hex in perseus/<hex>.json.
  • Item order follows the manifest across both kinds (get_assessment_item_resources_from_manifest in imsmanifest.py).
  • Graphie svg + data json in packaged Perseus questions are rejoined into EXERCISE_GRAPHIE files.
  • An unreadable or unreachable package restores no items, with a warning; it does not fall back to the exercise archive, whose QTI ids cannot republish.

AI usage

Implemented by Claude Code following a pre-approved plan with TDD. Python tests ran in CI on fork verify branches (the agent's sandbox blocks Postgres): full suite passes on the branch and on its second commit alone.


@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
@rtibblesbot

🟡 Waiting for feedback

Last updated: 2026-10-06 04:46 UTC

@rtibblesbot
rtibblesbot marked this pull request as ready for review September 30, 2026 23:56
@rtibblesbot
rtibblesbot requested review from a team and rtibbles September 30, 2026 23:56
@rtibblesbot

Copy link
Copy Markdown
Contributor Author

Outside this PR's scope — tracked under #5998:


@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

Co-Authored-By: Claude Sonnet 5.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.

More precise testing assertions would help here, but ultimately, not restoring in a broken way is key.

Comment thread contentcuration/contentcuration/utils/import_tools.py
Comment thread contentcuration/contentcuration/tests/test_restore_channel.py
@rtibbles rtibbles self-assigned this Oct 2, 2026

@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.

I am more confident that the code is actually doing what is intended in terms of setting the appropriate assessment_id - but the tests are too weak. If there are exceptions that need to be handled, that is fine, but we shouldn't weaken the assertions in the cases where the ids should be exactly replicated.

self.assertEqual(self._file_rows(item), self._file_rows(source_item))
if source_item.type == exercises.QTI:
# An editor item's identifier is not its assessment_id.
self.assertRegex(item.assessment_id, "^[0-9a-f]{32}$")

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.

This assertion is too weak - it only asserts that the restored assessment_id is a valid 32 digit hex - not that it restored the original assessment id.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in bd281a4: an editor item whose identifier encodes its assessment_id (hex_to_qti_id) now must restore that exact id. Both node tests (QTI-only and dual-published) gained such an item.

Only an item named by an editor slug (e.g. item_1) keeps the regex check. Neither the package nor Kolibri's assessment_item_ids records its source assessment_id, so restore has to mint a new one.

Searched test_restore_channel.py for format-only assessment_id assertions and found 2:

  • _assert_restores_source_rows: changed as above.
  • test_editor_item_with_non_canonical_id_gets_a_new_assessment_id: left as is. Its identifier encodes no id, so there is no source id to compare against.

rtibblesbot and others added 3 commits October 6, 2026 04:23
Exercises with a qti_zip restore every manifest item as type=qti with its
images. A dual node's exercise archive holds QTI ids, so it is not a
fallback: an unusable package restores no items. A converted legacy item
gets back the assessment_id its identifier encodes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Packaged Perseus custom interactions restore with their hex assessment_id,
images and graphies instead of landing as qti items.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixed fixture ids whose QTI encodings hold "-" and "_"; expected ids stated per test
- Publish a restored legacy-only node through create_content_database

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rtibblesbot

Copy link
Copy Markdown
Contributor Author

Outside this PR's scope — tracked in:


Written by rtibblesbot, an LLM-based coding agent.

@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.

This now appears to properly encode and decode the assessment ids - this is good to go.

@rtibbles
rtibbles merged commit 51657a6 into learningequality:unstable Oct 7, 2026
13 checks passed
@rtibblesbot

Copy link
Copy Markdown
Contributor Author

Quick check on rtibblesbot's work on this PR: react 👍 helpful · 😕 mixed · 👎 not helpful. Reply with anything specific.

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] Restoring a published channel loses or breaks its QTI exercises

2 participants