Enhance exam search widget: add error handling for model retrieval and improve query parsing
This commit is contained in:
+74
-8
@@ -6658,7 +6658,14 @@ def exam_search_widget(request):
|
|||||||
# Support wildcard queries
|
# Support wildcard queries
|
||||||
q = q_raw
|
q = q_raw
|
||||||
|
|
||||||
model = apps.get_model(model_label)
|
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
|
# Helper to parse optional boolean GET params
|
||||||
def _get_bool_param(name):
|
def _get_bool_param(name):
|
||||||
@@ -6680,14 +6687,73 @@ def exam_search_widget(request):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Build queryset
|
# Build queryset
|
||||||
qs = None
|
qs = model.objects.none()
|
||||||
field_lookups = ["name__icontains"]
|
|
||||||
for lookup in field_lookups:
|
|
||||||
qs = model.objects.filter(**{lookup: q})
|
|
||||||
if qs.exists():
|
|
||||||
break
|
|
||||||
|
|
||||||
logger.error(qs)
|
# 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)
|
||||||
|
return True
|
||||||
|
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")
|
||||||
|
|
||||||
|
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 results
|
||||||
limit = 50 if q in ("*", "%") else 10
|
limit = 50 if q in ("*", "%") else 10
|
||||||
|
|||||||
Reference in New Issue
Block a user