Add bulk update functionality for trainees from spreadsheet input

This commit is contained in:
Ross
2025-10-27 13:07:50 +00:00
parent 5d0eb56b37
commit d9602eeae9
4 changed files with 169 additions and 0 deletions
+115
View File
@@ -4254,6 +4254,121 @@ def trainees(request, grade: None | str = None):
return render(request, "generic/trainees.html", context)
@user_is_cid_user_manager
def trainees_bulk_update_from_spreadsheet(request):
"""Bulk update trainees based on pasted spreadsheet-like data.
The view works in two stages:
- Preview: parse the pasted text and show rows with any matched user / supervisor
- Apply: apply grade and supervisor updates for matched users
"""
logger.debug("Trainees bulk update called")
import re
preview = []
report = None
if request.method == "POST":
action = request.POST.get("action", "preview")
data = request.POST.get("data", "")
lines = [l.strip() for l in data.splitlines()]
current_grade = None
for line in lines:
if not line:
continue
# detect grade headings like 'Year 1 - Group 1' -> ST1
m = re.search(r"Year\s*(\d+)", line, re.IGNORECASE)
if m:
num = m.group(1)
current_grade = f"ST{num}"
continue
# split columns by tab or two+ spaces
parts = re.split(r"\t| {2,}", line)
if len(parts) < 2:
# maybe single-space separated; try splitting on tab or single tab
parts = [p.strip() for p in line.split("\t") if p.strip()]
if not parts:
continue
name_part = parts[0].strip()
supervisor_part = parts[1].strip() if len(parts) > 1 else ""
# normalize name (remove parenthetical annotations)
norm_name = re.sub(r"\s*\([^)]*\)", "", name_part).strip()
# split into tokens; assume first token is first name, last token is last name
tokens = [t for t in re.split(r"\s+", norm_name) if t]
first = tokens[0] if tokens else ""
last = tokens[-1] if tokens else ""
matched_user = None
if first and last:
qs = User.objects.filter(first_name__iexact=first, last_name__iexact=last)
if not qs.exists():
qs = User.objects.filter(last_name__iexact=last)
if qs.exists():
matched_user = qs.first()
matched_supervisor = None
if supervisor_part:
sup_qs = Supervisor.objects.filter(name__icontains=supervisor_part)
if sup_qs.exists():
matched_supervisor = sup_qs.first()
preview.append(
{
"raw": line,
"name": name_part,
"norm_name": norm_name,
"first": first,
"last": last,
"supervisor_text": supervisor_part,
"matched_user": matched_user,
"matched_supervisor": matched_supervisor,
"grade": current_grade,
}
)
if action == "apply":
updated = 0
skipped = 0
for row in preview:
user = row.get("matched_user")
if not user:
skipped += 1
continue
profile = user.userprofile
# apply grade if present
grade_name = row.get("grade")
if grade_name:
grade_obj, _ = UserGrades.objects.get_or_create(name=grade_name)
profile.grade = grade_obj
# apply supervisor if matched
sup = row.get("matched_supervisor")
if sup:
profile.supervisor = sup
profile.save()
updated += 1
report = {"updated": updated, "skipped": skipped}
return render(
request,
"generic/trainees_bulk_update.html",
{"preview": preview, "report": report},
)
def create_trainee(request, context=None):
return create_user(request, context, trainee=True)