Enhance exam overview and fragment by adding question status indicators for answered and flagged questions
This commit is contained in:
@@ -49,6 +49,12 @@
|
||||
<h4>Questions</h4>
|
||||
<div id="menu-list"></div>
|
||||
|
||||
<script>
|
||||
// Minimal per-question status injected by view: answered/flagged arrays
|
||||
window.examQuestionAnswered = {{ answered_json|safe }};
|
||||
window.examQuestionFlagged = {{ flagged_json|safe }};
|
||||
</script>
|
||||
|
||||
{% 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;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -42,11 +42,11 @@
|
||||
<div class="overview-text">{{answer_count}} out of {{exam_length}} questions answered. Unanswered questions are shown in red. <span class="d-block">Click any tile to jump to that question.</span></div>
|
||||
|
||||
<div class="physics-finish-list" role="list">
|
||||
{% 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 %}
|
||||
<a role="listitem" href="{% url 'physics:exam_take_user' pk=exam.id sk=forloop.counter0 %}"
|
||||
class="overview-question-btn btn btn-outline-secondary"
|
||||
class="overview-question-btn btn btn-outline-secondary {% if flagged %}flagged{% endif %}"
|
||||
aria-label="Go to question {{ forloop.counter }}"
|
||||
{% if not answer %}title="You have not answered this question" aria-current="false"{% endif %}>
|
||||
{{forloop.counter}}{% if answer and answer.answer %}: {{answer.answer}}{% endif %}
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
{% else %}
|
||||
<a role="listitem" href="{% url 'physics:exam_take' pk=exam.id sk=forloop.counter0 cid=cid passcode=passcode %}"
|
||||
class="overview-question-btn btn btn-outline-secondary"
|
||||
class="overview-question-btn btn btn-outline-secondary {% if flagged %}flagged{% endif %}"
|
||||
aria-label="Go to question {{ forloop.counter }}"
|
||||
{% if not answer %}title="You have not answered this question" aria-current="false"{% endif %}>
|
||||
{{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;
|
||||
|
||||
@@ -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 = $(`<button class="question-menu-item" name="goto-${i}" data-qn="${i}">${i+1}</button>`);
|
||||
const qbutton = $(`<button class="question-menu-item" name="goto-${i}" data-qn="${i}"><span class="qnum">${i+1}</span></button>`);
|
||||
// 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('<span class="question-flag" aria-hidden="true">⚑</span>');
|
||||
}
|
||||
if (i == {{ pos }}) { qbutton.addClass('current-question'); }
|
||||
$('#menu-list').append(qbutton);
|
||||
}
|
||||
|
||||
+26
-4
@@ -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
|
||||
]),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user