From bd71c669f07aa602715f77966875807123129f75 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 10 Nov 2025 13:08:24 +0000 Subject: [PATCH] Refactor exam review response handling to include structured items with optional per-part breakdown for multi-part answers --- ...exam_review_question_summary_fragment.html | 24 ++++++++-- physics/views.py | 45 +++++++++++++++++-- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/physics/templates/physics/partials/exam_review_question_summary_fragment.html b/physics/templates/physics/partials/exam_review_question_summary_fragment.html index c1b5d854..1740f70c 100644 --- a/physics/templates/physics/partials/exam_review_question_summary_fragment.html +++ b/physics/templates/physics/partials/exam_review_question_summary_fragment.html @@ -35,14 +35,30 @@
{# Render top-level answer breakdown from exam_response_counts and pcts #} - {% for key, count in exam_response_items %} + {% for item in exam_response_items %}
-
{{ key }}
-
{{ count }} {{ exam_response_pcts|get_item:key|default:0 }}%
+
+ {% if item.parts %} +
+ {% for part in item.parts %} + {% if part.is_correct == True %} + {{ part.value|yesno:"T,F" }} + {% elif part.is_correct == False %} + {{ part.value|yesno:"T,F" }} + {% else %} + {{ part.value|default:"-" }} + {% endif %} + {% endfor %} +
+ {% else %} +
{{ item.key }}
+ {% endif %} +
+
{{ item.count }} {{ item.pct }}%
-
+
{% endfor %} diff --git a/physics/views.py b/physics/views.py index 65b3679f..7e4c3085 100644 --- a/physics/views.py +++ b/physics/views.py @@ -518,10 +518,47 @@ def exam_review_question_summary(request, pk: int, q_index: int): for k in counts.keys(): pcts[k] = 0.0 - # Provide an explicit iterable of (key, count) pairs for templates to consume. - # This avoids relying on attribute-resolution like `exam_response_counts.items` - # in templates which can be ambiguous in Django's template variable lookup. - exam_response_items = sorted(counts.items(), key=lambda x: x[1], reverse=True) + # Provide an explicit iterable of structured items for templates to consume. + # Each item includes the original key, count, pct and optional per-part + # breakdown (list of {value, is_correct}) for physics multi-part answers. + exam_response_items = [] + parts_names = ["a", "b", "c", "d", "e"] + for k, v in sorted(counts.items(), key=lambda x: x[1], reverse=True): + item = {"key": k, "count": v, "pct": pcts.get(k, 0.0), "parts": None} + # Attempt to parse comma-separated boolean-like keys into parts + try: + if isinstance(k, str) and "," in k: + parts_raw = [p.strip() for p in k.split(",")] + if len(parts_raw) == 5: + parts_details = [] + for idx, sval in enumerate(parts_raw): + sval_l = sval.lower() + if sval_l in ("true", "t", "1", "yes"): + val = True + elif sval_l in ("false", "f", "0", "no"): + val = False + elif sval_l in ("none", "", "not answered", "not_answered"): + val = None + else: + # leave as string fallback + val = sval + + # determine correctness where possible + is_correct = None + try: + correct_val = getattr(question, f"{parts_names[idx]}_answer") + if isinstance(val, bool) and isinstance(correct_val, bool): + is_correct = val == correct_val + except Exception: + is_correct = None + + parts_details.append({"value": val, "is_correct": is_correct}) + + item["parts"] = parts_details + except Exception: + item["parts"] = None + + exam_response_items.append(item) # Compute percent correct using per-answer scoring correct_count = None