Implement bulk user management for exams: add user bulk edit endpoint, update templates for user list rendering, and enhance toggle functionality with HTMX support

This commit is contained in:
Ross
2025-12-15 12:01:01 +00:00
parent 158db133b3
commit 508f8a22a2
5 changed files with 215 additions and 89 deletions
+94 -2
View File
@@ -2412,8 +2412,15 @@ class ExamViews(View, LoginRequiredMixin):
else:
app_exam_map[self.app_name].remove(pk)
data = {"status": "success", "added": add}
return JsonResponse(data, status=200)
# Return rendered list-item HTML for HTMX (or fetch fallback) and an HX-Trigger
html = render_to_string(
'generic/partials/user_user_li.html',
{'user': user_user, 'exam': exam, 'current': add},
request=request,
)
response = HttpResponse(html, content_type='text/html')
response['HX-Trigger'] = json.dumps({'user_toggled': {'pk': user_user.pk, 'added': add}})
return response
if "add_exam_questions" in request.POST:
question_ids = json.loads(request.POST.get("add_exam_questions"))
@@ -2601,6 +2608,91 @@ class ExamViews(View, LoginRequiredMixin):
response['HX-Trigger'] = json.dumps({'cid_bulk_toggled': {'changed': changed, 'add': add_flag, 'mixed': add_flag is None}})
return response
@method_decorator(login_required)
def exam_user_bulk_edit(self, request, pk):
"""Bulk add/remove User objects to/from an exam. Expects POST with 'bulk_pks' (JSON list)
and 'add' ('true'/'false'). Returns a rendered user list partial for HTMX swaps.
"""
if request.method != 'POST':
return JsonResponse({'status': 'error, invalid method'}, status=400)
if request.user.groups.filter(name="cid_user_manager").exists():
pass
elif not self.check_user_edit_access(request.user, exam_id=pk):
data = {"status": "invalid permisions"}
return JsonResponse(data, status=403)
exam = get_object_or_404(self.Exam, pk=pk)
bulk_pks_raw = request.POST.get('bulk_pks') or request.POST.get('bulk_pks[]')
items = []
if bulk_pks_raw:
try:
items = json.loads(bulk_pks_raw)
except Exception:
try:
items = [int(x) for x in bulk_pks_raw.split(',') if x.strip()]
except Exception:
items = []
else:
sel = request.POST.getlist('selection') or request.POST.getlist('selection[]')
for s in sel:
try:
items.append(int(s))
except Exception:
continue
add_flag = None
if 'add' in request.POST:
add_flag = request.POST.get('add') == 'true'
changed = []
for entry in items:
if isinstance(entry, dict):
uid = entry.get('pk')
this_add = bool(entry.get('add'))
else:
uid = entry
this_add = add_flag if add_flag is not None else True
try:
user_obj = User.objects.get(pk=uid)
except Exception:
continue
app_exam_map = {}
app_exam_map["rapids"] = user_obj.user_rapid_exams
app_exam_map["shorts"] = user_obj.user_shorts_exams
app_exam_map["anatomy"] = user_obj.user_anatomy_exams
app_exam_map["longs"] = user_obj.user_longs_exams
app_exam_map["physics"] = user_obj.user_physics_exams
app_exam_map["sbas"] = user_obj.user_sba_exams
app_exam_map["atlas"] = user_obj.user_casecollection_exams
try:
if this_add:
app_exam_map[self.app_name].add(pk)
else:
app_exam_map[self.app_name].remove(pk)
changed.append(user_obj.pk)
except Exception:
continue
# Rebuild lists for rendering
current_user_users = exam.valid_user_users.all()
exam_groups = exam.user_user_groups.all()
available_user_users = User.objects.filter(user_groups__in=exam_groups).difference(current_user_users)
html = render_to_string('generic/partials/user_user_list.html', {
'exam': exam,
'current_user_users': current_user_users,
'available_user_users': available_user_users,
}, request=request)
response = HttpResponse(html, content_type='text/html')
response['HX-Trigger'] = json.dumps({'user_bulk_toggled': {'changed': changed, 'add': add_flag, 'mixed': add_flag is None}})
return response
@method_decorator(login_required)
def mark_overview(self, request, pk):
exam: ExamBase = get_object_or_404(self.Exam, pk=pk)