Features: 1) Add support for .sat suffix fields in search query with phrase_prefix match; 2) Incorporate lenient fuzzy match and prefix match into a combined query logic; 3) Enhance function_score with additional filtering for better query relevance.
Fixes: 1) Handle missing or empty query input with a more explicit check; 2) Resolve potential response errors by catching `NotFoundError` exceptions in search execution. Extra: Refactor code structure to improve readability and modularity, including better slug generation logic and streamlined image URL handling.
This commit is contained in:
parent
570990fd87
commit
5efac0d5ff
1 changed files with 105 additions and 93 deletions
|
|
@ -30,114 +30,126 @@ SMART_FIELDS = [
|
||||||
"title.ngram^3",
|
"title.ngram^3",
|
||||||
"title.phonetic",
|
"title.phonetic",
|
||||||
"title.auto^4",
|
"title.auto^4",
|
||||||
|
"name.sat^6",
|
||||||
|
"title.sat^4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def process_query(query: str = "", request: Request | None = None):
|
def process_query(query: str = "", request: Request | None = None):
|
||||||
"""
|
if not (query := query.strip()):
|
||||||
Perform a lenient, typo‑tolerant, multi‑index search.
|
|
||||||
|
|
||||||
* Full‑text with fuzziness for spelling mistakes
|
|
||||||
* `bool_prefix` for edge‑ngram autocomplete / “icontains”
|
|
||||||
"""
|
|
||||||
if not query:
|
|
||||||
raise ValueError(_("no search term provided."))
|
raise ValueError(_("no search term provided."))
|
||||||
|
|
||||||
query = query.strip()
|
sat_match = Q(
|
||||||
try:
|
"multi_match",
|
||||||
q = Q(
|
query=query,
|
||||||
"bool",
|
type="phrase_prefix",
|
||||||
should=[
|
fields=[f for f in SMART_FIELDS if ".sat" in f],
|
||||||
Q(
|
)
|
||||||
"multi_match",
|
|
||||||
query=query,
|
|
||||||
fields=SMART_FIELDS,
|
|
||||||
fuzziness="AUTO",
|
|
||||||
operator="and",
|
|
||||||
),
|
|
||||||
Q(
|
|
||||||
"multi_match",
|
|
||||||
query=query,
|
|
||||||
fields=[f for f in SMART_FIELDS if f.endswith(".auto")],
|
|
||||||
type="bool_prefix",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
minimum_should_match=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
functions = [
|
fuzzy_match = Q(
|
||||||
{
|
"multi_match",
|
||||||
"gauss": {
|
query=query,
|
||||||
"sales_rank": {
|
fields=SMART_FIELDS,
|
||||||
"origin": 100,
|
fuzziness="AUTO",
|
||||||
"scale": 500,
|
operator="and",
|
||||||
"offset": 0,
|
)
|
||||||
"decay": 0.3,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"weight": 3,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
boosted = Q(
|
prefix_match = Q(
|
||||||
"function_score",
|
"multi_match",
|
||||||
query=q,
|
query=query,
|
||||||
boost_mode="sum",
|
fields=[f for f in SMART_FIELDS if f.endswith(".auto")],
|
||||||
score_mode="sum",
|
type="bool_prefix",
|
||||||
functions=functions,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
search = (
|
combined = Q(
|
||||||
Search(index=["products", "categories", "brands", "posts"])
|
"bool",
|
||||||
.query(boosted)
|
should=[sat_match, fuzzy_match, prefix_match],
|
||||||
.extra(size=100)
|
minimum_should_match=1,
|
||||||
)
|
)
|
||||||
response = search.execute()
|
|
||||||
|
|
||||||
results: dict = {"products": [], "categories": [], "brands": [], "posts": []}
|
functions = [
|
||||||
for hit in response.hits:
|
{
|
||||||
obj_uuid = getattr(hit, "uuid", None) or hit.meta.id
|
"filter": Q("prefix", **{"name.raw": query.lower()}),
|
||||||
obj_name = (
|
"weight": 5,
|
||||||
getattr(hit, "name", None) or getattr(hit, "title", None) or "N/A"
|
},
|
||||||
)
|
{
|
||||||
obj_slug = ""
|
"gauss": {
|
||||||
raw_slug = getattr(hit, "slug", None)
|
"sales_rank": {
|
||||||
if raw_slug:
|
"origin": 100,
|
||||||
obj_slug = raw_slug
|
"scale": 500,
|
||||||
elif hit.meta.index == "brands":
|
"offset": 0,
|
||||||
obj_slug = slugify(obj_name)
|
"decay": 0.3,
|
||||||
elif hit.meta.index == "categories":
|
|
||||||
obj_slug = slugify(f"{obj_name}")
|
|
||||||
|
|
||||||
image_url = None
|
|
||||||
idx = hit.meta.index
|
|
||||||
if idx == "products" and request:
|
|
||||||
prod = get_object_or_404(Product, uuid=obj_uuid)
|
|
||||||
first = prod.images.order_by("priority").first()
|
|
||||||
if first and first.image:
|
|
||||||
image_url = request.build_absolute_uri(first.image.url)
|
|
||||||
elif idx == "brands" and request:
|
|
||||||
brand = get_object_or_404(Brand, uuid=obj_uuid)
|
|
||||||
if brand.small_logo:
|
|
||||||
image_url = request.build_absolute_uri(brand.small_logo.url)
|
|
||||||
elif idx == "categories" and request:
|
|
||||||
cat = get_object_or_404(Category, uuid=obj_uuid)
|
|
||||||
if cat.image:
|
|
||||||
image_url = request.build_absolute_uri(cat.image.url)
|
|
||||||
|
|
||||||
results[idx].append(
|
|
||||||
{
|
|
||||||
"uuid": str(obj_uuid),
|
|
||||||
"name": obj_name,
|
|
||||||
"slug": obj_slug,
|
|
||||||
"image": image_url,
|
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
|
"weight": 3,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
return results
|
boosted = Q(
|
||||||
|
"function_score",
|
||||||
|
query=combined,
|
||||||
|
boost_mode="sum",
|
||||||
|
score_mode="sum",
|
||||||
|
functions=functions,
|
||||||
|
)
|
||||||
|
|
||||||
|
search = (
|
||||||
|
Search(index=["products", "categories", "brands", "posts"])
|
||||||
|
.query(boosted)
|
||||||
|
.extra(size=100)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = search.execute()
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
results = {"products": [], "categories": [], "brands": [], "posts": []}
|
||||||
|
for hit in response.hits:
|
||||||
|
obj_uuid = getattr(hit, "uuid", None) or hit.meta.id
|
||||||
|
obj_name = getattr(hit, "name", None) or getattr(hit, "title", None) or "N/A"
|
||||||
|
raw_slug = getattr(hit, "slug", None) or ""
|
||||||
|
obj_slug = (
|
||||||
|
raw_slug or slugify(obj_name)
|
||||||
|
if hit.meta.index in {"brands", "categories"}
|
||||||
|
else raw_slug
|
||||||
|
)
|
||||||
|
image_url = None
|
||||||
|
idx = hit.meta.index
|
||||||
|
|
||||||
|
if request:
|
||||||
|
if idx == "products":
|
||||||
|
prod = get_object_or_404(Product, uuid=obj_uuid)
|
||||||
|
first = prod.images.order_by("priority").first()
|
||||||
|
image_url = (
|
||||||
|
request.build_absolute_uri(first.image.url)
|
||||||
|
if first and first.image
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if idx == "brands":
|
||||||
|
brand = get_object_or_404(Brand, uuid=obj_uuid)
|
||||||
|
image_url = (
|
||||||
|
request.build_absolute_uri(brand.small_logo.url)
|
||||||
|
if brand.small_logo
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if idx == "categories":
|
||||||
|
cat = get_object_or_404(Category, uuid=obj_uuid)
|
||||||
|
image_url = (
|
||||||
|
request.build_absolute_uri(cat.image.url) if cat.image else None
|
||||||
|
)
|
||||||
|
|
||||||
|
results[idx].append(
|
||||||
|
{
|
||||||
|
"uuid": str(obj_uuid),
|
||||||
|
"name": obj_name,
|
||||||
|
"slug": obj_slug,
|
||||||
|
"image": image_url,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
LANGUAGE_ANALYZER_MAP = {
|
LANGUAGE_ANALYZER_MAP = {
|
||||||
"ar": "arabic",
|
"ar": "arabic",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue