From 1c8f86230305b58cfb2440f8eb339959a7b46d2f Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 27 Oct 2025 14:26:55 +0000 Subject: [PATCH] Add user search functionality for manual trainee matching in bulk update --- .../generic/trainees_bulk_update.html | 69 ++++++++++++++++++- generic/urls.py | 1 + generic/views.py | 40 +++++++++-- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/generic/templates/generic/trainees_bulk_update.html b/generic/templates/generic/trainees_bulk_update.html index 109a97d0..10b828ce 100644 --- a/generic/templates/generic/trainees_bulk_update.html +++ b/generic/templates/generic/trainees_bulk_update.html @@ -33,7 +33,21 @@
{{ r.raw }}
{{ r.norm_name }} {{ r.grade }} - {% if r.matched_user %}{{ r.matched_user.first_name }} {{ r.matched_user.last_name }}{% else %}No match{% endif %} + + {% if r.matched_user %} + {{ r.matched_user.first_name }} {{ r.matched_user.last_name }} + {% else %} +
+ No match +
+ + +
+
+ +
+ {% endif %} + {{ r.supervisor_text }} {% if r.matched_supervisor %}{{ r.matched_supervisor.name }}{% else %}No match{% endif %} @@ -48,3 +62,56 @@ {% endif %} {% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/generic/urls.py b/generic/urls.py index 4b0c3aed..6d4fb9cc 100755 --- a/generic/urls.py +++ b/generic/urls.py @@ -57,6 +57,7 @@ urlpatterns = [ views.SupervisorAutocomplete.as_view(model=Supervisor), name="supervisor-autocomplete", ), + path("user-search/", views.user_search, name="user_search"), path( "cids/manage//update", views.CidUserUpdate.as_view(), name="update_cid" ), diff --git a/generic/views.py b/generic/views.py index eb584762..e3512665 100644 --- a/generic/views.py +++ b/generic/views.py @@ -3067,13 +3067,12 @@ class GenericViewBase: Accepts POST or GET parameters: - category: integer primary key of category (optional) - - status: one of the status codes (AC, OD, ER, RJ, IP), or 'UNREVIEWED' or 'ANY' - If not supplied, the default behavior is to return only unreviewed questions. + - status: one of the status codes (AC, OD, ER, RJ, IP), or 'UNREVIEWED' or 'ANY' """ # Read filters category = request.POST.get("category") or request.GET.get("category") - status = request.POST.get("status") or request.GET.get("status") or "UNREVIEWED" + status = request.POST.get("status") or request.GET.get("status") or "ANY" # How many matching questions to skip (useful for Skip button). Expected integer >= 0. try: skip = int(request.POST.get("skip") or request.GET.get("skip") or 0) @@ -4277,7 +4276,7 @@ def trainees_bulk_update_from_spreadsheet(request): current_grade = None - for line in lines: + for idx, line in enumerate(lines): if not line: continue @@ -4340,6 +4339,7 @@ def trainees_bulk_update_from_spreadsheet(request): preview.append( { "raw": line, + "row_index": idx, "name": name_part, "norm_name": norm_name, "first": first, @@ -4355,7 +4355,21 @@ def trainees_bulk_update_from_spreadsheet(request): updated = 0 skipped = 0 for row in preview: - user = row.get("matched_user") + # Check for a manual override from the submitted form + manual_key = f"manual_user_{row.get('row_index')}" + manual_val = request.POST.get(manual_key) + + user = None + if manual_val: + try: + user = User.objects.get(pk=int(manual_val)) + except Exception: + user = None + + # Fall back to automatic match + if not user: + user = row.get("matched_user") + if not user: skipped += 1 continue @@ -4385,6 +4399,22 @@ def trainees_bulk_update_from_spreadsheet(request): ) +@user_is_cid_user_manager +def user_search(request): + """Simple JSON endpoint to search users by name/email for manual matching.""" + q = request.GET.get("q", "").strip() + results = [] + if q: + qs = User.objects.filter( + Q(first_name__icontains=q) | Q(last_name__icontains=q) | Q(email__icontains=q) + ).order_by("last_name", "first_name")[:50] + + for u in qs: + results.append({"id": u.pk, "text": f"{u.first_name} {u.last_name} ({u.email})"}) + + return JsonResponse({"results": results}) + + def create_trainee(request, context=None): return create_user(request, context, trainee=True)