Refactor trainees bulk update logic to improve user matching criteria and enhance clarity in code comments

This commit is contained in:
Ross
2025-10-27 14:03:21 +00:00
parent d9602eeae9
commit 4dc07b1860
2 changed files with 59 additions and 44 deletions
@@ -1,50 +1,50 @@
{% extends 'generic/base.html' %}
{% block content %}
<h2>Bulk update trainees (paste spreadsheet text)</h2>
<h2>Bulk update trainees (paste spreadsheet text)</h2>
<p>Paste the spreadsheet rows below. Headings like "Year 1 - Group 1" are used to set the grade (Year N -> STN). Rows should contain "Name <tab or 2+spaces> Supervisor".</p>
<p>Paste the spreadsheet rows below. Headings like "Year 1 - Group 1" are used to set the grade (Year N -> STN). Rows should contain "Name <tab or 2+spaces> Supervisor".</p>
<form method="post">
{% csrf_token %}
<textarea name="data" rows="18" style="width:100%">{% if request.POST.data %}{{ request.POST.data }}{% endif %}</textarea>
<div style="margin-top:8px">
<button type="submit" name="action" value="preview" class="btn btn-primary">Preview</button>
<button type="submit" name="action" value="apply" class="btn btn-danger" onclick="return confirm('This will apply updates to matched trainee profiles. Are you sure?')">Apply updates</button>
</div>
</form>
<form method="post">
{% csrf_token %}
<textarea name="data" rows="18" style="width:100%">{% if request.POST.data %}{{ request.POST.data }}{% endif %}</textarea>
<div style="margin-top:8px">
<button type="submit" name="action" value="preview" class="btn btn-primary">Preview</button>
<button type="submit" name="action" value="apply" class="btn btn-danger" onclick="return confirm('This will apply updates to matched trainee profiles. Are you sure?')">Apply updates</button>
</div>
</form>
{% if preview %}
<h3>Preview ({{ preview|length }} rows)</h3>
<table class="table table-sm">
<thead>
<tr>
<th>Raw</th>
<th>Name</th>
<th>Grade</th>
<th>Matched user</th>
<th>Supervisor text</th>
<th>Matched supervisor</th>
</tr>
</thead>
<tbody>
{% for r in preview %}
<tr>
<td><pre style="margin:0">{{ r.raw }}</pre></td>
<td>{{ r.norm_name }}</td>
<td>{{ r.grade }}</td>
<td>{% if r.matched_user %}<a href="{% url 'account_profile' r.matched_user.username %}">{{ r.matched_user.first_name }} {{ r.matched_user.last_name }}</a>{% else %}<span style="color:red">No match</span>{% endif %}</td>
<td>{{ r.supervisor_text }}</td>
<td>{% if r.matched_supervisor %}<a href="{% url 'generic:supervisor_detail' r.matched_supervisor.pk %}">{{ r.matched_supervisor.name }}</a>{% else %}<span style="color:red">No match</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if preview %}
<h3>Preview ({{ preview|length }} rows)</h3>
<table class="table table-sm">
<thead>
<tr>
<th>Raw</th>
<th>Name</th>
<th>Grade</th>
<th>Matched user</th>
<th>Supervisor text</th>
<th>Matched supervisor</th>
</tr>
</thead>
<tbody>
{% for r in preview %}
<tr>
<td><pre style="margin:0">{{ r.raw }}</pre></td>
<td>{{ r.norm_name }}</td>
<td>{{ r.grade }}</td>
<td>{% if r.matched_user %}<a href="{% url 'account_profile' r.matched_user.username %}">{{ r.matched_user.first_name }} {{ r.matched_user.last_name }}</a>{% else %}<span style="color:red">No match</span>{% endif %}</td>
<td>{{ r.supervisor_text }}</td>
<td>{% if r.matched_supervisor %}<a href="{% url 'generic:supervisor_detail' r.matched_supervisor.pk %}">{{ r.matched_supervisor.name }}</a>{% else %}<span style="color:red">No match</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if report %}
<h3>Apply report</h3>
<p>Updated: {{ report.updated }}; Skipped (no user matched): {{ report.skipped }}</p>
{% endif %}
{% if report %}
<h3>Apply report</h3>
<p>Updated: {{ report.updated }}; Skipped (no user matched): {{ report.skipped }}</p>
{% endif %}
{% endblock %}
+18 -3
View File
@@ -4262,7 +4262,6 @@ def trainees_bulk_update_from_spreadsheet(request):
- 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
@@ -4309,11 +4308,27 @@ def trainees_bulk_update_from_spreadsheet(request):
matched_user = None
if first and last:
# Prefer an exact first+last match
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()
else:
# Fallback: only match by last name when the last-name query returns
# a single candidate AND the candidate's first name reasonably
# matches the provided first token. This avoids ambiguous matches
# (e.g. Maria vs Stephanie both 'Bailey'). We accept a match when
# the candidate first name startswith the first 3 chars of the
# provided first token, or contains it as a substring (case-ins).
candidates = User.objects.filter(last_name__iexact=last)
if candidates.count() == 1:
candidate = candidates.first()
cand_first = (candidate.first_name or "").strip()
if cand_first:
# take at most 3 chars for prefix comparison
prefix = first[:3].lower()
cand_first_low = cand_first.lower()
if cand_first_low.startswith(prefix) or first.lower() in cand_first_low:
matched_user = candidate
matched_supervisor = None
if supervisor_part: