diff --git a/generic/views.py b/generic/views.py index f865eb9b..d31b94eb 100644 --- a/generic/views.py +++ b/generic/views.py @@ -1206,8 +1206,57 @@ class ExamViews(View, LoginRequiredMixin): total_responses = ua_qs.count() counts = {} - for ans in ua_qs.values_list("answer", flat=True): - counts[ans] = counts.get(ans, 0) + 1 + # Some UserAnswer models (e.g. physics) do not have a single 'answer' + # field. Try the simple values_list first; on failure fall back to + # iterating objects and using get_answer() or composing from + # per-field parts (a,b,c,d,e, or longs sections). + try: + for ans in ua_qs.values_list("answer", flat=True): + counts[ans] = counts.get(ans, 0) + 1 + except Exception: + for ua in ua_qs: + ans_val = None + # Prefer model helper if available + try: + if hasattr(ua, "get_answer"): + ans_val = ua.get_answer() + except Exception: + ans_val = None + + if not ans_val: + parts = [] + # Common single-letter fields used by physics + for fld in ("a", "b", "c", "d", "e"): + if hasattr(ua, fld): + v = getattr(ua, fld) + if v is None: + continue + if isinstance(v, (list, tuple)): + parts.extend([str(x) for x in v]) + else: + parts.append(str(v)) + + # Long-form answer fields (longs) + for fld in ( + "answer_observations", + "answer_interpretation", + "answer_principle_diagnosis", + "answer_differential_diagnosis", + "answer_management", + ): + if hasattr(ua, fld): + v = getattr(ua, fld) + if v: + parts.append(str(v)) + + if parts: + ans_val = ", ".join(parts) + + # Final fallback + if ans_val is None: + ans_val = "" + + counts[ans_val] = counts.get(ans_val, 0) + 1 if total_responses: pcts = {k: round(100.0 * v / total_responses, 1) for k, v in counts.items()} @@ -1386,8 +1435,49 @@ class ExamViews(View, LoginRequiredMixin): ua_qs = question.cid_user_answers.filter(exam=exam) total_responses = ua_qs.count() counts = {} - for ans in ua_qs.values_list("answer", flat=True): - counts[ans] = counts.get(ans, 0) + 1 + try: + for ans in ua_qs.values_list("answer", flat=True): + counts[ans] = counts.get(ans, 0) + 1 + except Exception: + for ua in ua_qs: + ans_val = None + try: + if hasattr(ua, "get_answer"): + ans_val = ua.get_answer() + except Exception: + ans_val = None + + if not ans_val: + parts = [] + for fld in ("a", "b", "c", "d", "e"): + if hasattr(ua, fld): + v = getattr(ua, fld) + if v is None: + continue + if isinstance(v, (list, tuple)): + parts.extend([str(x) for x in v]) + else: + parts.append(str(v)) + + for fld in ( + "answer_observations", + "answer_interpretation", + "answer_principle_diagnosis", + "answer_differential_diagnosis", + "answer_management", + ): + if hasattr(ua, fld): + v = getattr(ua, fld) + if v: + parts.append(str(v)) + + if parts: + ans_val = ", ".join(parts) + + if ans_val is None: + ans_val = "" + + counts[ans_val] = counts.get(ans_val, 0) + 1 # Compute percentages per answer key pcts = {} diff --git a/physics/views.py b/physics/views.py index af7fbb4f..59f0a04c 100644 --- a/physics/views.py +++ b/physics/views.py @@ -466,16 +466,47 @@ def exam_review_question_summary(request, pk: int, q_index: int): # Physics answers may be stored as tuples/lists or named fields; attempt to aggregate counts = defaultdict(int) other_count = 0 - for ans in ua_qs.values_list("answer", flat=True): - if not ans: - other_count += 1 - continue - # normalize iterable answers to a string key - if isinstance(ans, (list, tuple)): - key = ", ".join([str(x) for x in ans]) - else: - key = str(ans) - counts[key] += 1 + # Try simple 'answer' field aggregation first; if not present, fall + # back to iterating objects and using get_answer() or composing parts. + try: + for ans in ua_qs.values_list("answer", flat=True): + if not ans: + other_count += 1 + continue + if isinstance(ans, (list, tuple)): + key = ", ".join([str(x) for x in ans]) + else: + key = str(ans) + counts[key] += 1 + except Exception: + for ua in ua_qs: + ans_val = None + try: + if hasattr(ua, "get_answer"): + ans_val = ua.get_answer() + except Exception: + ans_val = None + + if not ans_val: + parts = [] + for fld in ("a", "b", "c", "d", "e"): + if hasattr(ua, fld): + v = getattr(ua, fld) + if v is None: + continue + if isinstance(v, (list, tuple)): + parts.extend([str(x) for x in v]) + else: + parts.append(str(v)) + + if parts: + ans_val = ", ".join(parts) + + if not ans_val: + other_count += 1 + continue + + counts[ans_val] += 1 counts["unanswered"] = other_count