Refactor exam search widget: streamline filtering logic and enhance queryset efficiency

This commit is contained in:
Ross
2026-02-16 13:49:17 +00:00
parent 6e30fcc19f
commit c61f92ae9e
2 changed files with 22 additions and 33 deletions
+22 -22
View File
@@ -6757,11 +6757,6 @@ def exam_search_widget(request):
qobj |= Q(**{lk: q}) qobj |= Q(**{lk: q})
qs = model.objects.filter(qobj) qs = model.objects.filter(qobj)
# Limit results
limit = 50 if q in ("*", "%") else 10
qs = qs.order_by("name")[:limit]
# Apply optional filters from GET params to the per-model queryset # Apply optional filters from GET params to the per-model queryset
# Only include filters that were supplied in GET _and_ where the model # Only include filters that were supplied in GET _and_ where the model
# actually exposes the field. This prevents models without a given # actually exposes the field. This prevents models without a given
@@ -6777,26 +6772,31 @@ def exam_search_widget(request):
continue continue
extra_filters[pname] = val extra_filters[pname] = val
# Apply filters at the queryset level for efficiency
if extra_filters:
try:
qs = qs.filter(**extra_filters)
except Exception:
# If a filter cannot be applied at DB level, fall back to leaving qs unchanged
pass
# Limit results (wildcard returns up to 50)
limit = 50 if q in ("*", "%") else 10
qs = qs.order_by("name")[:limit]
results = [] results = []
logger.error(f"Exam search widget: model={model}, q='{q}', extra_filters={extra_filters}, initial_count={qs.count() if qs is not None else 'N/A'}") logger.error(f"Exam search widget: model={model}, q='{q}', extra_filters={extra_filters}, post_filter_count={qs.count() if qs is not None else 'N/A'}")
for e in qs: for e in qs:
# apply any extra_filters defensively (models may not have fields) if not user_can_view_exam(e, request.user):
skip = False
for k, v in extra_filters.items():
if getattr(e, k, None) != v:
skip = True
break
if skip:
continue continue
if user_can_view_exam(e, request.user): results.append({
results.append({ "id": e.pk,
"id": e.pk, "text": str(e),
"text": str(e), "archive": getattr(e, "archive", False),
"archive": getattr(e, "archive", False), "open_access": getattr(e, "open_access", False),
"open_access": getattr(e, "open_access", False), "exam_mode": getattr(e, "exam_mode", False),
"exam_mode": getattr(e, "exam_mode", False), "candidates_only": getattr(e, "candidates_only", False),
"candidates_only": getattr(e, "candidates_only", False), })
})
logger.error(f"Exam search widget: returning {len(results)} results after filtering") logger.error(f"Exam search widget: returning {len(results)} results after filtering")
-11
View File
@@ -564,17 +564,6 @@ class ExamSearchSelectMultipleWidget:
return {{ cleaned: cleaned, filters: filters }}; return {{ cleaned: cleaned, filters: filters }};
}} }}
function parseFilterTokens(q) {{
const filters = {{}};
if (!q) return {{ cleaned: '', filters: filters }};
let cleaned = q.replace(tokenRe, function(m, name, val) {{
filters[name] = /^(1|true|yes)$/i.test(val);
return ' ';
}});
cleaned = cleaned.replace(/\s+/g,' ').trim();
return {{ cleaned: cleaned, filters: filters }};
}}
function fetchResults(rawQ) {{ function fetchResults(rawQ) {{
const parsed = parseFilterTokens(rawQ || ''); const parsed = parseFilterTokens(rawQ || '');
const q = parsed.cleaned; const q = parsed.cleaned;