Enhance submission tracking by adding first and last name data attributes for candidates, improving badge tooltip information in the submitted candidates list.
This commit is contained in:
@@ -159,14 +159,18 @@
|
|||||||
// data attributes for cid/user. Then insert a small badge on matching list items.
|
// data attributes for cid/user. Then insert a small badge on matching list items.
|
||||||
// Collect submitted identifiers first to avoid duplicate processing
|
// Collect submitted identifiers first to avoid duplicate processing
|
||||||
const submittedEls = document.querySelectorAll('#submitted-candidates [data-cid], #submitted-candidates [data-user]');
|
const submittedEls = document.querySelectorAll('#submitted-candidates [data-cid], #submitted-candidates [data-user]');
|
||||||
const submittedCids = new Set();
|
// Build maps of submitted identifiers -> their timestamps so we
|
||||||
const submittedUsers = new Set();
|
// can attach tooltip text to the badges we create below.
|
||||||
|
const submittedCids = new Map();
|
||||||
|
const submittedUsers = new Map();
|
||||||
submittedEls.forEach(function(el){
|
submittedEls.forEach(function(el){
|
||||||
try{
|
try{
|
||||||
const cid = el.dataset && el.dataset.cid ? el.dataset.cid.toString() : null;
|
const cid = el.dataset && el.dataset.cid ? el.dataset.cid.toString() : null;
|
||||||
const user = el.dataset && el.dataset.user ? el.dataset.user.toString() : null;
|
const user = el.dataset && el.dataset.user ? el.dataset.user.toString() : null;
|
||||||
if(cid && cid !== 'None') submittedCids.add(cid);
|
const first = el.dataset && el.dataset.first ? el.dataset.first.toString() : '';
|
||||||
if(user && user !== 'None') submittedUsers.add(user);
|
const last = el.dataset && el.dataset.last ? el.dataset.last.toString() : '';
|
||||||
|
if(cid && cid !== 'None') submittedCids.set(cid, {first:first, last:last});
|
||||||
|
if(user && user !== 'None') submittedUsers.set(user, {first:first, last:last});
|
||||||
}catch(e){ console.error('highlightSubmitted collect error', e); }
|
}catch(e){ console.error('highlightSubmitted collect error', e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -177,9 +181,15 @@
|
|||||||
const link = li.querySelector && li.querySelector('.cid-link');
|
const link = li.querySelector && li.querySelector('.cid-link');
|
||||||
const text = (link && link.textContent) ? link.textContent.trim() : (li.textContent||'').trim();
|
const text = (link && link.textContent) ? link.textContent.trim() : (li.textContent||'').trim();
|
||||||
if(submittedCids.has(text) && !li.querySelector('.submitted-badge')){
|
if(submittedCids.has(text) && !li.querySelector('.submitted-badge')){
|
||||||
|
const info = submittedCids.get(text) || {first:'', last:''};
|
||||||
const badge = document.createElement('span');
|
const badge = document.createElement('span');
|
||||||
badge.className = 'submitted-badge badge bg-success ms-2';
|
badge.className = 'submitted-badge badge bg-success ms-2';
|
||||||
badge.textContent = 'Submitted';
|
badge.textContent = 'Submitted';
|
||||||
|
if(info.first || info.last){
|
||||||
|
const title = `Submitted\nFirst: ${info.first}\nLast: ${info.last}`;
|
||||||
|
badge.setAttribute('title', title);
|
||||||
|
badge.setAttribute('data-bs-toggle', 'tooltip');
|
||||||
|
}
|
||||||
if(link && link.parentNode){
|
if(link && link.parentNode){
|
||||||
link.parentNode.appendChild(badge);
|
link.parentNode.appendChild(badge);
|
||||||
} else {
|
} else {
|
||||||
@@ -198,9 +208,15 @@
|
|||||||
for(const user of submittedUsers){
|
for(const user of submittedUsers){
|
||||||
if(txt.indexOf(user) !== -1){
|
if(txt.indexOf(user) !== -1){
|
||||||
if(!li.querySelector('.submitted-badge')){
|
if(!li.querySelector('.submitted-badge')){
|
||||||
|
const info = submittedUsers.get(user) || {first:'', last:''};
|
||||||
const badge = document.createElement('span');
|
const badge = document.createElement('span');
|
||||||
badge.className = 'submitted-badge badge bg-success ms-2';
|
badge.className = 'submitted-badge badge bg-success ms-2';
|
||||||
badge.textContent = 'Submitted';
|
badge.textContent = 'Submitted';
|
||||||
|
if(info.first || info.last){
|
||||||
|
const title = `Submitted\nFirst: ${info.first}\nLast: ${info.last}`;
|
||||||
|
badge.setAttribute('title', title);
|
||||||
|
badge.setAttribute('data-bs-toggle', 'tooltip');
|
||||||
|
}
|
||||||
const nameEl = li.querySelector('.fw-bold') || li.querySelector('a') || li;
|
const nameEl = li.querySelector('.fw-bold') || li.querySelector('a') || li;
|
||||||
nameEl.appendChild(badge);
|
nameEl.appendChild(badge);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="submitted-candidates">
|
<tbody id="submitted-candidates">
|
||||||
{% for user_data in user_exam_data %}
|
{% for user_data in user_exam_data %}
|
||||||
<tr data-cid="{{ user_data.cid_user }}" data-user="{{ user_data.user_user }}">
|
<tr data-cid="{{ user_data.cid_user }}" data-user="{{ user_data.user_user }}" data-first="{{ user_data.start_time|default_if_none:'' }}" data-last="{{ user_data.end_time|default_if_none:'' }}">
|
||||||
<td class="fw-bold">{{ user_data.cid_user }}</td>
|
<td class="fw-bold">{{ user_data.cid_user }}</td>
|
||||||
<td>{{ user_data.user_user }}</td>
|
<td>{{ user_data.user_user }}</td>
|
||||||
<td class="text-nowrap small text-muted">{{ user_data.start_time }}</td>
|
<td class="text-nowrap small text-muted">{{ user_data.start_time }}</td>
|
||||||
|
|||||||
+17
-3
@@ -56,7 +56,7 @@ from reversion.views import RevisionMixin
|
|||||||
from atlas.models import CaseCollection, CaseDetail
|
from atlas.models import CaseCollection, CaseDetail
|
||||||
from generic.decorators import user_is_cid_user_manager
|
from generic.decorators import user_is_cid_user_manager
|
||||||
from generic.filters import CidUserFilter, ExaminationFilter, SupervisorFilter
|
from generic.filters import CidUserFilter, ExaminationFilter, SupervisorFilter
|
||||||
from generic.models import UserUserGroup
|
from generic.models import UserUserGroup, CidUserExam
|
||||||
|
|
||||||
from generic.tables import (
|
from generic.tables import (
|
||||||
CidUserExamTable,
|
CidUserExamTable,
|
||||||
@@ -2335,9 +2335,23 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
if request.user not in exam.author.all() and not request.user.is_superuser:
|
if request.user not in exam.author.all() and not request.user.is_superuser:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
user_exam_data = exam.cid_users.all().prefetch_related("cid_user", "user_user")
|
# Explicitly query the CidUserExam entries for this exam to avoid any
|
||||||
|
# ambiguity from GenericRelation managers and to ensure we only return
|
||||||
|
# submissions that belong to this exam instance.
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
return render(request, "generic/partials/exam_cids_submitted_list.html", {"user_exam_data": user_exam_data, "exam": exam})
|
ct = ContentType.objects.get_for_model(self.Exam)
|
||||||
|
user_exam_data = (
|
||||||
|
CidUserExam.objects.filter(content_type=ct, object_id=exam.pk)
|
||||||
|
.select_related("cid_user", "user_user")
|
||||||
|
.order_by("start_time")
|
||||||
|
)
|
||||||
|
|
||||||
|
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):
|
# def exam_groups_edit(self, request, exam_id):
|
||||||
# exam = get_object_or_404(self.Exam, pk=exam_id)
|
# exam = get_object_or_404(self.Exam, pk=exam_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user