Source code for pykubegrader.widgets_base.multi_select
from typing import Callable, Tuple
import panel as pn
from ..telemetry import ensure_responses, update_responses
from ..utils import shuffle_questions
[docs]
class MultiSelectQuestion:
def __init__(
self,
title: str,
style: Callable[
[list[str], list[list[str]], list[bool]],
Tuple[list[pn.pane.HTML], list[pn.Column]],
],
question_number: int,
keys: list[str],
options: list[list[str]],
descriptions: list[str],
points: int,
):
responses = ensure_responses()
self.points = points
self.question_number = question_number
self.style = style
flat_index = 0
self.keys: list[str] = []
for i, _ in enumerate(keys):
for _ in options[i]:
flat_index += 1 # Start at 1
self.keys.append(f"q{question_number}_{flat_index}")
try:
seed: int = responses["seed"]
except ValueError:
raise ValueError(
"You must submit your student info before starting the exam"
)
# Dynamically assigning attributes based on keys, with default values from responses
for key in self.keys:
setattr(self, key, responses.get(key, False))
self.initial_vals = [getattr(self, key) for key in self.keys]
description_widgets, self.widgets = style(
descriptions, options, self.initial_vals
)
self.submit_button = pn.widgets.Button(name="Submit")
self.submit_button.on_click(self.submit)
widget_pairs = shuffle_questions(description_widgets, self.widgets, seed)
# Panel layout
question_header = pn.pane.HTML(
f"<h2>Question {self.question_number}: {title}</h2>"
)
question_body = pn.Column(
*[
pn.Row(desc_widget, checkbox_set)
for desc_widget, checkbox_set in widget_pairs
]
)
self.layout = pn.Column(question_header, question_body, self.submit_button)
[docs]
def submit(self, _) -> None:
responses_flat: list[bool] = []
self.responses_nested: list[list[bool]] = []
for row in self.widgets:
next_selections = []
for widget in row.objects:
# Skip HTML widgets
if isinstance(widget, pn.pane.HTML):
continue
if isinstance(widget, pn.widgets.Checkbox):
next_selections.append(widget.value)
responses_flat.append(widget.value) # For flat list of responses
# Append all responses for this widget at once, forming a list of lists
self.responses_nested.append(next_selections)
self.record_responses(responses_flat)
[docs]
def record_responses(self, responses_flat: list[bool]) -> None:
for key, value in zip(self.keys, responses_flat):
update_responses(key, value)
print("Responses recorded successfully")
[docs]
def show(self):
return self.layout