fix api contact search

This commit is contained in:
javad committed 2026-09-30 11:09:12 +03:30
1 parent 04d2bc957f
commit 39e64854b8
4 files changed
+124 -33

No files matched your search

+66 -26
View File
@@ -17,12 +17,74 @@ def _dialable_number(value):
return number if 0 < len(number) <= 64 and re.fullmatch(r"[0-9+*#]+", number) else ""
def _canonical_number(value):
number = _dialable_number(value)
if not number or not re.fullmatch(r"\+[0-9]+|[0-9*#]+", number):
return ""
if number.isdigit() and number.startswith("00") and len(number) > 2:
return "+" + number[2:]
if number.isdigit() and number.startswith("98") and len(number) >= 12:
return "+" + number
if number.isdigit() and number.startswith("0") and len(number) > 1:
return "+98" + number[1:]
if number.isdigit() and len(number) == 10:
return "+98" + number
return number
def _phone_variants(value):
canonical = _canonical_number(value)
if not canonical:
return ()
variants = {canonical, _dialable_number(value)}
if canonical.startswith("+98"):
variants.update({"0" + canonical[3:], "00" + canonical[1:], canonical[1:]})
elif canonical.startswith("+"):
variants.add("00" + canonical[1:])
return variants
def _display_number(value):
canonical = _canonical_number(value)
return "0" + canonical[3:] if canonical.startswith("+98") else canonical
def _json(data, status=200):
response = JsonResponse(data, status=status, json_dumps_params={"ensure_ascii": False})
response["Cache-Control"] = "no-store"
return response
def _matching_contacts(contacts, query):
phone_query = query.translate(_PHONE_DIGITS)
if re.fullmatch(r"[+*#0-9().\s-]+", phone_query):
variants = _phone_variants(phone_query)
return contacts.filter(user__mobile__in=variants).order_by("pk") if variants else contacts.none()
for word in re.split(r"\s+", query):
contacts = contacts.filter(
Q(first_name__icontains=word) | Q(last_name__icontains=word) |
Q(organization_name__icontains=word) | Q(user__first_name__icontains=word) |
Q(user__last_name__icontains=word) | Q(user__mobile__icontains=word)
)
preferred = (
Q(first_name__iexact=query) | Q(last_name__iexact=query) |
Q(organization_name__iexact=query) | Q(user__first_name__iexact=query) |
Q(user__last_name__iexact=query)
)
starts_with = (
Q(first_name__istartswith=query) | Q(last_name__istartswith=query) |
Q(organization_name__istartswith=query) | Q(user__first_name__istartswith=query) |
Q(user__last_name__istartswith=query)
)
return contacts.annotate(_search_rank=Case(
When(preferred, then=Value(0)),
When(starts_with, then=Value(1)),
default=Value(2), output_field=IntegerField(),
)).order_by("_search_rank", "pk")
@require_GET
def phone_contacts(request):
authorization = request.headers.get("Authorization", "")
@@ -50,35 +112,13 @@ def phone_contacts(request):
Q(first_name__gt="") | Q(last_name__gt="") | Q(organization_name__gt="") |
Q(user__first_name__gt="") | Q(user__last_name__gt="")
)
for word in re.split(r"\s+", query):
contacts = contacts.filter(
Q(first_name__icontains=word) | Q(last_name__icontains=word) |
Q(organization_name__icontains=word) | Q(user__first_name__icontains=word) |
Q(user__last_name__icontains=word) | Q(user__mobile__icontains=word)
)
preferred = (
Q(first_name__iexact=query) | Q(last_name__iexact=query) |
Q(organization_name__iexact=query) | Q(user__first_name__iexact=query) |
Q(user__last_name__iexact=query)
)
starts_with = (
Q(first_name__istartswith=query) | Q(last_name__istartswith=query) |
Q(organization_name__istartswith=query) | Q(user__first_name__istartswith=query) |
Q(user__last_name__istartswith=query)
)
contacts = contacts.annotate(_search_rank=Case(
When(preferred, then=Value(0)),
When(starts_with, then=Value(1)),
default=Value(2), output_field=IntegerField(),
)).order_by("_search_rank", "pk")
results = []
for profile in contacts[:limit]:
for profile in _matching_contacts(contacts, query)[:limit]:
name = (f"{profile.first_name} {profile.last_name}".strip() or
f"{profile.user.first_name} {profile.user.last_name}".strip() or
profile.organization_name.strip())[:120]
number = _dialable_number(profile.user.local_mobile)
number = _display_number(profile.user.mobile)
if name and number:
results.append({"name": name, "phones": [{"type": "همراه", "number": number}]})
label = "همراه" if re.fullmatch(r"09[0-9]{9}", number) else "تلفن"
results.append({"name": name, "mobile": number, "phones": [{"type": label, "number": number}]})
return _json({"contacts": results})