From e078b5a98fefd561d010aca074d823e3891d7f58 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 10 Nov 2025 09:58:02 +0000 Subject: [PATCH] Refactor exam review templates and views to improve response summary display and handle unanswered responses --- .../generic/exam_review_question.html | 3 -- .../exam_review_question_fragment.html | 1 - ...exam_review_question_summary_fragment.html | 52 +++++++++++++++++-- sbas/views.py | 26 +++++++++- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/generic/templates/generic/exam_review_question.html b/generic/templates/generic/exam_review_question.html index c1bea05d..dc0d7c64 100644 --- a/generic/templates/generic/exam_review_question.html +++ b/generic/templates/generic/exam_review_question.html @@ -6,9 +6,6 @@
{# The #review-content element is replaced by HTMX when navigating between questions. #}
- {# The view selects an app-specific fragment if present and passes its name - in `fragment_template`. Include that fragment here so the full-page - render and the HTMX partials both use the same template. #} {% include fragment_template %}
diff --git a/sbas/templates/sbas/partials/exam_review_question_fragment.html b/sbas/templates/sbas/partials/exam_review_question_fragment.html index 56547b68..9c2a4301 100644 --- a/sbas/templates/sbas/partials/exam_review_question_fragment.html +++ b/sbas/templates/sbas/partials/exam_review_question_fragment.html @@ -1,6 +1,5 @@
-
SBAs
Question

{{ question|safe }}

diff --git a/sbas/templates/sbas/partials/exam_review_question_summary_fragment.html b/sbas/templates/sbas/partials/exam_review_question_summary_fragment.html index 31b783c8..58f8bbfa 100644 --- a/sbas/templates/sbas/partials/exam_review_question_summary_fragment.html +++ b/sbas/templates/sbas/partials/exam_review_question_summary_fragment.html @@ -19,7 +19,12 @@ {# Choice A #}
-
A
+
+
A
+ {% if exam_response_texts.a %} +
{{ exam_response_texts.a|safe }}
+ {% endif %} +
{{ exam_response_counts.a|default:0 }} {{ exam_response_pcts.a|default:0 }}% @@ -35,7 +40,12 @@ {# Choice B #}
-
B
+
+
B
+ {% if exam_response_texts.b %} +
{{ exam_response_texts.b|safe }}
+ {% endif %} +
{{ exam_response_counts.b|default:0 }} {{ exam_response_pcts.b|default:0 }}% @@ -51,7 +61,12 @@ {# Choice C #}
-
C
+
+
C
+ {% if exam_response_texts.c %} +
{{ exam_response_texts.c|safe }}
+ {% endif %} +
{{ exam_response_counts.c|default:0 }} {{ exam_response_pcts.c|default:0 }}% @@ -67,7 +82,12 @@ {# Choice D #}
-
D
+
+
D
+ {% if exam_response_texts.d %} +
{{ exam_response_texts.d|safe }}
+ {% endif %} +
{{ exam_response_counts.d|default:0 }} {{ exam_response_pcts.d|default:0 }}% @@ -83,7 +103,12 @@ {# Choice E #}
-
E
+
+
E
+ {% if exam_response_texts.e %} +
{{ exam_response_texts.e|safe }}
+ {% endif %} +
{{ exam_response_counts.e|default:0 }} {{ exam_response_pcts.e|default:0 }}% @@ -96,6 +121,23 @@
+ {# Unanswered / other responses (blank, None, or unexpected labels) #} + {% if exam_response_counts.unanswered|default:0 %} +
+
+
No answer / Other
+
+ {{ exam_response_counts.unanswered|default:0 }} + {{ exam_response_pcts.unanswered|default:0 }}% +
+
+
+
+
+
+ {% endif %} {% else %}
No responses recorded for this question in the exam yet.
{% endif %} diff --git a/sbas/views.py b/sbas/views.py index f4dab079..5b1d2e58 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -138,13 +138,24 @@ def exam_review_question_summary(request, pk: int, q_index: int): ua_qs = question.cid_user_answers.filter(exam=exam) total_responses = ua_qs.count() + # Track counts for the five canonical choices and aggregate any + # empty / unexpected answers into an 'unanswered' bucket so the + # template can show them explicitly and percentages use the same + # denominator (total_responses). counts = {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0} + other_count = 0 for ans in ua_qs.values_list("answer", flat=True): + # Normalize None/empty strings to be considered 'unanswered' + if not ans: + other_count += 1 + continue if ans in counts: counts[ans] += 1 else: - # Unexpected answer labels — store them as-is (rare) - counts[ans] = counts.get(ans, 0) + 1 + # Unexpected answer labels — treat as 'other/unanswered' + other_count += 1 + # Expose 'unanswered' so templates can report it + counts['unanswered'] = other_count pcts = {} if total_responses: @@ -161,6 +172,16 @@ def exam_review_question_summary(request, pk: int, q_index: int): if total_responses: correct_pct = round(100.0 * correct_count / total_responses, 1) + # Provide the actual answer text for each canonical choice so the + # summary template can display the full answer alongside the letter. + exam_response_texts = { + "a": (getattr(question, "a_answer", None) or "") , + "b": (getattr(question, "b_answer", None) or "") , + "c": (getattr(question, "c_answer", None) or "") , + "d": (getattr(question, "d_answer", None) or "") , + "e": (getattr(question, "e_answer", None) or "") , + } + context = { "exam": exam, "question": question, @@ -171,6 +192,7 @@ def exam_review_question_summary(request, pk: int, q_index: int): "exam_response_total": total_responses, "exam_response_correct_count": correct_count, "exam_response_correct_pct": correct_pct, + "exam_response_texts": exam_response_texts, } return render(request, "sbas/partials/exam_review_question_summary_fragment.html", context)