5.2. Question Interface

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

The QuestionInterface requires three methods to be defined in any child question classes:

5.2.1. generate_instance

Using 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 random.seed before using the random module in the method.
see:core.interfaces.QuestionInterface.generate_instance

5.2.2. clean_instance

The 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 mess!

note:When the finished parameter is passed to the method, all data can be shown. When finished is True, this means that the student’s answer has already been submitted and so the answer and explanation (and other ‘confidential’ fields) can now be passed.

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

see:core.interfaces.QuestionInterface.clean_instance

5.2.3. validate_answer

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`).

see:core.interfaces.QuestionInterface.validate_answer

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.

5.3.1. get_question_template

Get the name of the template to render for the question.

If you just want a different template name, override the core.interfaces.QuestionInterface.question_template attribute.

5.3.2. get_answer_template

Get the name of the template to render for the answer view.

If you just want a different template name, override the core.interfaces.QuestionInterface.answer_template attribute.

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.

5.3.3. get_answer

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 answer field is tried. This should be overridden if special instance formats are used.

5.3.4. instances_equal

Compare two instances and return if they are equal. For most purposes, this just returns a == b. This should be overridden if a special instance is defined where equality is measured differently.

5.3.5. generate_seed

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.

5.3.6. provider

This is really a property which returns a static value. It is used internally by various classes (core.manager.QuestionManager and core.cache.CacheManager, for example) that deal with the underlying question classes.

warning:You will most likely break question instance generation/rendering if you override this property. For all things good, pretend it doesn’t exist.