+
@@ -135,23 +117,9 @@
{% block js %}
{% endblock %}
diff --git a/generic/templates/generic/partials/exam_cids_cid_list.html b/generic/templates/generic/partials/exam_cids_cid_list.html
new file mode 100644
index 00000000..0e23d7c4
--- /dev/null
+++ b/generic/templates/generic/partials/exam_cids_cid_list.html
@@ -0,0 +1,31 @@
+{% comment %} Partial: list of CID candidates for HTMX swap {% endcomment %}
+{% if cid_users %}
+
+ {% for cid in cid_users %}
+ -
+
+
+
+
{{ cid.name }}
+
Email: {{ cid.email }}
+ {% if cid.notes %}
+
Notes: {{ cid.notes|truncatechars:120 }}
+ {% endif %}
+
+
+
+ {% endfor %}
+
+{% else %}
+
This exam has no CID candidates.
+{% endif %}
diff --git a/generic/templates/generic/partials/exam_cids_submitted_list.html b/generic/templates/generic/partials/exam_cids_submitted_list.html
new file mode 100644
index 00000000..2d676f0f
--- /dev/null
+++ b/generic/templates/generic/partials/exam_cids_submitted_list.html
@@ -0,0 +1,16 @@
+{% comment %} Partial: submitted candidates fragment for HTMX swap {% endcomment %}
+{% if user_exam_data %}
+
+
+
Submitted candidates
+
The following candidate submissions have been received. Candidates with submissions are highlighted above.
+
+ {% for user_data in user_exam_data %}
+ - CID: {{user_data.cid_user}} / User: {{user_data.user_user}} / Start: {{user_data.start_time}} / End: {{user_data.end_time}}
+ {% endfor %}
+
+
+
+{% else %}
+
No submissions yet.
+{% endif %}
diff --git a/generic/templates/generic/partials/exam_cids_user_list.html b/generic/templates/generic/partials/exam_cids_user_list.html
new file mode 100644
index 00000000..c754a397
--- /dev/null
+++ b/generic/templates/generic/partials/exam_cids_user_list.html
@@ -0,0 +1,27 @@
+{% comment %} Partial: list of User candidates for HTMX swap {% endcomment %}
+{% if user_users %}
+
+ {% for user in user_users %}
+ -
+
+
+
+ {% if request.user.is_superuser %}
+
+ {% endif %}
+
{{ user.username }}
+
+
Email: {{ user.email }}
+
+
+
+ {% endfor %}
+
+{% else %}
+
This exam has no User candidates.
+{% endif %}
diff --git a/generic/urls.py b/generic/urls.py
index df7b7ac4..dbc83b56 100755
--- a/generic/urls.py
+++ b/generic/urls.py
@@ -523,6 +523,9 @@ def generic_exam_urls(generic_exam_view: GenericExamViews):
name="order_questions",
),
path("exam/
/cids", generic_exam_view.exam_cids, name="exam_cids"),
+ path("exam//cids/cid_list", generic_exam_view.exam_cids_cid_list, name="exam_cids_cid_list"),
+ path("exam//cids/user_list", generic_exam_view.exam_cids_user_list, name="exam_cids_user_list"),
+ path("exam//cids/submitted_list", generic_exam_view.exam_cids_submitted_list, name="exam_cids_submitted_list"),
# path("exam//groups", generic_exam_view.exam_groups_edit, name="exam_groups_edit"),
path(
"exam//reset_answers",
diff --git a/generic/views.py b/generic/views.py
index d997e93e..d6d3cef7 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -2183,23 +2183,20 @@ class ExamViews(View, LoginRequiredMixin):
if not request.user.is_superuser:
raise PermissionDenied
- cid_users = (
- exam.valid_cid_users.all()
- ) # .order_by("cid")#.prefetch_related("*")
- cid_user_count = len(cid_users)
+ # Avoid loading large querysets here — the detailed lists are
+ # rendered by HTMX partials which will fetch the data separately.
+ cid_user_count = exam.valid_cid_users.count()
+ user_user_count = exam.valid_user_users.count()
- user_users = exam.valid_user_users.all() # .order_by("username")
- user_user_count = len(user_users)
-
- user_exam_data = exam.cid_users.all().prefetch_related("cid_user", "user_user")
+ # Provide a lightweight indicator if there are any submissions so the
+ # template can decide whether to load the submitted-list partial.
+ has_submissions = exam.cid_users.exists()
context = {
"exam": exam,
- "cid_users": cid_users,
"cid_user_count": cid_user_count,
- "user_exam_data": user_exam_data,
- "user_users": user_users,
"user_user_count": user_user_count,
+ "has_submissions": has_submissions,
"app_name": self.app_name,
}
@@ -2208,6 +2205,36 @@ class ExamViews(View, LoginRequiredMixin):
return render(request, "exam_cids.html", context)
+ def exam_cids_cid_list(self, request, exam_id):
+ """HTMX partial returning the CID candidates list for an exam."""
+ exam = get_object_or_404(self.Exam, pk=exam_id)
+ if request.user not in exam.author.all() and not request.user.is_superuser:
+ raise PermissionDenied
+
+ cid_users = exam.valid_cid_users.all()
+
+ return render(request, "generic/partials/exam_cids_cid_list.html", {"cid_users": cid_users, "exam": exam})
+
+ def exam_cids_user_list(self, request, exam_id):
+ """HTMX partial returning the User candidates list for an exam."""
+ exam = get_object_or_404(self.Exam, pk=exam_id)
+ if request.user not in exam.author.all() and not request.user.is_superuser:
+ raise PermissionDenied
+
+ user_users = exam.valid_user_users.all()
+
+ return render(request, "generic/partials/exam_cids_user_list.html", {"user_users": user_users, "exam": exam})
+
+ def exam_cids_submitted_list(self, request, exam_id):
+ """HTMX partial returning the submitted candidates summary."""
+ exam = get_object_or_404(self.Exam, pk=exam_id)
+ if request.user not in exam.author.all() and not request.user.is_superuser:
+ raise PermissionDenied
+
+ user_exam_data = exam.cid_users.all().prefetch_related("cid_user", "user_user")
+
+ return render(request, "generic/partials/exam_cids_submitted_list.html", {"user_exam_data": user_exam_data, "exam": exam})
+
# def exam_groups_edit(self, request, exam_id):
# exam = get_object_or_404(self.Exam, pk=exam_id)