diff --git a/modelsearch/backends/base.py b/modelsearch/backends/base.py index d4b23fb4..fc80abf9 100644 --- a/modelsearch/backends/base.py +++ b/modelsearch/backends/base.py @@ -8,6 +8,7 @@ from django.db.models.lookups import Lookup from django.db.models.query import QuerySet from django.db.models.sql.where import NothingNode, WhereNode +from queryish import Queryish from modelsearch.index import class_is_indexed, get_indexed_models from modelsearch.query import MATCH_ALL, PlainText @@ -329,7 +330,7 @@ def check(self): list(self._get_order_by()) -class BaseSearchResults: +class BaseSearchResults(Queryish): """ Represents the results of a search query. This emulates a Django QuerySet, but with the results not necessarily coming from the database - the result set can be sliced to obtain a new SearchResults instance, and the search @@ -343,45 +344,16 @@ class BaseSearchResults: supports_facet = False def __init__(self, backend, query_compiler, prefetch_related=None): + super().__init__() self.backend = backend self.query_compiler = query_compiler self.prefetch_related = prefetch_related - self.start = 0 - self.stop = None - self._results_cache = None - self._count_cache = None self._score_field = None # Attach the model to mimic a QuerySet so that we can inspect it after # doing a search, e.g. to get the model's name in a paginator. # The query_compiler may be None, e.g. when using EmptySearchResults. self.model = query_compiler.queryset.model if query_compiler else None - def _set_limits(self, start=None, stop=None): - if stop is not None: - if self.stop is not None: - self.stop = min(self.stop, self.start + stop) - else: - self.stop = self.start + stop - - if start is not None: - if self.stop is not None: - self.start = min(self.stop, self.start + start) - else: - self.start = self.start + start - - def _clone(self): - """ - Returns a copy of this object with the same options in place. - """ - klass = self.__class__ - new = klass( - self.backend, self.query_compiler, prefetch_related=self.prefetch_related - ) - new.start = self.start - new.stop = self.stop - new._score_field = self._score_field - return new - def _do_search(self): """ To be implemented by subclasses - performs the actual search query, returning an iterable @@ -389,69 +361,20 @@ def _do_search(self): """ raise NotImplementedError + def run_query(self): + return self._do_search() + def _do_count(self): """ To be implemented by subclasses - returns the result count. """ raise NotImplementedError - def results(self): - """ - Returns the search results, caching them to avoid repeated queries. - """ - if self._results_cache is None: - self._results_cache = list(self._do_search()) - return self._results_cache - - def count(self): - """ - Returns the count of search results, caching it to avoid repeated queries. - """ - if self._count_cache is None: - if self._results_cache is not None: - self._count_cache = len(self._results_cache) - else: - self._count_cache = self._do_count() - return self._count_cache - - def __getitem__(self, key): - new = self._clone() - - if isinstance(key, slice): - # Set limits - start = int(key.start) if key.start is not None else None - stop = int(key.stop) if key.stop is not None else None - new._set_limits(start, stop) - - # Copy results cache - if self._results_cache is not None: - new._results_cache = self._results_cache[key] - - return new - else: - if self._results_cache is not None: - return self._results_cache[key] - - new.start = self.start + key - new.stop = self.start + key + 1 - return list(new)[0] - - def __iter__(self): - return iter(self.results()) - - def __len__(self): - return len(self.results()) - - def __repr__(self): - data = list(self[:21]) - if len(data) > 20: - data[-1] = "...(remaining elements truncated)..." - return f"" + def run_count(self): + return self._do_count() def annotate_score(self, field_name): - clone = self._clone() - clone._score_field = field_name - return clone + return self.clone(_score_field=field_name) def facet(self, field_name): raise NotImplementedError("This search backend does not support faceting") diff --git a/pyproject.toml b/pyproject.toml index b27fc2ec..dc769a2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ requires-python = ">= 3.10" dependencies = [ "Django>=4.2", "django-tasks>=0.7,<0.12", + "queryish>=0.3,<1", ] description = "A library for indexing Django models with Elasicsearch, OpenSearch or database and searching them with the Django ORM." readme = "README.md"