5.2. Question Interface¶
core.interfaces.QuestionInterface is an abstract base class and so,
it inherits from the metaclass
abc.ABCMeta. Don’t worry - you don’t
need to know what that means or why it’s used.
Python’s Abstract Base Classes (ABCs)
If you’re curious, check out:
Essentially, this allows the
QuestionInterface class to
act similar to an
Interface in Java - if the class’s methods
are not defined in a child class (i.e question), an error is raised when
trying to create an instance of that class.
QuestionInterface requires three methods to be defined
in any child question classes:
self.seed, a random instance of the question should be generated.
This method should return the ``seed`` and ``question data`` as a tuple
in the form of (`int`, `dict`).
The question data (a.k.a. instance data) and is a dictionary and contains all of the data ever required by the question - the prompt, answer, explanation, input data, etc. However, the format of that data (and the data itself) can be defined in whatever way you choose - as long as it is a Python dictionary and can be serialized into JSON.
|note:||The seed should act as a unique identifier for the random instance.
That is, reusing the same seed should result in the instance data.
To do so, call
clean_instance method should, as the name suggests, ‘clean’ the
instance data by removing any fields that the student should not see when
presented with the question prompt. For example, you want to remove
any answer and explanation fields, as the student should not be able to
see the answer before answering the question - that would lead to quite
The utility function
utils.misc.copy_dict_specification can be used to
only keep the fields required. The method should return the cleaned
instance, which is a `dict`.
This is the most complex method because it takes numerous arguments and has numerous return values. The primary focus of the method is to grade the student’s answer and prepare the explanation to be shown to the student.
The method also implicitly consumes and produces state - this allows for iterated feedback questions, where the student submits parts of an answer and receives feedback on those before finishing the submission.
However, questions can also just end the submission on the first attempt by the user - this is the default behavior and recommended for simpler questions. Once you have more experience with question design, you will be create the ‘iterated feedback’ questions with ease.
|warning:||The grade returned by this method is not a raw grade. Instead, it is a floating-point value, between 0 and 1, of how many points out of the maximum possible should be awarded to the student. The value returned is multiplied to the maximum possible score for the question, which is chosen by the course administrator. Make sure that your implementation does not return a value greater than 1.|
This method has a complex signature - it accepts the question instance, the user’s answer, and the current number of submissions. If working on a question that does not have iterated feedback, you can ignore that last parameter. The method should return the response data, updated instance, grade, and state - this is a tuple of (`dict`, `dict`, `float`, `int`).
5.2.4. Next Steps¶
Now that we have an overview of the methods we need to implement in the interface for a question, we can start designing our own, right? It’ll be confusing to stump all the COS 226 students, but elegant to allow those same students to redesign the question in the future. A true wonder shall arise before our very eyes!
Not so fast. Let’s walk through a few examples first, with increasing levels of abstraction (remember - abstraction encourages reusability and modularity, which is the primary goal of the question module infrastructure) to get a feel for how the implementation actually looks in practice.
5.3. Question Interface Internals¶
There are also numerous methods you can optionally override. However, sane default implementations already exist, so only override them if you know what you’re doing. Feel free to ignore this section if you just want to get started in creating a question - 99.9% of questions will never need to think about these methods.
Get the name of the template to render for the question.
If you just want a different template name, override the
Get the name of the template to render for the answer view.
If you just want a different template name, override the
|note:||If using React, which is recommended, it is much cleaner to simply have one template. As such, this method won’t be called unless necessary.|
Get an answer for the queston instance. Effectively, this is used in tests and more importantly, when determining various properties of answers (such as length for answers with a maximum/fixed length).
By default, this attempts to get the
answers from the instance and return
the first item. If
answers does not exist in the instance, the
is tried. This should be overridden if special instance formats are used.
Compare two instances and return if they are equal. For most purposes, this
a == b. This should be overridden if a special instance is
defined where equality is measured differently.
Generate a random integer seed to identify the question instance. Currently, this generates a random integer between 1 and 1 million (1e6).
There’s really no need to override this. In fact, you should never touch it.
This is really a property which returns a static value. It is used internally
by various classes (
core.cache.CacheManager, for example) that deal with the underlying question
|warning:||You will most likely break question instance generation/rendering if you override this property. For all things good, pretend it doesn’t exist.|