From 9cf7ad6da7b0ca3c0f6b83d03decfda9534daadf Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 10 Nov 2025 21:18:58 +0000 Subject: [PATCH] Refactor exam_take function to use a canonical list of questions for consistent indexing and improved error handling for question navigation --- physics/templates/physics/exam_take.html | 385 +++++++++++++---------- physics/views.py | 27 +- 2 files changed, 244 insertions(+), 168 deletions(-) diff --git a/physics/templates/physics/exam_take.html b/physics/templates/physics/exam_take.html index 93e2a42c..e1ef7f25 100755 --- a/physics/templates/physics/exam_take.html +++ b/physics/templates/physics/exam_take.html @@ -3,17 +3,24 @@ {% block content %} {% if request.user.is_authenticated %} - User: {{request.user}} + User: {{ request.user }} {% else %} - CID: {{cid}} + CID: {{ cid }} {% endif %} + {% include "exam_clock.html" %} +
-

{{exam}}: Question [{{pos|add:1}}/{{exam_length}}]

+

+ {{ exam }}: Question + [{{ pos|add:1 }}/{{ exam_length }}] +

+ {% if exam.publish_results %} {% elif cid_user_exam.completed %} +

Questions

- {% include "physics/exam_take_help.html" %} - {% endblock %} +{% endblock %} - {% block js %} - +{% endblock %} - }) +{% block css %} + - {% endblock %} + /* Question menu (jump buttons) */ + #menu-list { margin-top: 1rem; display:flex; flex-wrap:wrap; gap:0.4rem; } + button.question-menu-item { + padding: 0.35rem 0.6rem; + border-radius: 0.35rem; + border: 1px solid var(--card-border); + background: var(--card-bg); + color: var(--text); + cursor: pointer; + font-size: 0.9rem; + } + button.question-menu-item.current-question { + background: var(--primary); color: #fff; border-color: var(--primary); + box-shadow: 0 1px 3px rgba(13,110,253,0.18); + } + + /* Control buttons spacing */ + .mt-2 .save { margin-right: 0.5rem; } + .mt-2 .save.btn.btn-default { background: var(--feedback-bg); border: 1px solid var(--card-border); color: var(--text); } + + /* Small helper: make labels wrap nicely on small screens */ + @media (max-width: 576px) { + .flex-container { flex-direction: column; align-items: stretch; } + .postinput { margin-top: 0.4rem; } + .physics-answer-list li { padding: 0.6rem; } + } + +{% endblock %} diff --git a/physics/views.py b/physics/views.py index e08e45ac..d8149859 100644 --- a/physics/views.py +++ b/physics/views.py @@ -188,7 +188,16 @@ def exam_take_overview(request, pk, cid=None, passcode=None): ) -def exam_take(request, pk: int, sk: int, cid: str| None = None, passcode: str| None = None): +def exam_take(request, pk: int, sk: int, cid: str | None = None, passcode: str | None = None): + """Display and handle answers for a single question in a physics exam. + + Behavior and invariants: + - Uses a single canonical list of questions (from `exam.get_questions()`) for + selection, navigation and sizing so indexes cannot drift. + - Validates the incoming question index and raises 404 for invalid values. + - Supports candidate (cid) and logged-in user answers. + """ + exam = get_object_or_404(Exam, pk=pk) if not exam.active: @@ -198,25 +207,23 @@ def exam_take(request, pk: int, sk: int, cid: str| None = None, passcode: str| N cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user) - # Use a single, canonical list of questions for all indexing/navigation - # operations to avoid mismatches between different querysets or orderings. + # Canonical questions list for consistent indexing/navigation questions = list(exam.get_questions()) - # Validate sk and select the current question + # Normalize and validate provided index try: - sk = int(sk) + index = int(sk) except Exception: raise Http404("Invalid question index") - if sk < 0 or sk >= len(questions): + if index < 0 or index >= len(questions): raise Http404("Question not found in exam") - question = questions[sk] - + question = questions[index] exam_length = len(questions) - # Since we're using the canonical list, the positional index is sk. - pos = sk + # local numeric position equals the validated index + pos = index if cid is not None: answer = question.cid_user_answers.filter(cid=cid, exam=exam).first()