Refactor exam review response handling to include structured items with optional per-part breakdown for multi-part answers
This commit is contained in:
@@ -35,14 +35,30 @@
|
|||||||
|
|
||||||
<div class="list-group mb-2">
|
<div class="list-group mb-2">
|
||||||
{# Render top-level answer breakdown from exam_response_counts and pcts #}
|
{# Render top-level answer breakdown from exam_response_counts and pcts #}
|
||||||
{% for key, count in exam_response_items %}
|
{% for item in exam_response_items %}
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<div class="d-flex w-100 justify-content-between align-items-center">
|
<div class="d-flex w-100 justify-content-between align-items-center">
|
||||||
<div>{{ key }}</div>
|
<div>
|
||||||
<div class="text-end"><strong>{{ count }}</strong> <small class="text-muted">{{ exam_response_pcts|get_item:key|default:0 }}%</small></div>
|
{% if item.parts %}
|
||||||
|
<div class="d-flex gap-1 align-items-center">
|
||||||
|
{% for part in item.parts %}
|
||||||
|
{% if part.is_correct == True %}
|
||||||
|
<span class="badge bg-success">{{ part.value|yesno:"T,F" }}</span>
|
||||||
|
{% elif part.is_correct == False %}
|
||||||
|
<span class="badge bg-danger">{{ part.value|yesno:"T,F" }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-light text-muted">{{ part.value|default:"-" }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div>{{ item.key }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="text-end"><strong>{{ item.count }}</strong> <small class="text-muted">{{ item.pct }}%</small></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress mt-2" style="height:12px;">
|
<div class="progress mt-2" style="height:12px;">
|
||||||
<div class="progress-bar bg-primary" role="progressbar" style="width: {{ exam_response_pcts|get_item:key|default:0 }}%;" aria-valuenow="{{ exam_response_pcts|get_item:key|default:0 }}" aria-valuemin="0" aria-valuemax="100"></div>
|
<div class="progress-bar bg-primary" role="progressbar" style="width: {{ item.pct }}%;" aria-valuenow="{{ item.pct }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
+41
-4
@@ -518,10 +518,47 @@ def exam_review_question_summary(request, pk: int, q_index: int):
|
|||||||
for k in counts.keys():
|
for k in counts.keys():
|
||||||
pcts[k] = 0.0
|
pcts[k] = 0.0
|
||||||
|
|
||||||
# Provide an explicit iterable of (key, count) pairs for templates to consume.
|
# Provide an explicit iterable of structured items for templates to consume.
|
||||||
# This avoids relying on attribute-resolution like `exam_response_counts.items`
|
# Each item includes the original key, count, pct and optional per-part
|
||||||
# in templates which can be ambiguous in Django's template variable lookup.
|
# breakdown (list of {value, is_correct}) for physics multi-part answers.
|
||||||
exam_response_items = sorted(counts.items(), key=lambda x: x[1], reverse=True)
|
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
|
# Compute percent correct using per-answer scoring
|
||||||
correct_count = None
|
correct_count = None
|
||||||
|
|||||||
Reference in New Issue
Block a user