Repository: wagtail/wagtail

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



Discussion:

Resolving patch:
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 @@ def filter_queryset(self, request, queryset, view):
                         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,


Test patch:
diff --git a/wagtail/api/v2/tests/test_pages.py b/wagtail/api/v2/tests/test_pages.py
--- a/wagtail/api/v2/tests/test_pages.py
+++ b/wagtail/api/v2/tests/test_pages.py
@@ -454,6 +454,13 @@ def test_filtering_on_id(self):
         page_id_list = self.get_page_id_list(content)
         self.assertEqual(page_id_list, [16])
 
+    def test_filtering_on_foreign_key(self):
+        response = self.get_response(type="demosite.contactpage", feed_image=7)
+        content = json.loads(response.content.decode('UTF-8'))
+
+        page_id_list = self.get_page_id_list(content)
+        self.assertEqual(page_id_list, [12])
+
     def test_filtering_on_boolean(self):
         response = self.get_response(show_in_menus='false')
         content = json.loads(response.content.decode('UTF-8'))
@@ -489,13 +496,20 @@ def test_filtering_unknown_field_gives_error(self):
         self.assertEqual(response.status_code, 400)
         self.assertEqual(content, {'message': "query parameter is not an operation or a recognised field: not_a_field"})
 
-    def test_filtering_int_validation(self):
+    def test_filtering_id_int_validation(self):
         response = self.get_response(id='abc')
         content = json.loads(response.content.decode('UTF-8'))
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(content, {'message': "field filter error. 'abc' is not a valid value for id (invalid literal for int() with base 10: 'abc')"})
 
+    def test_filtering_foreign_key_int_validation(self):
+        response = self.get_response(type="demosite.contactpage", feed_image='abc')
+        content = json.loads(response.content.decode('UTF-8'))
+
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(content['message'][:61], "field filter error. 'abc' is not a valid value for feed_image")
+
     def test_filtering_boolean_validation(self):
         response = self.get_response(show_in_menus='abc')
         content = json.loads(response.content.decode('UTF-8'))