Add new endpoints and templates for displaying marked answers in the improved marking UI

This commit is contained in:
Ross
2025-11-11 11:53:35 +00:00
parent 1997d013d8
commit 34ef5106f4
5 changed files with 197 additions and 27 deletions
+56 -27
View File
@@ -23,7 +23,7 @@
</div>
<div class="text-end">
{% if question.answer_help %}
<button class="btn btn-sm btn-outline-info" data-bs-toggle="collapse" data-bs-target="#marking-help">Help</button>
<button class="btn btn-sm btn-outline-info" data-bs-toggle="collapse" data-bs-target="#marking-help">Answer Help</button>
{% endif %}
</div>
</div>
@@ -39,7 +39,7 @@
<form method="POST" class="post-form">{% csrf_token %}
<p class="small text-muted">Click each answer to toggle through marks awarded (as per colour). This UI writes marks immediately.</p>
<div class="d-flex justify-content-between align-items-center mb-2">
<div class="answer-list key small">Key: <span class="correct">2 Marks</span>, <span class="half-correct">1 Mark</span>, <span class="incorrect">0 Marks</span></div>
<div></div>
<div>
{% if question_details.current > 1 %}
<a class="btn btn-outline-secondary btn-sm me-1" href="{% url 'anatomy:mark2' exam.id question_number|add:-1 %}">Previous</a>
@@ -51,26 +51,28 @@
{% endif %}
</div>
</div>
<div class="mb-2">
<button class="btn btn-sm btn-outline-info me-1" hx-get="{% url 'anatomy:mark2_exam_marked' exam.id question_number %}" hx-target="#exam-marked-container" hx-swap="innerHTML">Show exam-submitted answers</button>
<button class="btn btn-sm btn-outline-info" hx-get="{% url 'anatomy:mark2_question_marked' question.pk %}" hx-target="#question-marked-container" hx-swap="innerHTML">Show all stored answers for question</button>
</div>
<div class="row">
<div class="col-12">
<div class="row">
<div class="col-md-6">
<h6>Unmarked</h6>
<ul id="unmarked-list" class="list-group answer-list mb-3">
{% include 'anatomy/partials/mark2_unmarked_list_fragment.html' %}
</ul>
</div>
<div class="col-md-6">
<h6>Marked</h6>
<ul id="marked-list" class="list-group answer-list mb-3">
{% include 'anatomy/partials/mark2_marked_list_fragment.html' %}
</ul>
</div>
</div>
<h6>Answers</h6>
<ul id="answer-list" class="list-group answer-list mb-3">
{# Render currently-stored marked answers first, then unmarked submissions #}
{% include 'anatomy/partials/mark2_marked_list_fragment.html' %}
{% include 'anatomy/partials/mark2_unmarked_list_fragment.html' %}
</ul>
<div id="exam-marked-container" class="mt-2"></div>
<div id="question-marked-container" class="mt-2"></div>
</div>
</div>
<div class="mt-3">
<div class="small text-muted key">Key: <span class="key-item correct">2 Marks</span>, <span class="key-item half-correct">1 Mark</span>, <span class="key-item incorrect">0 Marks</span></div>
</div>
<span class="visually-hidden">{{ form.as_p }}</span>
</form>
</div>
@@ -88,13 +90,25 @@
<style>
/* Ensure answer states are visible and clickable in the new layout */
.answer { cursor: pointer; }
.answer.correct { color: #155724; background-color: #d4edda; }
.answer.half-correct { color: #856404; background-color: #fff3cd; }
.answer.incorrect { color: #721c24; background-color: #f8d7da; }
/* Use border to indicate mark state instead of background */
.answer.correct { color: #155724; background-color: transparent; }
.answer.half-correct { color: #856404; background-color: transparent; }
.answer.incorrect { color: #721c24; background-color: transparent; }
.answer.not-marked { color: inherit; background-color: transparent; }
.answer-list .list-group-item pre { margin: 0; font-family: monospace; white-space: pre-wrap; }
.mark-controls { display:flex; gap:0.25rem; }
.mark-btn { padding: 0.15rem 0.4rem; font-size: 0.8rem; }
/* Per-state border indicators on the list item */
.marked-item { }
.marked-correct { border-left: 4px solid #2a9d3f; }
.marked-half-correct { border-left: 4px solid #ffb020; }
.marked-incorrect { border-left: 4px solid #d3413a; }
/* Key items use a small left-border swatch to match the list markers */
.key .key-item { display:inline-block; padding-left:8px; margin-left:6px; }
.key .key-item.correct { border-left: 8px solid #2a9d3f; }
.key .key-item.half-correct { border-left: 8px solid #ffb020; }
.key .key-item.incorrect { border-left: 8px solid #d3413a; }
</style>
{% endblock %}
@@ -138,13 +152,19 @@
// set class preserving 'answer' base
el.className = 'answer ' + newState;
// move DOM node to the appropriate list for immediate feedback
// mark/unmark list item visually (no moving between lists in single-column mode)
const li = el.closest('li');
if (li) {
// clear any previous per-state classes
li.classList.remove('marked-correct','marked-half-correct','marked-incorrect','marked-item');
if (newState === 'not-marked') {
document.getElementById('unmarked-list').appendChild(li);
} else {
document.getElementById('marked-list').appendChild(li);
// nothing
} else if (newState === 'correct') {
li.classList.add('marked-item','marked-correct');
} else if (newState === 'half-correct') {
li.classList.add('marked-item','marked-half-correct');
} else if (newState === 'incorrect') {
li.classList.add('marked-item','marked-incorrect');
}
}
@@ -155,7 +175,7 @@
function updateMarkedAnswersField() {
const marked = { 'correct': [], 'half-correct': [], 'incorrect': [] };
document.querySelectorAll('#marked-list span.answer').forEach(function(el) {
document.querySelectorAll('#answer-list span.answer').forEach(function(el) {
if (el.classList.contains('correct')) marked.correct.push((el.title || el.textContent).trim());
else if (el.classList.contains('half-correct')) marked['half-correct'].push((el.title || el.textContent).trim());
else if (el.classList.contains('incorrect')) marked.incorrect.push((el.title || el.textContent).trim());
@@ -211,7 +231,7 @@
}
function setMarkForSpan(el, numeric) {
// numeric: 2 -> correct, 1 -> half-correct, 0 -> incorrect, null -> not-marked
// numeric: 2 -> correct, 1 -> half-correct, 0 -> incorrect, null -> not-marked
const li = el.closest('li');
let state = 'not-marked';
if (numeric === 2) state = 'correct';
@@ -219,8 +239,17 @@
else if (numeric === 0) state = 'incorrect';
el.className = 'answer ' + state;
if (li) {
if (state === 'not-marked') document.getElementById('unmarked-list').appendChild(li);
else document.getElementById('marked-list').appendChild(li);
// clear previous per-state classes
li.classList.remove('marked-correct','marked-half-correct','marked-incorrect','marked-item');
if (state === 'not-marked') {
// nothing
} else if (state === 'correct') {
li.classList.add('marked-item','marked-correct');
} else if (state === 'half-correct') {
li.classList.add('marked-item','marked-half-correct');
} else if (state === 'incorrect') {
li.classList.add('marked-item','marked-incorrect');
}
}
updateMarkedAnswersField();
}
@@ -0,0 +1,37 @@
{% comment %}List of distinct answers submitted within this exam for the question, with counts and mark badges{% endcomment %}
<div class="card mt-2">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="card-title mb-0">Answers submitted in this exam</h6>
<div>
<div class="btn-group btn-group-sm" role="group" aria-label="Order">
<button class="btn btn-sm btn-outline-primary{% if request.GET.order == 'count' or not request.GET.order %} active{% endif %}" hx-get="{{ request.path }}?order=count" hx-swap="outerHTML" hx-target="closest .card">By count</button>
<button class="btn btn-sm btn-outline-primary{% if request.GET.order == 'mark' %} active{% endif %}" hx-get="{{ request.path }}?order=mark" hx-swap="outerHTML" hx-target="closest .card">By score</button>
</div>
</div>
</div>
{% if not items %}
<div class="small text-muted">No answers submitted in this exam.</div>
{% else %}
<ul class="list-group">
{% for it in items %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div><pre class="mb-0">{{ it.answer }}</pre></div>
<div class="text-end">
{% if it.mark == 'correct' %}
<span class="badge bg-success me-2">2</span>
{% elif it.mark == 'half-correct' %}
<span class="badge bg-warning text-dark me-2">1</span>
{% elif it.mark == 'incorrect' %}
<span class="badge bg-danger me-2">0</span>
{% else %}
<span class="badge bg-secondary me-2">-</span>
{% endif %}
<span class="badge bg-light text-dark me-2">{{ it.count }}</span>
</div>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
@@ -0,0 +1,38 @@
{% comment %}All stored Answer choices for this question grouped by status{% endcomment %}
<div class="card mt-2">
<div class="card-body">
<h6 class="card-title">All stored answers for this question</h6>
<div class="row">
<div class="col-md-4">
<h6>2 marks</h6>
<ul class="list-group mb-2">
{% for answer in correct_answers %}
<li class="list-group-item"><pre class="mb-0">{{ answer.answer }}</pre></li>
{% empty %}
<li class="list-group-item small text-muted">None</li>
{% endfor %}
</ul>
</div>
<div class="col-md-4">
<h6>1 mark</h6>
<ul class="list-group mb-2">
{% for answer in half_mark_answers %}
<li class="list-group-item"><pre class="mb-0">{{ answer.answer }}</pre></li>
{% empty %}
<li class="list-group-item small text-muted">None</li>
{% endfor %}
</ul>
</div>
<div class="col-md-4">
<h6>0 marks</h6>
<ul class="list-group mb-2">
{% for answer in incorrect_answers %}
<li class="list-group-item"><pre class="mb-0">{{ answer.answer }}</pre></li>
{% empty %}
<li class="list-group-item small text-muted">None</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>