diff --git a/physics/templates/physics/exam_take.html b/physics/templates/physics/exam_take.html
index 357a6d56..b8e74a83 100755
--- a/physics/templates/physics/exam_take.html
+++ b/physics/templates/physics/exam_take.html
@@ -49,6 +49,12 @@
Questions
+
+
{% include "physics/exam_take_help.html" %}
{% endblock %}
@@ -197,6 +203,27 @@
.postinput { margin-top: 0.4rem; text-align: left; }
.physics-answer-list li { padding: 0.6rem; }
}
+ /* Question menu item states (small pill buttons) */
+ button.question-menu-item {
+ color: var(--text);
+ border: 1px solid var(--card-border);
+ background: var(--card-bg);
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.35rem;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ }
+ button.question-menu-item.answered {
+ border-color: var(--success) !important; /* answered -> success border */
+ }
+ /* flagged items show an icon only; no border change here */
+ button.question-menu-item .question-flag {
+ margin-left: 0.25rem;
+ font-size: 0.85em;
+ color: #ffc107;
+ line-height: 1;
+ }
{% endblock %}
diff --git a/physics/templates/physics/exam_take_overview.html b/physics/templates/physics/exam_take_overview.html
index 6e6cb7b4..3ff7987d 100644
--- a/physics/templates/physics/exam_take_overview.html
+++ b/physics/templates/physics/exam_take_overview.html
@@ -42,11 +42,11 @@
{{answer_count}} out of {{exam_length}} questions answered. Unanswered questions are shown in red. Click any tile to jump to that question.
- {% for question, answer in question_answer_tuples %}
+ {% for question, answer, flagged in question_answer_tuples %}
{% comment %} Use an anchor styled as a button rather than nesting button inside anchor (invalid HTML). Keep link targets unchanged. {% endcomment %}
{% if not cid %}
{{forloop.counter}}{% if answer and answer.answer %}: {{answer.answer}}{% endif %}
@@ -54,7 +54,7 @@
{% else %}
{{forloop.counter}}{% if answer and answer.answer %}: {{answer.answer}}{% endif %}
@@ -129,6 +129,13 @@
justify-content: flex-start;
}
+ /* Flagged question indicator: use border colour to signal a flagged item */
+ .overview-question-btn.flagged {
+ border-color: #ffc107 !important;
+ box-shadow: 0 1px 2px rgba(255,193,7,0.06);
+ background: rgba(255,193,7,0.03);
+ }
+
/* Unanswered questions should draw attention */
.overview-question-btn[title] {
border-color: #dc3545 !important;
diff --git a/physics/templates/physics/partials/exam_take_fragment.html b/physics/templates/physics/partials/exam_take_fragment.html
index 8194fb65..08410727 100644
--- a/physics/templates/physics/partials/exam_take_fragment.html
+++ b/physics/templates/physics/partials/exam_take_fragment.html
@@ -124,8 +124,17 @@
// build the question menu locally so it is available after fragment swaps
$('#menu-list').empty();
+ const answeredArr = (window.examQuestionAnswered && Array.isArray(window.examQuestionAnswered)) ? window.examQuestionAnswered : null;
+ const flaggedArr = (window.examQuestionFlagged && Array.isArray(window.examQuestionFlagged)) ? window.examQuestionFlagged : null;
for (let i = 0; i < {{ exam_length }}; i++) {
- const qbutton = $(``);
+ const qbutton = $(``);
+ // answered: indicate with border colour but keep text colour unchanged (white)
+ if (answeredArr && answeredArr[i]) { qbutton.addClass('answered'); }
+ // flagged: add flag icon and flagged class which alters border
+ if (flaggedArr && flaggedArr[i]) {
+ qbutton.addClass('flagged');
+ qbutton.append('⚑');
+ }
if (i == {{ pos }}) { qbutton.addClass('current-question'); }
$('#menu-list').append(qbutton);
}
diff --git a/physics/views.py b/physics/views.py
index db7fa189..4733828d 100644
--- a/physics/views.py
+++ b/physics/views.py
@@ -186,12 +186,23 @@ def exam_take_overview(request, pk, cid=None, passcode=None):
question_answer_tuples = []
answer_count = 0
for q in questions:
- # if q in answer_question_map and answer_question_map[q].answer:
- if q in answer_question_map: # might need to improve this
- question_answer_tuples.append((q, answer_question_map[q]))
+ if q in answer_question_map:
+ ans = answer_question_map[q]
answer_count += 1
else:
- question_answer_tuples.append((q, None))
+ ans = None
+ # compute flagged state for this question+actor
+ try:
+ ct = ContentType.objects.get_for_model(q)
+ flags_qs = Flag.objects.filter(content_type=ct, object_id=q.pk)
+ if cid is not None:
+ flags_qs = flags_qs.filter(cid_user__cid=cid)
+ else:
+ flags_qs = flags_qs.filter(user=request.user)
+ flagged = flags_qs.exists()
+ except Exception:
+ flagged = False
+ question_answer_tuples.append((q, ans, flagged))
cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user)
@@ -380,6 +391,17 @@ def exam_take(request, pk: int, sk: int, cid: str | None = None, passcode: str |
"passcode": passcode,
"cid_user_exam": cid_user_exam,
"flagged": flagged,
+ # minimal per-question status for client-side menu: answered/flagged lists
+ "answered_json": json.dumps([
+ True if (question.cid_user_answers.filter(cid=cid, exam=exam).exists() if cid is not None else question.cid_user_answers.filter(user=request.user, exam=exam).exists()) else False
+ for question in questions
+ ]),
+ "flagged_json": json.dumps([
+ (Flag.objects.filter(content_type=ContentType.objects.get_for_model(question), object_id=question.pk).filter(cid_user__cid=cid).exists()
+ if cid is not None
+ else Flag.objects.filter(content_type=ContentType.objects.get_for_model(question), object_id=question.pk).filter(user=request.user).exists())
+ for question in questions
+ ]),
},
)