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
|
2022-01-10 05:40:26 -08:00
|
|
|
import re
|
2022-01-11 08:56:47 -06:00
|
|
|
|
2021-07-10 10:12:35 -05:00
|
|
|
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
|
|
|
|
2022-01-10 05:40:26 -08:00
|
|
|
ONLY_DIGITS = re.compile(r"^[0-9]+$")
|
|
|
|
# These are not exact, but should be good enough
|
|
|
|
PARTIAL_IPV4_ADDRESS = re.compile(r"^([0-9]{1,3}\.){1,3}([0-9]{1,3})?$")
|
|
|
|
PARTIAL_IPV6_ADDRESS = re.compile(r"^([0-9A-Fa-f]{1,4}|:):[0-9A-Fa-f:]*$")
|
|
|
|
|
|
|
|
|
2019-05-02 15:20:20 +00:00
|
|
|
def unaccent(v):
|
2022-01-10 05:40:26 -08:00
|
|
|
return unidecode.unidecode(v).lower().strip()
|
|
|
|
|
|
|
|
|
|
|
|
def valid_partial_ipv4_address(ip):
|
|
|
|
return all(int(s) >= 0 and int(s) <= 255 for s in ip.split(".") if len(s) > 0)
|
|
|
|
|
|
|
|
|
|
|
|
def make_asn_query(term):
|
2022-03-08 09:27:45 -04:00
|
|
|
return Network.objects.filter(asn__exact=term, status="ok")
|
2022-01-10 05:40:26 -08:00
|
|
|
|
|
|
|
|
|
|
|
def make_ipv4_query(term):
|
2022-03-08 09:27:45 -04:00
|
|
|
return NetworkIXLan.objects.filter(ipaddr4__startswith=term, status="ok")
|
2022-01-10 05:40:26 -08:00
|
|
|
|
|
|
|
|
|
|
|
def make_ipv6_query(term):
|
2022-03-08 09:27:45 -04:00
|
|
|
return NetworkIXLan.objects.filter(ipaddr6__startswith=term, status="ok")
|
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
|
|
|
|
2022-01-10 05:40:26 -08:00
|
|
|
term = unaccent(term)
|
|
|
|
|
|
|
|
if ONLY_DIGITS.match(term):
|
|
|
|
return make_asn_query(term)
|
|
|
|
|
|
|
|
if PARTIAL_IPV4_ADDRESS.match(term):
|
|
|
|
if valid_partial_ipv4_address(term):
|
|
|
|
return make_ipv4_query(term)
|
|
|
|
|
|
|
|
if PARTIAL_IPV6_ADDRESS.match(term):
|
|
|
|
return make_ipv6_query(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
|
|
|
|
2022-01-10 05:40:26 -08:00
|
|
|
return (
|
|
|
|
SearchQuerySet()
|
|
|
|
.filter(term_filters, status=Exact("ok"))
|
|
|
|
.models(*searchable_models)
|
|
|
|
)
|
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:
|
2022-01-10 05:40:26 -08:00
|
|
|
search_query = make_search_query(term)
|
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
|
|
|
# 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]:
|
2022-01-10 05:40:26 -08:00
|
|
|
if hasattr(sq, "model"):
|
|
|
|
model = sq.model
|
|
|
|
model.HandleRef.tag
|
|
|
|
categorize(sq, result, pk_map)
|
|
|
|
else:
|
|
|
|
if sq.HandleRef.tag == "netixlan":
|
|
|
|
add_secondary_entries(sq, result, pk_map)
|
|
|
|
else:
|
|
|
|
append_result(
|
|
|
|
sq.HandleRef.tag,
|
|
|
|
sq.pk,
|
|
|
|
getattr(sq, "search_result_name", None),
|
|
|
|
sq.org_id,
|
|
|
|
None,
|
|
|
|
result,
|
|
|
|
pk_map,
|
|
|
|
)
|
2021-07-08 21:00:23 -05:00
|
|
|
|
|
|
|
# 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)
|
2022-01-10 05:40:26 -08:00
|
|
|
else:
|
|
|
|
add_secondary_entries(sq, result, pk_map)
|
2018-11-08 19:45:21 +00:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
|
2022-01-10 05:40:26 -08:00
|
|
|
def add_secondary_entries(sq, result, pk_map):
|
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}
|
|
|
|
)
|