search.py 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import operator
  2. from functools import reduce
  3. from django.db.models import Q
  4. from wagtail.search.backends import get_search_backend
  5. try:
  6. from django.contrib.admin.utils import lookup_spawns_duplicates
  7. except ImportError:
  8. # fallback for Django <4.0
  9. from django.contrib.admin.utils import (
  10. lookup_needs_distinct as lookup_spawns_duplicates,
  11. )
  12. class BaseSearchHandler:
  13. def __init__(self, search_fields):
  14. self.search_fields = search_fields
  15. def search_queryset(self, queryset, search_term, **kwargs):
  16. """
  17. Returns an iterable of objects from ``queryset`` matching the
  18. provided ``search_term``.
  19. """
  20. raise NotImplementedError()
  21. @property
  22. def show_search_form(self):
  23. """
  24. Returns a boolean that determines whether a search form should be
  25. displayed in the IndexView UI.
  26. """
  27. return True
  28. class DjangoORMSearchHandler(BaseSearchHandler):
  29. def search_queryset(self, queryset, search_term, **kwargs):
  30. if not search_term or not self.search_fields:
  31. return queryset
  32. orm_lookups = [
  33. "%s__icontains" % str(search_field) for search_field in self.search_fields
  34. ]
  35. for bit in search_term.split():
  36. or_queries = [Q(**{orm_lookup: bit}) for orm_lookup in orm_lookups]
  37. queryset = queryset.filter(reduce(operator.or_, or_queries))
  38. opts = queryset.model._meta
  39. for search_spec in orm_lookups:
  40. if lookup_spawns_duplicates(opts, search_spec):
  41. return queryset.distinct()
  42. return queryset
  43. @property
  44. def show_search_form(self):
  45. return bool(self.search_fields)
  46. class WagtailBackendSearchHandler(BaseSearchHandler):
  47. default_search_backend = "default"
  48. def search_queryset(
  49. self,
  50. queryset,
  51. search_term,
  52. preserve_order=False,
  53. operator=None,
  54. backend=None,
  55. **kwargs,
  56. ):
  57. if not search_term:
  58. return queryset
  59. backend = get_search_backend(backend or self.default_search_backend)
  60. return backend.search(
  61. search_term,
  62. queryset,
  63. fields=self.search_fields or None,
  64. operator=operator,
  65. order_by_relevance=not preserve_order,
  66. )