We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Empty search in the API leading to TypeError
Steps to reproduce:
- use Wagtail 1.9 & elasticsearch 2
- install Wagtail’s API
- go to /api/v2/pages/?search=

You end up with:
```
File "…/wagtail/api/v2/pagination.py" in paginate_queryset
  36.         self.total_count = queryset.count()

TypeError: count() takes exactly one argument (0 given)
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wagtail/wagtailsearch/backends/base.py`
Content:
```
1 
2 from __future__ import absolute_import, unicode_literals
3 
4 from django.db.models.lookups import Lookup
5 from django.db.models.query import QuerySet
6 from django.db.models.sql.where import SubqueryConstraint, WhereNode
7 from django.utils.six import text_type
8 
9 from wagtail.wagtailsearch.index import class_is_indexed
10 
11 
12 class FilterError(Exception):
13     pass
14 
15 
16 class FieldError(Exception):
17     pass
18 
19 
20 class BaseSearchQuery(object):
21     DEFAULT_OPERATOR = 'or'
22 
23     def __init__(self, queryset, query_string, fields=None, operator=None, order_by_relevance=True):
24         self.queryset = queryset
25         self.query_string = query_string
26         self.fields = fields
27         self.operator = operator or self.DEFAULT_OPERATOR
28         self.order_by_relevance = order_by_relevance
29 
30     def _get_filterable_field(self, field_attname):
31         # Get field
32         field = dict(
33             (field.get_attname(self.queryset.model), field)
34             for field in self.queryset.model.get_filterable_search_fields()
35         ).get(field_attname, None)
36 
37         return field
38 
39     def _process_lookup(self, field, lookup, value):
40         raise NotImplementedError
41 
42     def _connect_filters(self, filters, connector, negated):
43         raise NotImplementedError
44 
45     def _process_filter(self, field_attname, lookup, value):
46         # Get the field
47         field = self._get_filterable_field(field_attname)
48 
49         if field is None:
50             raise FieldError(
51                 'Cannot filter search results with field "' + field_attname + '". Please add index.FilterField(\'' +
52                 field_attname + '\') to ' + self.queryset.model.__name__ + '.search_fields.'
53             )
54 
55         # Process the lookup
56         result = self._process_lookup(field, lookup, value)
57 
58         if result is None:
59             raise FilterError(
60                 'Could not apply filter on search results: "' + field_attname + '__' +
61                 lookup + ' = ' + text_type(value) + '". Lookup "' + lookup + '"" not recognised.'
62             )
63 
64         return result
65 
66     def _get_filters_from_where_node(self, where_node):
67         # Check if this is a leaf node
68         if isinstance(where_node, Lookup):
69             field_attname = where_node.lhs.target.attname
70             lookup = where_node.lookup_name
71             value = where_node.rhs
72 
73             # Ignore pointer fields that show up in specific page type queries
74             if field_attname.endswith('_ptr_id'):
75                 return
76 
77             # Process the filter
78             return self._process_filter(field_attname, lookup, value)
79 
80         elif isinstance(where_node, SubqueryConstraint):
81             raise FilterError('Could not apply filter on search results: Subqueries are not allowed.')
82 
83         elif isinstance(where_node, WhereNode):
84             # Get child filters
85             connector = where_node.connector
86             child_filters = [self._get_filters_from_where_node(child) for child in where_node.children]
87             child_filters = [child_filter for child_filter in child_filters if child_filter]
88 
89             return self._connect_filters(child_filters, connector, where_node.negated)
90 
91         else:
92             raise FilterError('Could not apply filter on search results: Unknown where node: ' + str(type(where_node)))
93 
94     def _get_filters_from_queryset(self):
95         return self._get_filters_from_where_node(self.queryset.query.where)
96 
97 
98 class BaseSearchResults(object):
99     def __init__(self, backend, query, prefetch_related=None):
100         self.backend = backend
101         self.query = query
102         self.prefetch_related = prefetch_related
103         self.start = 0
104         self.stop = None
105         self._results_cache = None
106         self._count_cache = None
107         self._score_field = None
108 
109     def _set_limits(self, start=None, stop=None):
110         if stop is not None:
111             if self.stop is not None:
112                 self.stop = min(self.stop, self.start + stop)
113             else:
114                 self.stop = self.start + stop
115 
116         if start is not None:
117             if self.stop is not None:
118                 self.start = min(self.stop, self.start + start)
119             else:
120                 self.start = self.start + start
121 
122     def _clone(self):
123         klass = self.__class__
124         new = klass(self.backend, self.query, prefetch_related=self.prefetch_related)
125         new.start = self.start
126         new.stop = self.stop
127         new._score_field = self._score_field
128         return new
129 
130     def _do_search(self):
131         raise NotImplementedError
132 
133     def _do_count(self):
134         raise NotImplementedError
135 
136     def results(self):
137         if self._results_cache is None:
138             self._results_cache = self._do_search()
139         return self._results_cache
140 
141     def count(self):
142         if self._count_cache is None:
143             if self._results_cache is not None:
144                 self._count_cache = len(self._results_cache)
145             else:
146                 self._count_cache = self._do_count()
147         return self._count_cache
148 
149     def __getitem__(self, key):
150         new = self._clone()
151 
152         if isinstance(key, slice):
153             # Set limits
154             start = int(key.start) if key.start else None
155             stop = int(key.stop) if key.stop else None
156             new._set_limits(start, stop)
157 
158             # Copy results cache
159             if self._results_cache is not None:
160                 new._results_cache = self._results_cache[key]
161 
162             return new
163         else:
164             if self._results_cache is not None:
165                 return self._results_cache[key]
166 
167             new.start = self.start + key
168             new.stop = self.start + key + 1
169             return list(new)[0]
170 
171     def __iter__(self):
172         return iter(self.results())
173 
174     def __len__(self):
175         return len(self.results())
176 
177     def __repr__(self):
178         data = list(self[:21])
179         if len(data) > 20:
180             data[-1] = "...(remaining elements truncated)..."
181         return '<SearchResults %r>' % data
182 
183     def annotate_score(self, field_name):
184         clone = self._clone()
185         clone._score_field = field_name
186         return clone
187 
188 
189 class BaseSearchBackend(object):
190     query_class = None
191     results_class = None
192     rebuilder_class = None
193 
194     def __init__(self, params):
195         pass
196 
197     def get_index_for_model(self, model):
198         return None
199 
200     def get_rebuilder(self):
201         return None
202 
203     def reset_index(self):
204         raise NotImplementedError
205 
206     def add_type(self, model):
207         raise NotImplementedError
208 
209     def refresh_index(self):
210         raise NotImplementedError
211 
212     def add(self, obj):
213         raise NotImplementedError
214 
215     def add_bulk(self, model, obj_list):
216         raise NotImplementedError
217 
218     def delete(self, obj):
219         raise NotImplementedError
220 
221     def search(self, query_string, model_or_queryset, fields=None, filters=None,
222                prefetch_related=None, operator=None, order_by_relevance=True):
223         # Find model/queryset
224         if isinstance(model_or_queryset, QuerySet):
225             model = model_or_queryset.model
226             queryset = model_or_queryset
227         else:
228             model = model_or_queryset
229             queryset = model_or_queryset.objects.all()
230 
231         # Model must be a class that is in the index
232         if not class_is_indexed(model):
233             return []
234 
235         # Check that theres still a query string after the clean up
236         if query_string == "":
237             return []
238 
239         # Only fields that are indexed as a SearchField can be passed in fields
240         if fields:
241             allowed_fields = {field.field_name for field in model.get_searchable_search_fields()}
242 
243             for field_name in fields:
244                 if field_name not in allowed_fields:
245                     raise FieldError(
246                         'Cannot search with field "' + field_name + '". Please add index.SearchField(\'' +
247                         field_name + '\') to ' + model.__name__ + '.search_fields.'
248                     )
249 
250         # Apply filters to queryset
251         if filters:
252             queryset = queryset.filter(**filters)
253 
254         # Prefetch related
255         if prefetch_related:
256             for prefetch in prefetch_related:
257                 queryset = queryset.prefetch_related(prefetch)
258 
259         # Check operator
260         if operator is not None:
261             operator = operator.lower()
262             if operator not in ['or', 'and']:
263                 raise ValueError("operator must be either 'or' or 'and'")
264 
265         # Search
266         search_query = self.query_class(
267             queryset, query_string, fields=fields, operator=operator, order_by_relevance=order_by_relevance
268         )
269         return self.results_class(self, search_query)
270
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/wagtail/wagtailsearch/backends/base.py b/wagtail/wagtailsearch/backends/base.py
--- a/wagtail/wagtailsearch/backends/base.py
+++ b/wagtail/wagtailsearch/backends/base.py
@@ -186,6 +186,20 @@
         return clone
 
 
+class EmptySearchResults(BaseSearchResults):
+    def __init__(self):
+        return super(EmptySearchResults, self).__init__(None, None)
+
+    def _clone(self):
+        return self.__class__()
+
+    def _do_search(self):
+        return []
+
+    def _do_count(self):
+        return 0
+
+
 class BaseSearchBackend(object):
     query_class = None
     results_class = None
@@ -230,11 +244,11 @@
 
         # Model must be a class that is in the index
         if not class_is_indexed(model):
-            return []
+            return EmptySearchResults()
 
         # Check that theres still a query string after the clean up
         if query_string == "":
-            return []
+            return EmptySearchResults()
 
         # Only fields that are indexed as a SearchField can be passed in fields
         if fields:

{"golden_diff": "diff --git a/wagtail/wagtailsearch/backends/base.py b/wagtail/wagtailsearch/backends/base.py\n--- a/wagtail/wagtailsearch/backends/base.py\n+++ b/wagtail/wagtailsearch/backends/base.py\n@@ -186,6 +186,20 @@\n         return clone\n \n \n+class EmptySearchResults(BaseSearchResults):\n+    def __init__(self):\n+        return super(EmptySearchResults, self).__init__(None, None)\n+\n+    def _clone(self):\n+        return self.__class__()\n+\n+    def _do_search(self):\n+        return []\n+\n+    def _do_count(self):\n+        return 0\n+\n+\n class BaseSearchBackend(object):\n     query_class = None\n     results_class = None\n@@ -230,11 +244,11 @@\n \n         # Model must be a class that is in the index\n         if not class_is_indexed(model):\n-            return []\n+            return EmptySearchResults()\n \n         # Check that theres still a query string after the clean up\n         if query_string == \"\":\n-            return []\n+            return EmptySearchResults()\n \n         # Only fields that are indexed as a SearchField can be passed in fields\n         if fields:\n", "issue": "Empty search in the API leading to TypeError\nSteps to reproduce:\r\n- use Wagtail 1.9 & elasticsearch 2\r\n- install Wagtail\u2019s API\r\n- go to /api/v2/pages/?search=\r\n\r\nYou end up with:\r\n```\r\nFile \"\u2026/wagtail/api/v2/pagination.py\" in paginate_queryset\r\n  36.         self.total_count = queryset.count()\r\n\r\nTypeError: count() takes exactly one argument (0 given)\r\n```\n", "before_files": [{"content": "\nfrom __future__ import absolute_import, unicode_literals\n\nfrom django.db.models.lookups import Lookup\nfrom django.db.models.query import QuerySet\nfrom django.db.models.sql.where import SubqueryConstraint, WhereNode\nfrom django.utils.six import text_type\n\nfrom wagtail.wagtailsearch.index import class_is_indexed\n\n\nclass FilterError(Exception):\n    pass\n\n\nclass FieldError(Exception):\n    pass\n\n\nclass BaseSearchQuery(object):\n    DEFAULT_OPERATOR = 'or'\n\n    def __init__(self, queryset, query_string, fields=None, operator=None, order_by_relevance=True):\n        self.queryset = queryset\n        self.query_string = query_string\n        self.fields = fields\n        self.operator = operator or self.DEFAULT_OPERATOR\n        self.order_by_relevance = order_by_relevance\n\n    def _get_filterable_field(self, field_attname):\n        # Get field\n        field = dict(\n            (field.get_attname(self.queryset.model), field)\n            for field in self.queryset.model.get_filterable_search_fields()\n        ).get(field_attname, None)\n\n        return field\n\n    def _process_lookup(self, field, lookup, value):\n        raise NotImplementedError\n\n    def _connect_filters(self, filters, connector, negated):\n        raise NotImplementedError\n\n    def _process_filter(self, field_attname, lookup, value):\n        # Get the field\n        field = self._get_filterable_field(field_attname)\n\n        if field is None:\n            raise FieldError(\n                'Cannot filter search results with field \"' + field_attname + '\". Please add index.FilterField(\\'' +\n                field_attname + '\\') to ' + self.queryset.model.__name__ + '.search_fields.'\n            )\n\n        # Process the lookup\n        result = self._process_lookup(field, lookup, value)\n\n        if result is None:\n            raise FilterError(\n                'Could not apply filter on search results: \"' + field_attname + '__' +\n                lookup + ' = ' + text_type(value) + '\". Lookup \"' + lookup + '\"\" not recognised.'\n            )\n\n        return result\n\n    def _get_filters_from_where_node(self, where_node):\n        # Check if this is a leaf node\n        if isinstance(where_node, Lookup):\n            field_attname = where_node.lhs.target.attname\n            lookup = where_node.lookup_name\n            value = where_node.rhs\n\n            # Ignore pointer fields that show up in specific page type queries\n            if field_attname.endswith('_ptr_id'):\n                return\n\n            # Process the filter\n            return self._process_filter(field_attname, lookup, value)\n\n        elif isinstance(where_node, SubqueryConstraint):\n            raise FilterError('Could not apply filter on search results: Subqueries are not allowed.')\n\n        elif isinstance(where_node, WhereNode):\n            # Get child filters\n            connector = where_node.connector\n            child_filters = [self._get_filters_from_where_node(child) for child in where_node.children]\n            child_filters = [child_filter for child_filter in child_filters if child_filter]\n\n            return self._connect_filters(child_filters, connector, where_node.negated)\n\n        else:\n            raise FilterError('Could not apply filter on search results: Unknown where node: ' + str(type(where_node)))\n\n    def _get_filters_from_queryset(self):\n        return self._get_filters_from_where_node(self.queryset.query.where)\n\n\nclass BaseSearchResults(object):\n    def __init__(self, backend, query, prefetch_related=None):\n        self.backend = backend\n        self.query = query\n        self.prefetch_related = prefetch_related\n        self.start = 0\n        self.stop = None\n        self._results_cache = None\n        self._count_cache = None\n        self._score_field = None\n\n    def _set_limits(self, start=None, stop=None):\n        if stop is not None:\n            if self.stop is not None:\n                self.stop = min(self.stop, self.start + stop)\n            else:\n                self.stop = self.start + stop\n\n        if start is not None:\n            if self.stop is not None:\n                self.start = min(self.stop, self.start + start)\n            else:\n                self.start = self.start + start\n\n    def _clone(self):\n        klass = self.__class__\n        new = klass(self.backend, self.query, prefetch_related=self.prefetch_related)\n        new.start = self.start\n        new.stop = self.stop\n        new._score_field = self._score_field\n        return new\n\n    def _do_search(self):\n        raise NotImplementedError\n\n    def _do_count(self):\n        raise NotImplementedError\n\n    def results(self):\n        if self._results_cache is None:\n            self._results_cache = self._do_search()\n        return self._results_cache\n\n    def count(self):\n        if self._count_cache is None:\n            if self._results_cache is not None:\n                self._count_cache = len(self._results_cache)\n            else:\n                self._count_cache = self._do_count()\n        return self._count_cache\n\n    def __getitem__(self, key):\n        new = self._clone()\n\n        if isinstance(key, slice):\n            # Set limits\n            start = int(key.start) if key.start else None\n            stop = int(key.stop) if key.stop else None\n            new._set_limits(start, stop)\n\n            # Copy results cache\n            if self._results_cache is not None:\n                new._results_cache = self._results_cache[key]\n\n            return new\n        else:\n            if self._results_cache is not None:\n                return self._results_cache[key]\n\n            new.start = self.start + key\n            new.stop = self.start + key + 1\n            return list(new)[0]\n\n    def __iter__(self):\n        return iter(self.results())\n\n    def __len__(self):\n        return len(self.results())\n\n    def __repr__(self):\n        data = list(self[:21])\n        if len(data) > 20:\n            data[-1] = \"...(remaining elements truncated)...\"\n        return '<SearchResults %r>' % data\n\n    def annotate_score(self, field_name):\n        clone = self._clone()\n        clone._score_field = field_name\n        return clone\n\n\nclass BaseSearchBackend(object):\n    query_class = None\n    results_class = None\n    rebuilder_class = None\n\n    def __init__(self, params):\n        pass\n\n    def get_index_for_model(self, model):\n        return None\n\n    def get_rebuilder(self):\n        return None\n\n    def reset_index(self):\n        raise NotImplementedError\n\n    def add_type(self, model):\n        raise NotImplementedError\n\n    def refresh_index(self):\n        raise NotImplementedError\n\n    def add(self, obj):\n        raise NotImplementedError\n\n    def add_bulk(self, model, obj_list):\n        raise NotImplementedError\n\n    def delete(self, obj):\n        raise NotImplementedError\n\n    def search(self, query_string, model_or_queryset, fields=None, filters=None,\n               prefetch_related=None, operator=None, order_by_relevance=True):\n        # Find model/queryset\n        if isinstance(model_or_queryset, QuerySet):\n            model = model_or_queryset.model\n            queryset = model_or_queryset\n        else:\n            model = model_or_queryset\n            queryset = model_or_queryset.objects.all()\n\n        # Model must be a class that is in the index\n        if not class_is_indexed(model):\n            return []\n\n        # Check that theres still a query string after the clean up\n        if query_string == \"\":\n            return []\n\n        # Only fields that are indexed as a SearchField can be passed in fields\n        if fields:\n            allowed_fields = {field.field_name for field in model.get_searchable_search_fields()}\n\n            for field_name in fields:\n                if field_name not in allowed_fields:\n                    raise FieldError(\n                        'Cannot search with field \"' + field_name + '\". Please add index.SearchField(\\'' +\n                        field_name + '\\') to ' + model.__name__ + '.search_fields.'\n                    )\n\n        # Apply filters to queryset\n        if filters:\n            queryset = queryset.filter(**filters)\n\n        # Prefetch related\n        if prefetch_related:\n            for prefetch in prefetch_related:\n                queryset = queryset.prefetch_related(prefetch)\n\n        # Check operator\n        if operator is not None:\n            operator = operator.lower()\n            if operator not in ['or', 'and']:\n                raise ValueError(\"operator must be either 'or' or 'and'\")\n\n        # Search\n        search_query = self.query_class(\n            queryset, query_string, fields=fields, operator=operator, order_by_relevance=order_by_relevance\n        )\n        return self.results_class(self, search_query)\n", "path": "wagtail/wagtailsearch/backends/base.py"}], "after_files": [{"content": "\nfrom __future__ import absolute_import, unicode_literals\n\nfrom django.db.models.lookups import Lookup\nfrom django.db.models.query import QuerySet\nfrom django.db.models.sql.where import SubqueryConstraint, WhereNode\nfrom django.utils.six import text_type\n\nfrom wagtail.wagtailsearch.index import class_is_indexed\n\n\nclass FilterError(Exception):\n    pass\n\n\nclass FieldError(Exception):\n    pass\n\n\nclass BaseSearchQuery(object):\n    DEFAULT_OPERATOR = 'or'\n\n    def __init__(self, queryset, query_string, fields=None, operator=None, order_by_relevance=True):\n        self.queryset = queryset\n        self.query_string = query_string\n        self.fields = fields\n        self.operator = operator or self.DEFAULT_OPERATOR\n        self.order_by_relevance = order_by_relevance\n\n    def _get_filterable_field(self, field_attname):\n        # Get field\n        field = dict(\n            (field.get_attname(self.queryset.model), field)\n            for field in self.queryset.model.get_filterable_search_fields()\n        ).get(field_attname, None)\n\n        return field\n\n    def _process_lookup(self, field, lookup, value):\n        raise NotImplementedError\n\n    def _connect_filters(self, filters, connector, negated):\n        raise NotImplementedError\n\n    def _process_filter(self, field_attname, lookup, value):\n        # Get the field\n        field = self._get_filterable_field(field_attname)\n\n        if field is None:\n            raise FieldError(\n                'Cannot filter search results with field \"' + field_attname + '\". Please add index.FilterField(\\'' +\n                field_attname + '\\') to ' + self.queryset.model.__name__ + '.search_fields.'\n            )\n\n        # Process the lookup\n        result = self._process_lookup(field, lookup, value)\n\n        if result is None:\n            raise FilterError(\n                'Could not apply filter on search results: \"' + field_attname + '__' +\n                lookup + ' = ' + text_type(value) + '\". Lookup \"' + lookup + '\"\" not recognised.'\n            )\n\n        return result\n\n    def _get_filters_from_where_node(self, where_node):\n        # Check if this is a leaf node\n        if isinstance(where_node, Lookup):\n            field_attname = where_node.lhs.target.attname\n            lookup = where_node.lookup_name\n            value = where_node.rhs\n\n            # Ignore pointer fields that show up in specific page type queries\n            if field_attname.endswith('_ptr_id'):\n                return\n\n            # Process the filter\n            return self._process_filter(field_attname, lookup, value)\n\n        elif isinstance(where_node, SubqueryConstraint):\n            raise FilterError('Could not apply filter on search results: Subqueries are not allowed.')\n\n        elif isinstance(where_node, WhereNode):\n            # Get child filters\n            connector = where_node.connector\n            child_filters = [self._get_filters_from_where_node(child) for child in where_node.children]\n            child_filters = [child_filter for child_filter in child_filters if child_filter]\n\n            return self._connect_filters(child_filters, connector, where_node.negated)\n\n        else:\n            raise FilterError('Could not apply filter on search results: Unknown where node: ' + str(type(where_node)))\n\n    def _get_filters_from_queryset(self):\n        return self._get_filters_from_where_node(self.queryset.query.where)\n\n\nclass BaseSearchResults(object):\n    def __init__(self, backend, query, prefetch_related=None):\n        self.backend = backend\n        self.query = query\n        self.prefetch_related = prefetch_related\n        self.start = 0\n        self.stop = None\n        self._results_cache = None\n        self._count_cache = None\n        self._score_field = None\n\n    def _set_limits(self, start=None, stop=None):\n        if stop is not None:\n            if self.stop is not None:\n                self.stop = min(self.stop, self.start + stop)\n            else:\n                self.stop = self.start + stop\n\n        if start is not None:\n            if self.stop is not None:\n                self.start = min(self.stop, self.start + start)\n            else:\n                self.start = self.start + start\n\n    def _clone(self):\n        klass = self.__class__\n        new = klass(self.backend, self.query, prefetch_related=self.prefetch_related)\n        new.start = self.start\n        new.stop = self.stop\n        new._score_field = self._score_field\n        return new\n\n    def _do_search(self):\n        raise NotImplementedError\n\n    def _do_count(self):\n        raise NotImplementedError\n\n    def results(self):\n        if self._results_cache is None:\n            self._results_cache = self._do_search()\n        return self._results_cache\n\n    def count(self):\n        if self._count_cache is None:\n            if self._results_cache is not None:\n                self._count_cache = len(self._results_cache)\n            else:\n                self._count_cache = self._do_count()\n        return self._count_cache\n\n    def __getitem__(self, key):\n        new = self._clone()\n\n        if isinstance(key, slice):\n            # Set limits\n            start = int(key.start) if key.start else None\n            stop = int(key.stop) if key.stop else None\n            new._set_limits(start, stop)\n\n            # Copy results cache\n            if self._results_cache is not None:\n                new._results_cache = self._results_cache[key]\n\n            return new\n        else:\n            if self._results_cache is not None:\n                return self._results_cache[key]\n\n            new.start = self.start + key\n            new.stop = self.start + key + 1\n            return list(new)[0]\n\n    def __iter__(self):\n        return iter(self.results())\n\n    def __len__(self):\n        return len(self.results())\n\n    def __repr__(self):\n        data = list(self[:21])\n        if len(data) > 20:\n            data[-1] = \"...(remaining elements truncated)...\"\n        return '<SearchResults %r>' % data\n\n    def annotate_score(self, field_name):\n        clone = self._clone()\n        clone._score_field = field_name\n        return clone\n\n\nclass EmptySearchResults(BaseSearchResults):\n    def __init__(self):\n        return super(EmptySearchResults, self).__init__(None, None)\n\n    def _clone(self):\n        return self.__class__()\n\n    def _do_search(self):\n        return []\n\n    def _do_count(self):\n        return 0\n\n\nclass BaseSearchBackend(object):\n    query_class = None\n    results_class = None\n    rebuilder_class = None\n\n    def __init__(self, params):\n        pass\n\n    def get_index_for_model(self, model):\n        return None\n\n    def get_rebuilder(self):\n        return None\n\n    def reset_index(self):\n        raise NotImplementedError\n\n    def add_type(self, model):\n        raise NotImplementedError\n\n    def refresh_index(self):\n        raise NotImplementedError\n\n    def add(self, obj):\n        raise NotImplementedError\n\n    def add_bulk(self, model, obj_list):\n        raise NotImplementedError\n\n    def delete(self, obj):\n        raise NotImplementedError\n\n    def search(self, query_string, model_or_queryset, fields=None, filters=None,\n               prefetch_related=None, operator=None, order_by_relevance=True):\n        # Find model/queryset\n        if isinstance(model_or_queryset, QuerySet):\n            model = model_or_queryset.model\n            queryset = model_or_queryset\n        else:\n            model = model_or_queryset\n            queryset = model_or_queryset.objects.all()\n\n        # Model must be a class that is in the index\n        if not class_is_indexed(model):\n            return EmptySearchResults()\n\n        # Check that theres still a query string after the clean up\n        if query_string == \"\":\n            return EmptySearchResults()\n\n        # Only fields that are indexed as a SearchField can be passed in fields\n        if fields:\n            allowed_fields = {field.field_name for field in model.get_searchable_search_fields()}\n\n            for field_name in fields:\n                if field_name not in allowed_fields:\n                    raise FieldError(\n                        'Cannot search with field \"' + field_name + '\". Please add index.SearchField(\\'' +\n                        field_name + '\\') to ' + model.__name__ + '.search_fields.'\n                    )\n\n        # Apply filters to queryset\n        if filters:\n            queryset = queryset.filter(**filters)\n\n        # Prefetch related\n        if prefetch_related:\n            for prefetch in prefetch_related:\n                queryset = queryset.prefetch_related(prefetch)\n\n        # Check operator\n        if operator is not None:\n            operator = operator.lower()\n            if operator not in ['or', 'and']:\n                raise ValueError(\"operator must be either 'or' or 'and'\")\n\n        # Search\n        search_query = self.query_class(\n            queryset, query_string, fields=fields, operator=operator, order_by_relevance=order_by_relevance\n        )\n        return self.results_class(self, search_query)\n", "path": "wagtail/wagtailsearch/backends/base.py"}]}