search.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
  2. from django.utils.translation import gettext_lazy as _
  3. from ..conf import settings
  4. from ..core.shortcuts import paginate, pagination_dict
  5. from ..search import SearchProvider
  6. from .filtersearch import filter_search
  7. from .models import Post, Thread
  8. from .permissions import exclude_invisible_threads
  9. from .serializers import FeedSerializer
  10. from .utils import add_categories_to_items
  11. from .viewmodels import ThreadsRootCategory
  12. class SearchThreads(SearchProvider):
  13. name = _("Threads")
  14. icon = "forum"
  15. url = "threads"
  16. def search(self, query, page=1):
  17. root_category = ThreadsRootCategory(self.request)
  18. threads_categories = [root_category.unwrap()] + root_category.subcategories
  19. if len(query) > 1:
  20. visible_threads = exclude_invisible_threads(
  21. self.request.user_acl, threads_categories, Thread.objects
  22. )
  23. results = search_threads(self.request, query, visible_threads)
  24. else:
  25. results = []
  26. list_page = paginate(
  27. results,
  28. page,
  29. self.request.settings.posts_per_page,
  30. self.request.settings.posts_per_page_orphans,
  31. allow_explicit_first_page=True,
  32. )
  33. paginator = pagination_dict(list_page)
  34. posts = []
  35. threads = []
  36. if paginator["count"]:
  37. posts = list(
  38. list_page.object_list.select_related("thread", "poster", "poster__rank")
  39. )
  40. threads = []
  41. for post in posts:
  42. threads.append(post.thread)
  43. add_categories_to_items(
  44. root_category.unwrap(), threads_categories, posts + threads
  45. )
  46. results = {
  47. "results": FeedSerializer(
  48. posts, many=True, context={"user": self.request.user}
  49. ).data
  50. }
  51. results.update(paginator)
  52. return results
  53. def search_threads(request, query, visible_threads):
  54. max_hits = request.settings.posts_per_page * 5
  55. clean_query = filter_search(query)
  56. if not clean_query:
  57. # Short-circuit search due to empty cleaned query
  58. return Post.objects.none()
  59. search_query = SearchQuery(clean_query, config=settings.MISAGO_SEARCH_CONFIG)
  60. search_vector = SearchVector(
  61. "search_document", config=settings.MISAGO_SEARCH_CONFIG
  62. )
  63. queryset = Post.objects.filter(
  64. is_event=False,
  65. is_hidden=False,
  66. is_unapproved=False,
  67. thread_id__in=visible_threads.values("id"),
  68. search_vector=search_query,
  69. )
  70. if queryset[: max_hits + 1].count() > max_hits:
  71. queryset = queryset.order_by("-id")[:max_hits]
  72. return (
  73. Post.objects.filter(id__in=queryset.values("id"))
  74. .annotate(rank=SearchRank(search_vector, search_query))
  75. .order_by("-rank", "-id")
  76. )