Enhance user search widget with wildcard support and field-specific queries; improve logging in bulk add groups

This commit is contained in:
Ross
2025-11-12 21:54:51 +00:00
parent 208252a858
commit d8055a509c
+72 -7
View File
@@ -389,13 +389,18 @@ def exam_collection_bulk_add_groups(request, collection_id):
"""
collection = get_object_or_404(ExamCollection, pk=collection_id)
logger.debug(f"exam_collection_bulk_add_groups called with method={request.method} by user={request.user.username}")
if request.method == "GET":
logger.debug(f"exam_collection_bulk_add_groups GET request by user={request.user.username}")
form = ExamCollectionBulkAddGroupsForm(user=request.user)
return render(request, "generic/partials/examcollection_bulk_add_groups_form.html", {"form": form, "collection": collection})
# POST
form = ExamCollectionBulkAddGroupsForm(request.POST, user=request.user)
if not form.is_valid():
logger.debug(f"exam_collection_bulk_add_groups form invalid: {form.errors}")
return render(request, "generic/partials/examcollection_bulk_add_groups_form.html", {"form": form, "collection": collection})
cid_groups = form.cleaned_data.get("cid_user_groups")
@@ -5937,11 +5942,62 @@ def user_search_widget(request):
Returns a small HTML fragment listing up to 10 matching users. Each
result will call `addUserToField(field, id, text)` when clicked.
"""
q = (request.GET.get("q") or "").strip()
import re
q_raw = (request.GET.get("q") or "").strip()
field = request.GET.get("field") or request.GET.get("row")
if not q:
if not q_raw:
return HttpResponse("")
# Support wildcard '*' (return many results) and field-scoped queries like 'name:smith' or 'email:foo'
q = q_raw
# If user explicitly asks for wildcard, return an unfiltered queryset (limited)
if q in ("*", "%"):
users_qs = User.objects.all()
else:
# field-specific search syntax: field:term
m = re.match(r"^(?P<field>\w+):(?P<term>.+)$", q)
if m:
f = m.group("field").lower()
term = m.group("term").strip()
if term in ("*", "%"):
users_qs = User.objects.all()
else:
if f in ("name", "fullname", "full_name"):
users_qs = User.objects.filter(
Q(first_name__icontains=term) | Q(last_name__icontains=term)
)
elif f in ("first", "first_name"):
users_qs = User.objects.filter(first_name__icontains=term)
elif f in ("last", "last_name"):
users_qs = User.objects.filter(last_name__icontains=term)
elif f == "email":
users_qs = User.objects.filter(email__icontains=term)
elif f == "username":
users_qs = User.objects.filter(username__icontains=term)
elif f in ("id", "pk"):
try:
users_qs = User.objects.filter(pk=int(term))
except Exception:
users_qs = User.objects.none()
elif f == "grade":
# allow grade name or id
try:
gid = int(term)
users_qs = User.objects.filter(userprofile__grade_id=gid)
except Exception:
users_qs = User.objects.filter(userprofile__grade__name__icontains=term)
else:
# unknown field: fallback to general search
users_qs = User.objects.filter(
Q(first_name__icontains=term)
| Q(last_name__icontains=term)
| Q(email__icontains=term)
| Q(username__icontains=term)
)
else:
# default substring search (icontains)
users_qs = User.objects.filter(
Q(first_name__icontains=q)
| Q(last_name__icontains=q)
@@ -5949,19 +6005,28 @@ def user_search_widget(request):
| Q(username__icontains=q)
)
# optional grade filter (UserProfile.grade FK)
grade = request.GET.get('grade')
# optional grade filter (UserProfile.grade FK) still applies and will narrow results
grade = request.GET.get("grade")
if grade:
try:
users_qs = users_qs.filter(userprofile__grade_id=int(grade))
except Exception:
pass
users_qs = users_qs.order_by("last_name", "first_name")[:10]
# Limit results to avoid returning too many rows; wildcard returns up to 50
limit = 50 if q in ("*", "%") or q_raw in ("*", "%") else 10
users_qs = users_qs.order_by("last_name", "first_name")[:limit]
results = [{"id": u.pk, "text": f"{u.get_full_name() or u.username} - {u.email or ''}"} for u in users_qs]
results = [
{"id": u.pk, "text": f"{u.get_full_name() or u.username} - {u.email or ''}"}
for u in users_qs
]
return render(request, "generic/partials/user_search_widget_results.html", {"results": results, "field": field, "q": q})
return render(
request,
"generic/partials/user_search_widget_results.html",
{"results": results, "field": field, "q": q_raw},
)
@login_required