We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Wagtail API pages endpoint responds with a 500 error when entering an invalid value to a filter
### Issue Summary

When querying the Wagtail API and using filters, invalid filter values cause the server to respond with a 500 error.

Given a `BlogPage` with an `author` ForeignKey.

e.g. `/api/v2/pages/?type=blog.BlogPage&author=a`

This returns a 500 error.

### Steps to Reproduce

1. Create any page type that has a foreign key
2. Query that page type using the Wagtail API, and pass an invalid value to a filter (e.g. a letter to a foreign key field)
3. Server Error 500

Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?

* This is not a server error. It's a client error.
* We already have this error message for invalid IDs:

```
HTTP 400 Bad Request
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "message": "field filter error. 'a' is not a valid value for id (invalid literal for int() with base 10: 'a')"
}
```

* A 400 error is expected.


### Technical details

* Python version: 3.8.6.
* Django version: 3.1.
* Wagtail version: 2.12.
* Browser version: Firefox 87


--- 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/api/v2/filters.py`
Content:
```
1 from django.conf import settings
2 from django.db import models
3 from django.shortcuts import get_object_or_404
4 from rest_framework.filters import BaseFilterBackend
5 from taggit.managers import TaggableManager
6 
7 from wagtail.core.models import Locale, Page
8 from wagtail.search.backends import get_search_backend
9 from wagtail.search.backends.base import FilterFieldError, OrderByFieldError
10 
11 from .utils import BadRequestError, parse_boolean
12 
13 
14 class FieldsFilter(BaseFilterBackend):
15     def filter_queryset(self, request, queryset, view):
16         """
17         This performs field level filtering on the result set
18         Eg: ?title=James Joyce
19         """
20         fields = set(view.get_available_fields(queryset.model, db_fields_only=True))
21 
22         # Locale is a database field, but we provide a separate filter for it
23         if 'locale' in fields:
24             fields.remove('locale')
25 
26         for field_name, value in request.GET.items():
27             if field_name in fields:
28                 try:
29                     field = queryset.model._meta.get_field(field_name)
30                 except LookupError:
31                     field = None
32 
33                 # Convert value into python
34                 try:
35                     if isinstance(field, (models.BooleanField, models.NullBooleanField)):
36                         value = parse_boolean(value)
37                     elif isinstance(field, (models.IntegerField, models.AutoField)):
38                         value = int(value)
39                 except ValueError as e:
40                     raise BadRequestError("field filter error. '%s' is not a valid value for %s (%s)" % (
41                         value,
42                         field_name,
43                         str(e)
44                     ))
45 
46                 if isinstance(field, TaggableManager):
47                     for tag in value.split(','):
48                         queryset = queryset.filter(**{field_name + '__name': tag})
49 
50                     # Stick a message on the queryset to indicate that tag filtering has been performed
51                     # This will let the do_search method know that it must raise an error as searching
52                     # and tag filtering at the same time is not supported
53                     queryset._filtered_by_tag = True
54                 else:
55                     queryset = queryset.filter(**{field_name: value})
56 
57         return queryset
58 
59 
60 class OrderingFilter(BaseFilterBackend):
61     def filter_queryset(self, request, queryset, view):
62         """
63         This applies ordering to the result set
64         Eg: ?order=title
65 
66         It also supports reverse ordering
67         Eg: ?order=-title
68 
69         And random ordering
70         Eg: ?order=random
71         """
72         if 'order' in request.GET:
73             order_by = request.GET['order']
74 
75             # Random ordering
76             if order_by == 'random':
77                 # Prevent ordering by random with offset
78                 if 'offset' in request.GET:
79                     raise BadRequestError("random ordering with offset is not supported")
80 
81                 return queryset.order_by('?')
82 
83             # Check if reverse ordering is set
84             if order_by.startswith('-'):
85                 reverse_order = True
86                 order_by = order_by[1:]
87             else:
88                 reverse_order = False
89 
90             # Add ordering
91             if order_by in view.get_available_fields(queryset.model):
92                 queryset = queryset.order_by(order_by)
93             else:
94                 # Unknown field
95                 raise BadRequestError("cannot order by '%s' (unknown field)" % order_by)
96 
97             # Reverse order
98             if reverse_order:
99                 queryset = queryset.reverse()
100 
101         return queryset
102 
103 
104 class SearchFilter(BaseFilterBackend):
105     def filter_queryset(self, request, queryset, view):
106         """
107         This performs a full-text search on the result set
108         Eg: ?search=James Joyce
109         """
110         search_enabled = getattr(settings, 'WAGTAILAPI_SEARCH_ENABLED', True)
111 
112         if 'search' in request.GET:
113             if not search_enabled:
114                 raise BadRequestError("search is disabled")
115 
116             # Searching and filtering by tag at the same time is not supported
117             if getattr(queryset, '_filtered_by_tag', False):
118                 raise BadRequestError("filtering by tag with a search query is not supported")
119 
120             search_query = request.GET['search']
121             search_operator = request.GET.get('search_operator', None)
122             order_by_relevance = 'order' not in request.GET
123 
124             sb = get_search_backend()
125             try:
126                 queryset = sb.search(search_query, queryset, operator=search_operator, order_by_relevance=order_by_relevance)
127             except FilterFieldError as e:
128                 raise BadRequestError("cannot filter by '{}' while searching (field is not indexed)".format(e.field_name))
129             except OrderByFieldError as e:
130                 raise BadRequestError("cannot order by '{}' while searching (field is not indexed)".format(e.field_name))
131 
132         return queryset
133 
134 
135 class ChildOfFilter(BaseFilterBackend):
136     """
137     Implements the ?child_of filter used to filter the results to only contain
138     pages that are direct children of the specified page.
139     """
140     def filter_queryset(self, request, queryset, view):
141         if 'child_of' in request.GET:
142             try:
143                 parent_page_id = int(request.GET['child_of'])
144                 if parent_page_id < 0:
145                     raise ValueError()
146 
147                 parent_page = view.get_base_queryset().get(id=parent_page_id)
148             except ValueError:
149                 if request.GET['child_of'] == 'root':
150                     parent_page = view.get_root_page()
151                 else:
152                     raise BadRequestError("child_of must be a positive integer")
153             except Page.DoesNotExist:
154                 raise BadRequestError("parent page doesn't exist")
155 
156             queryset = queryset.child_of(parent_page)
157 
158             # Save the parent page on the queryset. This is required for the page
159             # explorer, which needs to pass the parent page into
160             # `construct_explorer_page_queryset` hook functions
161             queryset._filtered_by_child_of = parent_page
162 
163         return queryset
164 
165 
166 class DescendantOfFilter(BaseFilterBackend):
167     """
168     Implements the ?decendant_of filter which limits the set of pages to a
169     particular branch of the page tree.
170     """
171     def filter_queryset(self, request, queryset, view):
172         if 'descendant_of' in request.GET:
173             if hasattr(queryset, '_filtered_by_child_of'):
174                 raise BadRequestError("filtering by descendant_of with child_of is not supported")
175             try:
176                 parent_page_id = int(request.GET['descendant_of'])
177                 if parent_page_id < 0:
178                     raise ValueError()
179 
180                 parent_page = view.get_base_queryset().get(id=parent_page_id)
181             except ValueError:
182                 if request.GET['descendant_of'] == 'root':
183                     parent_page = view.get_root_page()
184                 else:
185                     raise BadRequestError("descendant_of must be a positive integer")
186             except Page.DoesNotExist:
187                 raise BadRequestError("ancestor page doesn't exist")
188 
189             queryset = queryset.descendant_of(parent_page)
190 
191         return queryset
192 
193 
194 class TranslationOfFilter(BaseFilterBackend):
195     """
196     Implements the ?translation_of filter which limits the set of pages to translations
197     of a page.
198     """
199     def filter_queryset(self, request, queryset, view):
200         if 'translation_of' in request.GET:
201             try:
202                 page_id = int(request.GET['translation_of'])
203                 if page_id < 0:
204                     raise ValueError()
205 
206                 page = view.get_base_queryset().get(id=page_id)
207             except ValueError:
208                 if request.GET['translation_of'] == 'root':
209                     page = view.get_root_page()
210                 else:
211                     raise BadRequestError("translation_of must be a positive integer")
212             except Page.DoesNotExist:
213                 raise BadRequestError("translation_of page doesn't exist")
214 
215             _filtered_by_child_of = getattr(queryset, '_filtered_by_child_of', None)
216 
217             queryset = queryset.translation_of(page)
218 
219             if _filtered_by_child_of:
220                 queryset._filtered_by_child_of = _filtered_by_child_of
221 
222         return queryset
223 
224 
225 class LocaleFilter(BaseFilterBackend):
226     """
227     Implements the ?locale filter which limits the set of pages to a
228     particular locale.
229     """
230     def filter_queryset(self, request, queryset, view):
231         if 'locale' in request.GET:
232             _filtered_by_child_of = getattr(queryset, '_filtered_by_child_of', None)
233 
234             locale = get_object_or_404(Locale, language_code=request.GET['locale'])
235             queryset = queryset.filter(locale=locale)
236 
237             if _filtered_by_child_of:
238                 queryset._filtered_by_child_of = _filtered_by_child_of
239 
240         return queryset
241
```
--- 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/api/v2/filters.py b/wagtail/api/v2/filters.py
--- a/wagtail/api/v2/filters.py
+++ b/wagtail/api/v2/filters.py
@@ -36,6 +36,8 @@
                         value = parse_boolean(value)
                     elif isinstance(field, (models.IntegerField, models.AutoField)):
                         value = int(value)
+                    elif isinstance(field, models.ForeignKey):
+                        value = field.target_field.get_prep_value(value)
                 except ValueError as e:
                     raise BadRequestError("field filter error. '%s' is not a valid value for %s (%s)" % (
                         value,

{"golden_diff": "diff --git a/wagtail/api/v2/filters.py b/wagtail/api/v2/filters.py\n--- a/wagtail/api/v2/filters.py\n+++ b/wagtail/api/v2/filters.py\n@@ -36,6 +36,8 @@\n                         value = parse_boolean(value)\n                     elif isinstance(field, (models.IntegerField, models.AutoField)):\n                         value = int(value)\n+                    elif isinstance(field, models.ForeignKey):\n+                        value = field.target_field.get_prep_value(value)\n                 except ValueError as e:\n                     raise BadRequestError(\"field filter error. '%s' is not a valid value for %s (%s)\" % (\n                         value,\n", "issue": "Wagtail API pages endpoint responds with a 500 error when entering an invalid value to a filter\n### Issue Summary\r\n\r\nWhen querying the Wagtail API and using filters, invalid filter values cause the server to respond with a 500 error.\r\n\r\nGiven a `BlogPage` with an `author` ForeignKey.\r\n\r\ne.g. `/api/v2/pages/?type=blog.BlogPage&author=a`\r\n\r\nThis returns a 500 error.\r\n\r\n### Steps to Reproduce\r\n\r\n1. Create any page type that has a foreign key\r\n2. Query that page type using the Wagtail API, and pass an invalid value to a filter (e.g. a letter to a foreign key field)\r\n3. Server Error 500\r\n\r\nAny other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?\r\n\r\n* This is not a server error. It's a client error.\r\n* We already have this error message for invalid IDs:\r\n\r\n```\r\nHTTP 400 Bad Request\r\nAllow: GET, HEAD, OPTIONS\r\nContent-Type: application/json\r\nVary: Accept\r\n\r\n{\r\n    \"message\": \"field filter error. 'a' is not a valid value for id (invalid literal for int() with base 10: 'a')\"\r\n}\r\n```\r\n\r\n* A 400 error is expected.\r\n\r\n\r\n### Technical details\r\n\r\n* Python version: 3.8.6.\r\n* Django version: 3.1.\r\n* Wagtail version: 2.12.\r\n* Browser version: Firefox 87\r\n\n", "before_files": [{"content": "from django.conf import settings\nfrom django.db import models\nfrom django.shortcuts import get_object_or_404\nfrom rest_framework.filters import BaseFilterBackend\nfrom taggit.managers import TaggableManager\n\nfrom wagtail.core.models import Locale, Page\nfrom wagtail.search.backends import get_search_backend\nfrom wagtail.search.backends.base import FilterFieldError, OrderByFieldError\n\nfrom .utils import BadRequestError, parse_boolean\n\n\nclass FieldsFilter(BaseFilterBackend):\n    def filter_queryset(self, request, queryset, view):\n        \"\"\"\n        This performs field level filtering on the result set\n        Eg: ?title=James Joyce\n        \"\"\"\n        fields = set(view.get_available_fields(queryset.model, db_fields_only=True))\n\n        # Locale is a database field, but we provide a separate filter for it\n        if 'locale' in fields:\n            fields.remove('locale')\n\n        for field_name, value in request.GET.items():\n            if field_name in fields:\n                try:\n                    field = queryset.model._meta.get_field(field_name)\n                except LookupError:\n                    field = None\n\n                # Convert value into python\n                try:\n                    if isinstance(field, (models.BooleanField, models.NullBooleanField)):\n                        value = parse_boolean(value)\n                    elif isinstance(field, (models.IntegerField, models.AutoField)):\n                        value = int(value)\n                except ValueError as e:\n                    raise BadRequestError(\"field filter error. '%s' is not a valid value for %s (%s)\" % (\n                        value,\n                        field_name,\n                        str(e)\n                    ))\n\n                if isinstance(field, TaggableManager):\n                    for tag in value.split(','):\n                        queryset = queryset.filter(**{field_name + '__name': tag})\n\n                    # Stick a message on the queryset to indicate that tag filtering has been performed\n                    # This will let the do_search method know that it must raise an error as searching\n                    # and tag filtering at the same time is not supported\n                    queryset._filtered_by_tag = True\n                else:\n                    queryset = queryset.filter(**{field_name: value})\n\n        return queryset\n\n\nclass OrderingFilter(BaseFilterBackend):\n    def filter_queryset(self, request, queryset, view):\n        \"\"\"\n        This applies ordering to the result set\n        Eg: ?order=title\n\n        It also supports reverse ordering\n        Eg: ?order=-title\n\n        And random ordering\n        Eg: ?order=random\n        \"\"\"\n        if 'order' in request.GET:\n            order_by = request.GET['order']\n\n            # Random ordering\n            if order_by == 'random':\n                # Prevent ordering by random with offset\n                if 'offset' in request.GET:\n                    raise BadRequestError(\"random ordering with offset is not supported\")\n\n                return queryset.order_by('?')\n\n            # Check if reverse ordering is set\n            if order_by.startswith('-'):\n                reverse_order = True\n                order_by = order_by[1:]\n            else:\n                reverse_order = False\n\n            # Add ordering\n            if order_by in view.get_available_fields(queryset.model):\n                queryset = queryset.order_by(order_by)\n            else:\n                # Unknown field\n                raise BadRequestError(\"cannot order by '%s' (unknown field)\" % order_by)\n\n            # Reverse order\n            if reverse_order:\n                queryset = queryset.reverse()\n\n        return queryset\n\n\nclass SearchFilter(BaseFilterBackend):\n    def filter_queryset(self, request, queryset, view):\n        \"\"\"\n        This performs a full-text search on the result set\n        Eg: ?search=James Joyce\n        \"\"\"\n        search_enabled = getattr(settings, 'WAGTAILAPI_SEARCH_ENABLED', True)\n\n        if 'search' in request.GET:\n            if not search_enabled:\n                raise BadRequestError(\"search is disabled\")\n\n            # Searching and filtering by tag at the same time is not supported\n            if getattr(queryset, '_filtered_by_tag', False):\n                raise BadRequestError(\"filtering by tag with a search query is not supported\")\n\n            search_query = request.GET['search']\n            search_operator = request.GET.get('search_operator', None)\n            order_by_relevance = 'order' not in request.GET\n\n            sb = get_search_backend()\n            try:\n                queryset = sb.search(search_query, queryset, operator=search_operator, order_by_relevance=order_by_relevance)\n            except FilterFieldError as e:\n                raise BadRequestError(\"cannot filter by '{}' while searching (field is not indexed)\".format(e.field_name))\n            except OrderByFieldError as e:\n                raise BadRequestError(\"cannot order by '{}' while searching (field is not indexed)\".format(e.field_name))\n\n        return queryset\n\n\nclass ChildOfFilter(BaseFilterBackend):\n    \"\"\"\n    Implements the ?child_of filter used to filter the results to only contain\n    pages that are direct children of the specified page.\n    \"\"\"\n    def filter_queryset(self, request, queryset, view):\n        if 'child_of' in request.GET:\n            try:\n                parent_page_id = int(request.GET['child_of'])\n                if parent_page_id < 0:\n                    raise ValueError()\n\n                parent_page = view.get_base_queryset().get(id=parent_page_id)\n            except ValueError:\n                if request.GET['child_of'] == 'root':\n                    parent_page = view.get_root_page()\n                else:\n                    raise BadRequestError(\"child_of must be a positive integer\")\n            except Page.DoesNotExist:\n                raise BadRequestError(\"parent page doesn't exist\")\n\n            queryset = queryset.child_of(parent_page)\n\n            # Save the parent page on the queryset. This is required for the page\n            # explorer, which needs to pass the parent page into\n            # `construct_explorer_page_queryset` hook functions\n            queryset._filtered_by_child_of = parent_page\n\n        return queryset\n\n\nclass DescendantOfFilter(BaseFilterBackend):\n    \"\"\"\n    Implements the ?decendant_of filter which limits the set of pages to a\n    particular branch of the page tree.\n    \"\"\"\n    def filter_queryset(self, request, queryset, view):\n        if 'descendant_of' in request.GET:\n            if hasattr(queryset, '_filtered_by_child_of'):\n                raise BadRequestError(\"filtering by descendant_of with child_of is not supported\")\n            try:\n                parent_page_id = int(request.GET['descendant_of'])\n                if parent_page_id < 0:\n                    raise ValueError()\n\n                parent_page = view.get_base_queryset().get(id=parent_page_id)\n            except ValueError:\n                if request.GET['descendant_of'] == 'root':\n                    parent_page = view.get_root_page()\n                else:\n                    raise BadRequestError(\"descendant_of must be a positive integer\")\n            except Page.DoesNotExist:\n                raise BadRequestError(\"ancestor page doesn't exist\")\n\n            queryset = queryset.descendant_of(parent_page)\n\n        return queryset\n\n\nclass TranslationOfFilter(BaseFilterBackend):\n    \"\"\"\n    Implements the ?translation_of filter which limits the set of pages to translations\n    of a page.\n    \"\"\"\n    def filter_queryset(self, request, queryset, view):\n        if 'translation_of' in request.GET:\n            try:\n                page_id = int(request.GET['translation_of'])\n                if page_id < 0:\n                    raise ValueError()\n\n                page = view.get_base_queryset().get(id=page_id)\n            except ValueError:\n                if request.GET['translation_of'] == 'root':\n                    page = view.get_root_page()\n                else:\n                    raise BadRequestError(\"translation_of must be a positive integer\")\n            except Page.DoesNotExist:\n                raise BadRequestError(\"translation_of page doesn't exist\")\n\n            _filtered_by_child_of = getattr(queryset, '_filtered_by_child_of', None)\n\n            queryset = queryset.translation_of(page)\n\n            if _filtered_by_child_of:\n                queryset._filtered_by_child_of = _filtered_by_child_of\n\n        return queryset\n\n\nclass LocaleFilter(BaseFilterBackend):\n    \"\"\"\n    Implements the ?locale filter which limits the set of pages to a\n    particular locale.\n    \"\"\"\n    def filter_queryset(self, request, queryset, view):\n        if 'locale' in request.GET:\n            _filtered_by_child_of = getattr(queryset, '_filtered_by_child_of', None)\n\n            locale = get_object_or_404(Locale, language_code=request.GET['locale'])\n            queryset = queryset.filter(locale=locale)\n\n            if _filtered_by_child_of:\n                queryset._filtered_by_child_of = _filtered_by_child_of\n\n        return queryset\n", "path": "wagtail/api/v2/filters.py"}], "after_files": [{"content": "from django.conf import settings\nfrom django.db import models\nfrom django.shortcuts import get_object_or_404\nfrom rest_framework.filters import BaseFilterBackend\nfrom taggit.managers import TaggableManager\n\nfrom wagtail.core.models import Locale, Page\nfrom wagtail.search.backends import get_search_backend\nfrom wagtail.search.backends.base import FilterFieldError, OrderByFieldError\n\nfrom .utils import BadRequestError, parse_boolean\n\n\nclass FieldsFilter(BaseFilterBackend):\n    def filter_queryset(self, request, queryset, view):\n        \"\"\"\n        This performs field level filtering on the result set\n        Eg: ?title=James Joyce\n        \"\"\"\n        fields = set(view.get_available_fields(queryset.model, db_fields_only=True))\n\n        # Locale is a database field, but we provide a separate filter for it\n        if 'locale' in fields:\n            fields.remove('locale')\n\n        for field_name, value in request.GET.items():\n            if field_name in fields:\n                try:\n                    field = queryset.model._meta.get_field(field_name)\n                except LookupError:\n                    field = None\n\n                # Convert value into python\n                try:\n                    if isinstance(field, (models.BooleanField, models.NullBooleanField)):\n                        value = parse_boolean(value)\n                    elif isinstance(field, (models.IntegerField, models.AutoField)):\n                        value = int(value)\n                    elif isinstance(field, models.ForeignKey):\n                        value = field.target_field.get_prep_value(value)\n                except ValueError as e:\n                    raise BadRequestError(\"field filter error. '%s' is not a valid value for %s (%s)\" % (\n                        value,\n                        field_name,\n                        str(e)\n                    ))\n\n                if isinstance(field, TaggableManager):\n                    for tag in value.split(','):\n                        queryset = queryset.filter(**{field_name + '__name': tag})\n\n                    # Stick a message on the queryset to indicate that tag filtering has been performed\n                    # This will let the do_search method know that it must raise an error as searching\n                    # and tag filtering at the same time is not supported\n                    queryset._filtered_by_tag = True\n                else:\n                    queryset = queryset.filter(**{field_name: value})\n\n        return queryset\n\n\nclass OrderingFilter(BaseFilterBackend):\n    def filter_queryset(self, request, queryset, view):\n        \"\"\"\n        This applies ordering to the result set\n        Eg: ?order=title\n\n        It also supports reverse ordering\n        Eg: ?order=-title\n\n        And random ordering\n        Eg: ?order=random\n        \"\"\"\n        if 'order' in request.GET:\n            order_by = request.GET['order']\n\n            # Random ordering\n            if order_by == 'random':\n                # Prevent ordering by random with offset\n                if 'offset' in request.GET:\n                    raise BadRequestError(\"random ordering with offset is not supported\")\n\n                return queryset.order_by('?')\n\n            # Check if reverse ordering is set\n            if order_by.startswith('-'):\n                reverse_order = True\n                order_by = order_by[1:]\n            else:\n                reverse_order = False\n\n            # Add ordering\n            if order_by in view.get_available_fields(queryset.model):\n                queryset = queryset.order_by(order_by)\n            else:\n                # Unknown field\n                raise BadRequestError(\"cannot order by '%s' (unknown field)\" % order_by)\n\n            # Reverse order\n            if reverse_order:\n                queryset = queryset.reverse()\n\n        return queryset\n\n\nclass SearchFilter(BaseFilterBackend):\n    def filter_queryset(self, request, queryset, view):\n        \"\"\"\n        This performs a full-text search on the result set\n        Eg: ?search=James Joyce\n        \"\"\"\n        search_enabled = getattr(settings, 'WAGTAILAPI_SEARCH_ENABLED', True)\n\n        if 'search' in request.GET:\n            if not search_enabled:\n                raise BadRequestError(\"search is disabled\")\n\n            # Searching and filtering by tag at the same time is not supported\n            if getattr(queryset, '_filtered_by_tag', False):\n                raise BadRequestError(\"filtering by tag with a search query is not supported\")\n\n            search_query = request.GET['search']\n            search_operator = request.GET.get('search_operator', None)\n            order_by_relevance = 'order' not in request.GET\n\n            sb = get_search_backend()\n            try:\n                queryset = sb.search(search_query, queryset, operator=search_operator, order_by_relevance=order_by_relevance)\n            except FilterFieldError as e:\n                raise BadRequestError(\"cannot filter by '{}' while searching (field is not indexed)\".format(e.field_name))\n            except OrderByFieldError as e:\n                raise BadRequestError(\"cannot order by '{}' while searching (field is not indexed)\".format(e.field_name))\n\n        return queryset\n\n\nclass ChildOfFilter(BaseFilterBackend):\n    \"\"\"\n    Implements the ?child_of filter used to filter the results to only contain\n    pages that are direct children of the specified page.\n    \"\"\"\n    def filter_queryset(self, request, queryset, view):\n        if 'child_of' in request.GET:\n            try:\n                parent_page_id = int(request.GET['child_of'])\n                if parent_page_id < 0:\n                    raise ValueError()\n\n                parent_page = view.get_base_queryset().get(id=parent_page_id)\n            except ValueError:\n                if request.GET['child_of'] == 'root':\n                    parent_page = view.get_root_page()\n                else:\n                    raise BadRequestError(\"child_of must be a positive integer\")\n            except Page.DoesNotExist:\n                raise BadRequestError(\"parent page doesn't exist\")\n\n            queryset = queryset.child_of(parent_page)\n\n            # Save the parent page on the queryset. This is required for the page\n            # explorer, which needs to pass the parent page into\n            # `construct_explorer_page_queryset` hook functions\n            queryset._filtered_by_child_of = parent_page\n\n        return queryset\n\n\nclass DescendantOfFilter(BaseFilterBackend):\n    \"\"\"\n    Implements the ?decendant_of filter which limits the set of pages to a\n    particular branch of the page tree.\n    \"\"\"\n    def filter_queryset(self, request, queryset, view):\n        if 'descendant_of' in request.GET:\n            if hasattr(queryset, '_filtered_by_child_of'):\n                raise BadRequestError(\"filtering by descendant_of with child_of is not supported\")\n            try:\n                parent_page_id = int(request.GET['descendant_of'])\n                if parent_page_id < 0:\n                    raise ValueError()\n\n                parent_page = view.get_base_queryset().get(id=parent_page_id)\n            except ValueError:\n                if request.GET['descendant_of'] == 'root':\n                    parent_page = view.get_root_page()\n                else:\n                    raise BadRequestError(\"descendant_of must be a positive integer\")\n            except Page.DoesNotExist:\n                raise BadRequestError(\"ancestor page doesn't exist\")\n\n            queryset = queryset.descendant_of(parent_page)\n\n        return queryset\n\n\nclass TranslationOfFilter(BaseFilterBackend):\n    \"\"\"\n    Implements the ?translation_of filter which limits the set of pages to translations\n    of a page.\n    \"\"\"\n    def filter_queryset(self, request, queryset, view):\n        if 'translation_of' in request.GET:\n            try:\n                page_id = int(request.GET['translation_of'])\n                if page_id < 0:\n                    raise ValueError()\n\n                page = view.get_base_queryset().get(id=page_id)\n            except ValueError:\n                if request.GET['translation_of'] == 'root':\n                    page = view.get_root_page()\n                else:\n                    raise BadRequestError(\"translation_of must be a positive integer\")\n            except Page.DoesNotExist:\n                raise BadRequestError(\"translation_of page doesn't exist\")\n\n            _filtered_by_child_of = getattr(queryset, '_filtered_by_child_of', None)\n\n            queryset = queryset.translation_of(page)\n\n            if _filtered_by_child_of:\n                queryset._filtered_by_child_of = _filtered_by_child_of\n\n        return queryset\n\n\nclass LocaleFilter(BaseFilterBackend):\n    \"\"\"\n    Implements the ?locale filter which limits the set of pages to a\n    particular locale.\n    \"\"\"\n    def filter_queryset(self, request, queryset, view):\n        if 'locale' in request.GET:\n            _filtered_by_child_of = getattr(queryset, '_filtered_by_child_of', None)\n\n            locale = get_object_or_404(Locale, language_code=request.GET['locale'])\n            queryset = queryset.filter(locale=locale)\n\n            if _filtered_by_child_of:\n                queryset._filtered_by_child_of = _filtered_by_child_of\n\n        return queryset\n", "path": "wagtail/api/v2/filters.py"}]}