2021-03-08 11:49:10 -05:00
|
|
|
""" search views"""
|
2021-01-13 15:03:27 -05:00
|
|
|
import re
|
|
|
|
|
|
|
|
from django.contrib.postgres.search import TrigramSimilarity
|
2021-05-01 13:47:01 -04:00
|
|
|
from django.core.paginator import Paginator
|
2021-01-13 15:03:27 -05:00
|
|
|
from django.db.models.functions import Greatest
|
|
|
|
from django.http import JsonResponse
|
|
|
|
from django.template.response import TemplateResponse
|
|
|
|
from django.views import View
|
|
|
|
|
|
|
|
from bookwyrm import models
|
|
|
|
from bookwyrm.connectors import connector_manager
|
2021-09-16 13:44:33 -04:00
|
|
|
from bookwyrm.book_search import search, format_search_result
|
2021-05-01 13:47:01 -04:00
|
|
|
from bookwyrm.settings import PAGE_LENGTH
|
2021-01-13 15:03:27 -05:00
|
|
|
from bookwyrm.utils import regex
|
2021-10-06 13:37:09 -04:00
|
|
|
from .helpers import is_api_request
|
2021-01-13 15:03:27 -05:00
|
|
|
from .helpers import handle_remote_webfinger
|
|
|
|
|
|
|
|
|
|
|
|
# pylint: disable= no-self-use
|
|
|
|
class Search(View):
|
2021-04-26 12:15:42 -04:00
|
|
|
"""search users or books"""
|
2021-03-08 11:49:10 -05:00
|
|
|
|
2021-04-30 21:59:02 -04:00
|
|
|
def get(self, request):
|
2021-04-26 12:15:42 -04:00
|
|
|
"""that search bar up top"""
|
2021-03-08 11:49:10 -05:00
|
|
|
query = request.GET.get("q")
|
2021-06-23 20:41:29 -04:00
|
|
|
min_confidence = request.GET.get("min_confidence", 0)
|
2021-04-30 21:59:02 -04:00
|
|
|
search_type = request.GET.get("type")
|
2021-04-30 23:08:05 -04:00
|
|
|
search_remote = (
|
|
|
|
request.GET.get("remote", False) and request.user.is_authenticated
|
|
|
|
)
|
2021-01-13 15:03:27 -05:00
|
|
|
|
|
|
|
if is_api_request(request):
|
|
|
|
# only return local book results via json so we don't cascade
|
2021-09-14 18:26:18 -04:00
|
|
|
book_results = search(query, min_confidence=min_confidence)
|
2021-09-16 13:44:33 -04:00
|
|
|
return JsonResponse(
|
|
|
|
[format_search_result(r) for r in book_results], safe=False
|
|
|
|
)
|
2021-01-13 15:03:27 -05:00
|
|
|
|
2021-05-20 19:34:32 -04:00
|
|
|
if query and not search_type:
|
2021-04-30 21:59:02 -04:00
|
|
|
search_type = "user" if "@" in query else "book"
|
|
|
|
|
|
|
|
endpoints = {
|
|
|
|
"book": book_search,
|
|
|
|
"user": user_search,
|
|
|
|
"list": list_search,
|
|
|
|
}
|
|
|
|
if not search_type in endpoints:
|
|
|
|
search_type = "book"
|
|
|
|
|
2021-04-30 22:19:10 -04:00
|
|
|
data = {
|
|
|
|
"query": query or "",
|
|
|
|
"type": search_type,
|
2021-04-30 22:56:29 -04:00
|
|
|
"remote": search_remote,
|
2021-04-30 22:19:10 -04:00
|
|
|
}
|
2021-04-30 22:56:29 -04:00
|
|
|
if query:
|
2021-07-28 16:52:16 -04:00
|
|
|
results, search_remote = endpoints[search_type](
|
2021-04-30 22:56:29 -04:00
|
|
|
query, request.user, min_confidence, search_remote
|
|
|
|
)
|
2021-05-02 01:20:23 -04:00
|
|
|
if results:
|
|
|
|
paginated = Paginator(results, PAGE_LENGTH).get_page(
|
|
|
|
request.GET.get("page")
|
|
|
|
)
|
|
|
|
data["results"] = paginated
|
2021-07-28 16:52:16 -04:00
|
|
|
data["remote"] = search_remote
|
2021-04-30 21:35:09 -04:00
|
|
|
|
2021-09-18 14:32:00 -04:00
|
|
|
return TemplateResponse(request, f"search/{search_type}.html", data)
|
2021-04-30 21:06:30 -04:00
|
|
|
|
|
|
|
|
2021-10-03 12:18:17 -04:00
|
|
|
def book_search(query, user, min_confidence, search_remote=False):
|
2021-04-30 22:19:10 -04:00
|
|
|
"""the real business is elsewhere"""
|
2021-07-28 16:29:24 -04:00
|
|
|
# try a local-only search
|
2021-09-14 18:26:18 -04:00
|
|
|
results = [{"results": search(query, min_confidence=min_confidence)}]
|
2021-10-03 12:44:27 -04:00
|
|
|
if not user.is_authenticated or (results[0]["results"] and not search_remote):
|
2021-09-14 18:26:18 -04:00
|
|
|
return results, False
|
|
|
|
|
|
|
|
# if there were no local results, or the request was for remote, search all sources
|
|
|
|
results += connector_manager.search(query, min_confidence=min_confidence)
|
|
|
|
return results, True
|
2021-04-30 21:06:30 -04:00
|
|
|
|
|
|
|
|
2021-04-30 22:56:29 -04:00
|
|
|
def user_search(query, viewer, *_):
|
2021-04-30 22:19:10 -04:00
|
|
|
"""cool kids members only user search"""
|
2021-04-30 21:06:30 -04:00
|
|
|
# logged out viewers can't search users
|
|
|
|
if not viewer.is_authenticated:
|
2021-07-28 19:43:49 -04:00
|
|
|
return models.User.objects.none(), None
|
2021-04-30 21:06:30 -04:00
|
|
|
|
|
|
|
# use webfinger for mastodon style account@domain.com username to load the user if
|
|
|
|
# they don't exist locally (handle_remote_webfinger will check the db)
|
2021-06-18 17:12:56 -04:00
|
|
|
if re.match(regex.FULL_USERNAME, query):
|
2021-04-30 21:06:30 -04:00
|
|
|
handle_remote_webfinger(query)
|
|
|
|
|
2021-04-30 22:19:10 -04:00
|
|
|
return (
|
|
|
|
models.User.viewer_aware_objects(viewer)
|
|
|
|
.annotate(
|
|
|
|
similarity=Greatest(
|
|
|
|
TrigramSimilarity("username", query),
|
|
|
|
TrigramSimilarity("localname", query),
|
2021-03-08 11:49:10 -05:00
|
|
|
)
|
2021-04-30 22:19:10 -04:00
|
|
|
)
|
|
|
|
.filter(
|
|
|
|
similarity__gt=0.5,
|
|
|
|
)
|
2021-10-03 12:38:41 -04:00
|
|
|
.order_by("-similarity")
|
2021-07-28 16:52:16 -04:00
|
|
|
), None
|
2021-04-30 21:06:30 -04:00
|
|
|
|
2021-01-13 15:03:27 -05:00
|
|
|
|
2021-04-30 22:56:29 -04:00
|
|
|
def list_search(query, viewer, *_):
|
2021-04-30 21:06:30 -04:00
|
|
|
"""any relevent lists?"""
|
2021-04-30 22:19:10 -04:00
|
|
|
return (
|
2021-10-06 13:37:09 -04:00
|
|
|
models.List.privacy_filter(
|
2021-04-30 22:19:10 -04:00
|
|
|
viewer,
|
|
|
|
privacy_levels=["public", "followers"],
|
|
|
|
)
|
|
|
|
.annotate(
|
|
|
|
similarity=Greatest(
|
|
|
|
TrigramSimilarity("name", query),
|
|
|
|
TrigramSimilarity("description", query),
|
2021-02-01 14:50:47 -05:00
|
|
|
)
|
2021-04-30 22:19:10 -04:00
|
|
|
)
|
|
|
|
.filter(
|
|
|
|
similarity__gt=0.1,
|
|
|
|
)
|
2021-10-03 12:38:41 -04:00
|
|
|
.order_by("-similarity")
|
2021-07-28 16:52:16 -04:00
|
|
|
), None
|