Native QTI Support

Summary

I built a proof-of-concept XBlock that lets you use QTI directly in Open edX Platform. I believe that this is a valuable feature, and that the basic architecture I’ve laid out for this is workable. However, I do not have the time to actively develop and maintain this functionality. I am sharing this in the hopes of finding one or more interested maintainers who are enthusiastic to take this on.

Context

QTI is a widely supported 1EdTech standard for assessments. When people talk about assessments in the context of Common Cartridge, they’re talking about QTI. There are definitely rough edges when it comes to interoperability, but QTI is as close to a ubiquitous assessment format standard as we have in ed tech today.

Open edX platform’s ProblemBlock XML format was modeled after LON-CAPA, which never gained as much popularity as QTI. Up until now, our approach to QTI support has been to convert it into our native OLX format using the aptly named cc2olx tool. This works, but there are always going to be compromises because the models do not precisely align to one another. To my knowledge, we also don’t currently have a way to export things back out to QTI.

Approach

The code is in my personal pyqti repo. This repo has:

  • The implementation of a QTI 3.0 Python library (using xsdata to build models from the official QTI schema docs)
  • An XBlock installed as an extras package with pyqti[xblock]
  • A Tutor plugin that exists in a tutor_plugin directory.

The QtiAssessmentItemBlock works by having a wrapper <openedx-qti> tag that has a <qti-assessment-item> tag as its only child. OLX-specific attributes that our platform expects get put in the wrapper tag, while the <qti-assessment-item> can be directly copy-pasted in from existing QTI content.

The frontend rendering is entirely done by Citolab’s qti-components. The QtiAssessmentItemBlock’s primary job is to take the source QTI XML, redact the answers from it, pass the renderable version of that XML to qti-components for rendering, and know how to communicate with it to check answers.

Screenshots

How to Try it Out

I haven’t pushed any of this to PyPI, so you have to clone my pyqti repo. The Tutor Plugin instructions are in the README, but I’ll echo them here:

pip install -e tutor_plugin      # into the environment tutor runs from
tutor plugins enable pyqti
tutor mounts add /path/to/pyqti
tutor images build openedx-dev
tutor dev start -d

(I’ve only ever run this on tutor-dev.)

Note for those brave souls who run on bare metal: you need to install pyqti[xblock]. I’m trying to keep the XBlock layer cleanly separated, because the vast, vast majority of this code is XBlock-agnostic, and I think a general Python QTI library would be useful.

Sample Data

I made a small course that you can import to see a few problems and try things out:
qti-test-course.tar.gz (5.5 KB)

Status and Next Steps

This is very much a prototype. There’s a lot of LLM code generation that went into this, and my code reviews were mostly aimed at ensuring that it respected certain architectural boundaries, rather than being a close, line-by-line reading. The frontend code has some hacky bits around the CSRF token, and it bypasses bundling altogether by hitting an external CDN for the qti-components JS. There are many unimplemented features, including important things like being able to render images and custom stylesheets.

I do believe that the high level architecture will work, but it is definitely NOT something I’d put into production today. I wanted to prioritize getting a working proof-of-concept out for discussion.

If you or your organization is interested in pursuing this, please reply to this thread. I’d love to chat. :grin:

5 Likes