Add mark2 overview page and update navigation for improved marking workflow

This commit is contained in:
Ross
2025-11-11 13:56:11 +00:00
parent 1232c3af32
commit 6c149bb2ae
6 changed files with 90 additions and 2 deletions
+1
View File
@@ -7,6 +7,7 @@
<a href="{% url 'anatomy:exam_overview' pk=exam.pk %}">Overview</a> / <a href="{% url 'anatomy:exam_overview' pk=exam.pk %}">Overview</a> /
{% if exam.exam_mode %} {% if exam.exam_mode %}
<a href="{% url 'anatomy:mark_overview' pk=exam.pk %}">Mark</a> / <a href="{% url 'anatomy:mark_overview' pk=exam.pk %}">Mark</a> /
<a href="{% url 'anatomy:mark2_overview' pk=exam.pk %}">Mark2</a> /
<a href="{% url 'anatomy:exam_scores_all' pk=exam.pk %}">Scores</a> / <a href="{% url 'anatomy:exam_scores_all' pk=exam.pk %}">Scores</a> /
<a href="{% url 'anatomy:exam_cids' exam_id=exam.pk %}">Candidates</a> / <a href="{% url 'anatomy:exam_cids' exam_id=exam.pk %}">Candidates</a> /
<a href="{% url 'anatomy:exam_stats' exam_id=exam.pk %}">Stats</a> / <a href="{% url 'anatomy:exam_stats' exam_id=exam.pk %}">Stats</a> /
@@ -0,0 +1,20 @@
{% extends 'anatomy/exams.html' %}
{% block content %}
<div class="anatomy">
<h2>Mark2: Marking exam: {{ exam }}</h2>
Use the improved HTMX marking UI by clicking into a question below.
<div>
<button class="show-all-button">Show all</button><button class="show-unmarked-button">Show unmarked</button>
</div>
<div id="stark-marking-button"><a href="{% url 'anatomy:mark2' exam_pk=exam.pk sk=0 %}"><button>Click here to start mark2</button></a></div>
<ul id="question-mark-list">
{% for question, unmarked_count, unmarked_count2 in question_unmarked_map %}
<li data-markcount={{unmarked_count}} {% if unmarked_count %}class="unmarked" {% endif %}><a href="{% url 'anatomy:mark2' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter }}:
{{ question }}</a><br />Unmarked answers: {{ unmarked_count }}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
+1
View File
@@ -109,6 +109,7 @@ urlpatterns = [
), ),
# New improved marking UI (mark2) and small toggle endpoint used by JS/HTMX # New improved marking UI (mark2) and small toggle endpoint used by JS/HTMX
path("exam/<int:exam_pk>/<int:sk>/mark2", views.mark2, name="mark2"), path("exam/<int:exam_pk>/<int:sk>/mark2", views.mark2, name="mark2"),
path("exam/<int:pk>/mark2", views.mark2_overview, name="mark2_overview"),
path("exam/mark2/toggle", views.mark2_toggle_answer, name="mark2_toggle_answer"), path("exam/mark2/toggle", views.mark2_toggle_answer, name="mark2_toggle_answer"),
path("exam/<int:exam_pk>/<int:sk>/mark2/exam_marked", views.mark2_exam_marked, name="mark2_exam_marked"), path("exam/<int:exam_pk>/<int:sk>/mark2/exam_marked", views.mark2_exam_marked, name="mark2_exam_marked"),
path("question/<int:pk>/mark2/question_marked", views.mark2_question_marked, name="mark2_question_marked"), path("question/<int:pk>/mark2/question_marked", views.mark2_question_marked, name="mark2_question_marked"),
+60
View File
@@ -15,6 +15,7 @@ from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView from django.views.generic import ListView
from django.db.models.functions import Lower from django.db.models.functions import Lower
from django.db.models import Prefetch
from django.core.cache import cache from django.core.cache import cache
@@ -780,6 +781,65 @@ def mark2(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
return render(request, "anatomy/mark2.html", context) return render(request, "anatomy/mark2.html", context)
@login_required
def mark2_overview(request, pk):
"""Overview page for mark2 workflow: lists questions and unmarked counts and links into mark2."""
exam = get_object_or_404(Exam, pk=pk)
if not exam.exam_mode:
raise Http404("Packet not in exam mode")
# Only allow authors or designated markers
if request.user not in exam.author.all():
if not GenericExamViews.check_user_marker_access(request.user, pk):
raise PermissionDenied
# For anatomy prefer prefetch similar to generic.mark_overview
questions = (
exam.exam_questions.select_related(
"modality",
"structure",
"body_part",
"region",
"examination",
"question_type",
)
.all()
.prefetch_related(
Prefetch(
"answers",
queryset=Answer.objects.filter(proposed=False, status=Answer.MarkOptions.CORRECT),
to_attr="prefetched_primary_answer",
),
)
.order_by("examquestiondetail__sort_order")
)
# Handle double marking which needs per-marker counts
try:
if exam.double_mark:
question_unmarked_map = []
for q in questions:
question_unmarked_map.append(
(
q,
int(q.get_unmarked_user_answer_count(exam_pk=exam.pk)),
int(q.get_unmarked_user_answer_count(exam_pk=exam.pk, marker=request.user)),
)
)
return render(request, "anatomy/mark2_overview.html", {"exam": exam, "question_unmarked_map": question_unmarked_map})
except AttributeError:
pass
question_unmarked_map = []
for q in questions:
count = int(q.get_unmarked_user_answer_count(exam_pk=exam.pk))
question_unmarked_map.append((q, count, count))
return render(request, "anatomy/mark2_overview.html", {"exam": exam, "question_unmarked_map": question_unmarked_map})
@login_required @login_required
def mark2_toggle_answer(request): def mark2_toggle_answer(request):
"""Endpoint to toggle/set a single answer mark quickly. """Endpoint to toggle/set a single answer mark quickly.
@@ -21,7 +21,10 @@
<small class="text-muted">Authors: {{ exam.get_authors }}</small> <small class="text-muted">Authors: {{ exam.get_authors }}</small>
{% if exam.exam_mode %} {% if exam.exam_mode %}
{% if app_name in "rapids longs anatomy" %} {% if app_name in "rapids longs anatomy" %}
{% if request.user.is_staff %}<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}">Mark answers</a>{% endif %} {% if request.user.is_staff %}
<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}">Mark answers</a>
{% if app_name == 'anatomy' %} / <a href="{% url 'anatomy:mark2_overview' pk=exam.pk %}">Mark (mark2)</a>{% endif %}
{% endif %}
{% endif %} {% endif %}
{% if request.user.is_staff %}<a href="{% url app_name|add:':exam_scores_all' pk=exam.pk %}">Scores</a>{% endif %} {% if request.user.is_staff %}<a href="{% url app_name|add:':exam_scores_all' pk=exam.pk %}">Scores</a>{% endif %}
{% endif %} {% endif %}
+4 -1
View File
@@ -12,7 +12,10 @@
<a href='{% url "generic:examcollection_detail" exam.examcollection_id %}' title="This exam is part of the collection {{exam.examcollection.name}}"><i class="bi bi-collection"></i></a> <a href='{% url "generic:examcollection_detail" exam.examcollection_id %}' title="This exam is part of the collection {{exam.examcollection.name}}"><i class="bi bi-collection"></i></a>
{% endif %} {% endif %}
</span> </span>
{% if marking %}<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col-half">Mark</a>{% endif %} {% if marking %}
<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col-half">Mark</a>
{% if app_name == 'anatomy' %}<a href="{% url 'anatomy:mark2_overview' pk=exam.pk %}" class="flex-col-half">Mark2</a>{% endif %}
{% endif %}
<span class="flex-col"> <span class="flex-col">
<a href="{% url app_name|add:':exam_cids' exam_id=exam.pk %}">Candidates</a> <span class="candidate-counts">[<span title="Number of active CID users"> <a href="{% url app_name|add:':exam_cids' exam_id=exam.pk %}">Candidates</a> <span class="candidate-counts">[<span title="Number of active CID users">
{{exam.valid_cid_users.count}} {{exam.valid_cid_users.count}}