2021-10-15 03:25:38 -05:00
|
|
|
"""
|
|
|
|
Search implementation used for the peeringdb top search bar, name
|
|
|
|
searches through the api `name_search` filter, as well as advanced
|
|
|
|
search functionality.
|
|
|
|
|
|
|
|
Search logic is handled by django-haystack and whoosh.
|
|
|
|
|
|
|
|
Refer to search_indexes.py for search index definition.
|
|
|
|
"""
|
|
|
|
|
2021-07-10 10:12:35 -05:00
|
|
|
# import time
|
|
|
|
import unidecode
|
2021-07-07 17:57:04 -05:00
|
|
|
from django.conf import settings
|
2021-07-10 10:12:35 -05:00
|
|
|
from django.db.models import Q
|
|
|
|
from haystack.inputs import Exact
|
|
|
|
from haystack.query import SearchQuerySet
|
2021-07-07 17:57:04 -05:00
|
|
|
|
2020-01-08 13:29:58 -06:00
|
|
|
from peeringdb_server.models import (
|
|
|
|
Facility,
|
2021-07-08 21:00:23 -05:00
|
|
|
InternetExchange,
|
|
|
|
IXLanPrefix,
|
2021-07-10 10:12:35 -05:00
|
|
|
Network,
|
|
|
|
NetworkIXLan,
|
|
|
|
Organization,
|
2020-01-08 13:29:58 -06:00
|
|
|
)
|
2021-07-07 17:57:04 -05:00
|
|
|
|
2021-07-08 21:00:23 -05:00
|
|
|
# models considered during autocomplete (quick-search)
|
|
|
|
|
|
|
|
autocomplete_models = [
|
2021-07-07 17:57:04 -05:00
|
|
|
Organization,
|
|
|
|
Network,
|
|
|
|
InternetExchange,
|
|
|
|
Facility,
|
|
|
|
]
|
|
|
|
|
2021-07-08 21:00:23 -05:00
|
|
|
# models considered during standard search
|
|
|
|
|
|
|
|
searchable_models = [
|
|
|
|
Organization,
|
|
|
|
Network,
|
|
|
|
Facility,
|
|
|
|
InternetExchange,
|
|
|
|
NetworkIXLan,
|
|
|
|
IXLanPrefix,
|
|
|
|
# InternetExchangeFacility,
|
|
|
|
# NetworkFacility,
|
|
|
|
# NetworkContact,
|
|
|
|
# IXLan,
|
|
|
|
]
|
|
|
|
|
2019-12-05 16:57:52 +00:00
|
|
|
|
2019-05-02 15:20:20 +00:00
|
|
|
def unaccent(v):
|
|
|
|
return unidecode.unidecode(v).lower()
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2019-12-05 16:57:52 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
def prepare_term(term):
|
|
|
|
try:
|
|
|
|
if len(term) == 1:
|
|
|
|
int(term)
|
|
|
|
term = f"AS{term}"
|
|
|
|
except ValueError:
|
|
|
|
pass
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
return unaccent(term)
|
2018-11-08 19:45:21 +00:00
|
|
|
|
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
def make_search_query(term):
|
|
|
|
if not term:
|
|
|
|
return SearchQuerySet().none()
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
term = prepare_term(term)
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
term_filters = Q(content=term) | Q(content__startswith=term)
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
return SearchQuerySet().filter(term_filters, status=Exact("ok"))
|
2018-11-08 19:45:21 +00:00
|
|
|
|
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
def make_name_search_query(term):
|
|
|
|
if not term:
|
|
|
|
return SearchQuerySet().none()
|
|
|
|
|
|
|
|
term = prepare_term(term)
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
term_filters = Q(name=term) | Q(name__startswith=term)
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
return SearchQuerySet().filter(term_filters, status=Exact("ok"))
|
2018-11-08 19:45:21 +00:00
|
|
|
|
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
def make_autocomplete_query(term):
|
|
|
|
if not term:
|
|
|
|
return SearchQuerySet().none()
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
term = prepare_term(term)
|
|
|
|
return SearchQuerySet().autocomplete(auto=term).filter(status=Exact("ok"))
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
|
|
|
|
def search(term, autocomplete=False):
|
2018-11-08 19:45:21 +00:00
|
|
|
"""
|
2021-10-15 03:25:38 -05:00
|
|
|
Search searchable objects (ixp, network, facility ...) by term.
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-10-15 03:25:38 -05:00
|
|
|
Returns result dict.
|
2018-11-08 19:45:21 +00:00
|
|
|
"""
|
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
# t0 = time.time()
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
if autocomplete:
|
2021-07-08 21:00:23 -05:00
|
|
|
search_query = make_autocomplete_query(term).models(*autocomplete_models)
|
2021-07-07 17:57:04 -05:00
|
|
|
limit = settings.SEARCH_RESULTS_AUTOCOMPLETE_LIMIT
|
2018-11-08 19:45:21 +00:00
|
|
|
else:
|
2021-07-08 21:00:23 -05:00
|
|
|
search_query = make_search_query(term).models(*searchable_models)
|
2021-07-07 17:57:04 -05:00
|
|
|
limit = settings.SEARCH_RESULTS_LIMIT
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-08 21:00:23 -05:00
|
|
|
categories = ("fac", "ix", "net", "org")
|
2021-07-10 10:12:35 -05:00
|
|
|
result = {tag: [] for tag in categories}
|
|
|
|
pk_map = {tag: {} for tag in categories}
|
2021-07-07 17:57:04 -05:00
|
|
|
|
2021-10-12 11:05:25 -05:00
|
|
|
# if term is an exact asn match, ensure that the matching
|
|
|
|
# network is always appended as the first entry
|
|
|
|
# issue #232
|
|
|
|
|
|
|
|
try:
|
|
|
|
asn_match = Network.objects.get(asn=term)
|
|
|
|
append_result(
|
|
|
|
"net",
|
|
|
|
asn_match.pk,
|
|
|
|
asn_match.search_result_name,
|
|
|
|
asn_match.org_id,
|
|
|
|
None,
|
|
|
|
result,
|
|
|
|
pk_map,
|
|
|
|
)
|
|
|
|
except (Network.DoesNotExist, ValueError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
# add entries to the result by order of scoring with the
|
|
|
|
# highest scored on top (beginning of list)
|
|
|
|
|
2021-07-13 08:21:43 -05:00
|
|
|
for sq in search_query[:limit]:
|
2021-07-07 17:57:04 -05:00
|
|
|
model = sq.model
|
2021-08-18 08:21:22 -05:00
|
|
|
model.HandleRef.tag
|
2021-07-07 17:57:04 -05:00
|
|
|
|
2021-07-08 21:00:23 -05:00
|
|
|
categorize(sq, result, pk_map)
|
|
|
|
|
|
|
|
# print("done", time.time() - t0)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def categorize(sq, result, pk_map):
|
|
|
|
|
|
|
|
if getattr(sq, "result_name", None):
|
|
|
|
# main entity
|
|
|
|
tag = sq.model.HandleRef.tag
|
2021-07-07 17:57:04 -05:00
|
|
|
if tag == "org":
|
2021-07-08 21:00:23 -05:00
|
|
|
org_id = int(sq.pk)
|
2021-07-07 17:57:04 -05:00
|
|
|
else:
|
|
|
|
org_id = sq.org_id
|
2021-07-08 21:00:23 -05:00
|
|
|
append_result(tag, int(sq.pk), sq.result_name, org_id, None, result, pk_map)
|
|
|
|
return
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-08 21:00:23 -05:00
|
|
|
# secondary entities
|
2021-07-07 17:57:04 -05:00
|
|
|
|
2021-07-08 21:00:23 -05:00
|
|
|
for tag in result.keys():
|
|
|
|
if not getattr(sq, f"{tag}_result_name", None):
|
|
|
|
continue
|
|
|
|
|
|
|
|
org_id = int(getattr(sq, f"{tag}_org_id", 0))
|
|
|
|
name = getattr(sq, f"{tag}_result_name")
|
|
|
|
pk = int(getattr(sq, f"{tag}_id", 0))
|
|
|
|
sub_name = getattr(sq, f"{tag}_sub_result_name")
|
|
|
|
append_result(tag, pk, name, org_id, sub_name, result, pk_map)
|
|
|
|
|
|
|
|
|
|
|
|
def append_result(tag, pk, name, org_id, sub_name, result, pk_map):
|
|
|
|
|
|
|
|
if pk in pk_map[tag]:
|
|
|
|
return
|
|
|
|
|
|
|
|
pk_map[tag][pk] = True
|
|
|
|
|
|
|
|
result[tag].append(
|
|
|
|
{"id": pk, "name": name, "org_id": int(org_id), "sub_name": sub_name}
|
|
|
|
)
|