Skip to content

[QTI] Restoring a published channel loses or breaks its QTI exercises #6211

Description

@rtibblesbot

❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Overview

restore_channel rebuilds an exercise's questions only from its Perseus archive. An exercise published as a QTI package alone restores with no questions. A dual-published exercise restores with its QTI item identifiers as assessment_ids, and publishing the restored channel then fails on them.

Complexity: Medium
Target branch: unstable

Context

  • Restore reads only preset='exercise' files:
    sql_command = (
    "SELECT checksum, extension "
    "preset FROM {table} WHERE contentnode_id='{id}' AND preset='exercise';".format(
    table=FILE_TABLE, id=contentnode.node_id
    )
    )
  • Restored assessment_ids are the ids exercise.json lists:
    for index, assessment_id in enumerate(data["all_assessment_items"]):
    with open("{}.json".format(assessment_id), "rb") as fobj:
    assessment_item = generate_assessment_item(
    assessment_id,
    index,
    data["assessment_mapping"][assessment_id],
    json.load(fobj),
    download_url=download_url,
    )
  • Publishing decodes every legacy item's assessment_id as hex:
    def hex_to_qti_id(hex_string):
    """
    Encode a 32 digit hex to a 22 character base64 encoded id and a K prefix.
    """
    bytes_data = bytes.fromhex(hex_string)
    return f"K{base64.urlsafe_b64encode(bytes_data).decode('ascii').rstrip('=')}"
  • On 1beec6ecb, a dual-published node with one QTI editor choice question and one legacy input_question, restored from its Perseus archive, then published:
    restored assessment_ids: item_1, <32-char hex>
    ValueError: non-hexadecimal number found in fromhex() arg at position 0
    

The Change

  • Restore should recreate the questions of every published exercise, whichever archives it was published with.

How to Get There

  • Publish a channel holding an exercise with one QTI editor question and one legacy question.
  • Run manage.py restore_channel <channel_id> --download-url <studio url>.
  • Publish the restored channel.

Acceptance Criteria

  • An exercise published as a QTI package only restores with all its questions.
  • An exercise mixing QTI editor and legacy questions restores with all its questions.
  • Publishing a channel restored from either case succeeds.
  • import_tools tests cover both cases.

AI usage

Claude Code found this during an automated self-review of the #6205 branch, reproduced the publish failure with a scratch test, and drafted this issue.

Activity

  1. added theissue type on Sep 30, 2026
  2. self-assigned this
    on Sep 30, 2026
  3. rtibblesbot commented on Sep 30, 2026

    @rtibblesbot
    ContributorAuthor

    Plan: restore_channel recreates every question of an exercise published with a QTI package, alone or alongside a Perseus archive, and the restored channel publishes.

    • Read item resources from the QTI manifest
    • Restore QTI-package items, preferring the package
    • /simplify pass (Phase 1)
    • Restore Perseus custom-interaction items as perseus_question
    • /simplify pass and branch self-review

    Assumptions — the issue did not settle these; say so if one is wrong.

    • Graphie files in a Perseus question are not re-attached; wrong → a republished graphie question loses its graphie asset (today's Perseus restore attaches none either).
    • ASSESSMENT_TEST manifest resources are ignored; wrong → items from externally authored packages with tests would be dropped (Studio never emits them).
    • Perseus images restore with EXERCISE_IMAGE preset; wrong → republish omits them from perseus/images/ (base.py:153-155 filters on that preset).

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

    How was this generated?
    • Implemented code following the pre-approved plan using test-driven development (red-green-refactor)
    • Ran the project's full test suite and linter before committing
    • For UI changes: captured live screenshots to verify rendering
    • For backend changes: verified with example API/CLI output
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions