Enhance exam search widget: refactor query handling to support wildcard searches and improve filter options rendering
This commit is contained in:
+65
-63
@@ -6661,10 +6661,6 @@ def exam_search_widget(request):
|
||||
try:
|
||||
model = apps.get_model(model_label) if model_label else None
|
||||
except Exception:
|
||||
model = None
|
||||
|
||||
# If we couldn't resolve a model, return empty fragment rather than raising
|
||||
if model is None:
|
||||
return HttpResponse("")
|
||||
|
||||
# Helper to parse optional boolean GET params
|
||||
@@ -6687,10 +6683,6 @@ def exam_search_widget(request):
|
||||
return False
|
||||
|
||||
# Build queryset
|
||||
qs = model.objects.none()
|
||||
|
||||
# Support field-prefixed queries like `name:term`, `code:term`, `id:123`, `date:YYYY-MM-DD`, `type:app`
|
||||
field_match = re.match(r'^(?P<field>name|code|id|pk|date|type):\s*(?P<term>.+)$', q, flags=re.I)
|
||||
def _field_exists(mdl, field_name):
|
||||
try:
|
||||
mdl._meta.get_field(field_name)
|
||||
@@ -6698,62 +6690,72 @@ def exam_search_widget(request):
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
if field_match:
|
||||
f = field_match.group('field').lower()
|
||||
term = field_match.group('term').strip()
|
||||
try:
|
||||
if f in ('id', 'pk'):
|
||||
qs = model.objects.filter(pk=int(term))
|
||||
elif f == 'name':
|
||||
qs = model.objects.filter(name__icontains=term)
|
||||
elif f == 'code':
|
||||
# try a few plausible code field names if present
|
||||
lookups = []
|
||||
for fn in ('code', 'short_code', 'identifier'):
|
||||
if _field_exists(model, fn):
|
||||
lookups.append(f"{fn}__icontains")
|
||||
if lookups:
|
||||
from django.db.models import Q
|
||||
qobj = Q()
|
||||
for lk in lookups:
|
||||
qobj |= Q(**{lk: term})
|
||||
qs = model.objects.filter(qobj)
|
||||
else:
|
||||
qs = model.objects.none()
|
||||
elif f == 'date':
|
||||
# try a few common date field names
|
||||
date_lookups = []
|
||||
for fn in ('date', 'exam_date', 'start_date'):
|
||||
if _field_exists(model, fn):
|
||||
date_lookups.append(fn)
|
||||
if date_lookups:
|
||||
from django.db.models import Q
|
||||
qobj = Q()
|
||||
for fn in date_lookups:
|
||||
qobj |= Q(**{f"{fn}__startswith": term})
|
||||
qs = model.objects.filter(qobj)
|
||||
else:
|
||||
qs = model.objects.none()
|
||||
elif f == 'type':
|
||||
# restrict by app_label or model name match
|
||||
if term.lower() in (model._meta.app_label.lower(), model._meta.model_name.lower()):
|
||||
qs = model.objects.all()
|
||||
else:
|
||||
qs = model.objects.none()
|
||||
except Exception:
|
||||
qs = model.objects.none()
|
||||
else:
|
||||
# Default: search name and, where available, code-like fields
|
||||
field_lookups = ["name__icontains"]
|
||||
for fn in ('code', 'short_code', 'identifier'):
|
||||
if _field_exists(model, fn):
|
||||
field_lookups.append(f"{fn}__icontains")
|
||||
qs = model.objects.none()
|
||||
|
||||
from django.db.models import Q
|
||||
qobj = Q()
|
||||
for lk in field_lookups:
|
||||
qobj |= Q(**{lk: q})
|
||||
qs = model.objects.filter(qobj)
|
||||
# Support field-prefixed queries like `name:term`, `code:term`, `id:123`, `date:YYYY-MM-DD`.
|
||||
# Treat '*' or '%' as a wildcard meaning "match everything" for the model.
|
||||
field_match = re.match(r'^(?P<field>name|code|id|pk|date|type):\s*(?P<term>.+)$', q, flags=re.I)
|
||||
|
||||
# Wildcard query: return all objects for this model (filters still apply later)
|
||||
if q in ("*", "%"):
|
||||
qs = model.objects.all()
|
||||
else:
|
||||
if field_match:
|
||||
f = field_match.group('field').lower()
|
||||
term = field_match.group('term').strip()
|
||||
try:
|
||||
if f in ('id', 'pk'):
|
||||
qs = model.objects.filter(pk=int(term))
|
||||
elif f == 'name':
|
||||
qs = model.objects.filter(name__icontains=term)
|
||||
elif f == 'code':
|
||||
# try a few plausible code field names if present
|
||||
lookups = []
|
||||
for fn in ('code', 'short_code', 'identifier'):
|
||||
if _field_exists(model, fn):
|
||||
lookups.append(f"{fn}__icontains")
|
||||
if lookups:
|
||||
from django.db.models import Q
|
||||
qobj = Q()
|
||||
for lk in lookups:
|
||||
qobj |= Q(**{lk: term})
|
||||
qs = model.objects.filter(qobj)
|
||||
else:
|
||||
qs = model.objects.none()
|
||||
elif f == 'date':
|
||||
# try a few common date field names
|
||||
date_lookups = []
|
||||
for fn in ('date', 'exam_date', 'start_date'):
|
||||
if _field_exists(model, fn):
|
||||
date_lookups.append(fn)
|
||||
if date_lookups:
|
||||
from django.db.models import Q
|
||||
qobj = Q()
|
||||
for fn in date_lookups:
|
||||
qobj |= Q(**{f"{fn}__startswith": term})
|
||||
qs = model.objects.filter(qobj)
|
||||
else:
|
||||
qs = model.objects.none()
|
||||
elif f == 'type':
|
||||
# restrict by app_label or model name match
|
||||
if term.lower() in (model._meta.app_label.lower(), model._meta.model_name.lower()):
|
||||
qs = model.objects.all()
|
||||
else:
|
||||
qs = model.objects.none()
|
||||
except Exception:
|
||||
qs = model.objects.none()
|
||||
else:
|
||||
# Default: search name and, where available, code-like fields
|
||||
field_lookups = ["name__icontains"]
|
||||
for fn in ('code', 'short_code', 'identifier'):
|
||||
if _field_exists(model, fn):
|
||||
field_lookups.append(f"{fn}__icontains")
|
||||
|
||||
from django.db.models import Q
|
||||
qobj = Q()
|
||||
for lk in field_lookups:
|
||||
qobj |= Q(**{lk: q})
|
||||
qs = model.objects.filter(qobj)
|
||||
|
||||
# Limit results
|
||||
limit = 50 if q in ("*", "%") else 10
|
||||
|
||||
Reference in New Issue
Block a user