Repository: svthalia/concrexit

Issue:
Handle invalid filter values
### Describe the bug
https://thalia.sentry.io/issues/4448548023/?project=1463433&query=is%3Aunresolved+issue.category%3Aerror&referrer=issue-stream&stream_index=8

When passing e.g. "foo" to a boolean filter in some API views, you get 500 Internal Server Error, not 400 Bad Request.

### How to reproduce
Steps to reproduce the behaviour:
1. Go to `https://staging.thalia.nu/api/v2/admin/events/183/registrations/?queued=all`

### Expected behaviour
400


_Note that there are more such filters. Let's fix them all, not just this single one._


Discussion:

Resolving patch:
diff --git a/website/events/api/v2/admin/filters.py b/website/events/api/v2/admin/filters.py
--- a/website/events/api/v2/admin/filters.py
+++ b/website/events/api/v2/admin/filters.py
@@ -1,4 +1,5 @@
 from rest_framework import filters
+from rest_framework.exceptions import ValidationError
 
 from utils.snippets import strtobool
 
@@ -10,7 +11,10 @@ def filter_queryset(self, request, queryset, view):
         published = request.query_params.get("published", None)
 
         if published is not None:
-            queryset = queryset.filter(published=strtobool(published))
+            try:
+                queryset = queryset.filter(published=strtobool(published))
+            except ValueError as e:
+                raise ValidationError({"published": "Invalid filter value."}) from e
 
         return queryset
 
@@ -37,8 +41,11 @@ def filter_queryset(self, request, queryset, view):
         if cancelled is None:
             return queryset
 
-        if strtobool(cancelled):
-            return queryset.exclude(date_cancelled=None)
+        try:
+            if strtobool(cancelled):
+                return queryset.exclude(date_cancelled=None)
+        except ValueError as e:
+            raise ValidationError({"cancelled": "Invalid filter value."}) from e
 
         return queryset.filter(date_cancelled=None)
 
@@ -65,8 +72,11 @@ def filter_queryset(self, request, queryset, view):
         if queued is None:
             return queryset
 
-        if strtobool(queued):
-            return queryset.exclude(queue_position=None)
+        try:
+            if strtobool(queued):
+                return queryset.exclude(queue_position=None)
+        except ValueError as e:
+            raise ValidationError({"queued": "Invalid filter value."}) from e
 
         return queryset.filter(queue_position=None)
 
diff --git a/website/payments/api/v2/filters.py b/website/payments/api/v2/filters.py
--- a/website/payments/api/v2/filters.py
+++ b/website/payments/api/v2/filters.py
@@ -1,4 +1,5 @@
 from rest_framework import filters
+from rest_framework.exceptions import ValidationError
 
 from utils.snippets import extract_date_range, strtobool
 
@@ -73,8 +74,11 @@ def filter_queryset(self, request, queryset, view):
         if settled is None:
             return queryset
 
-        if strtobool(settled):
-            return queryset.filter(batch__processed=True)
+        try:
+            if strtobool(settled):
+                return queryset.filter(batch__processed=True)
+        except ValueError as e:
+            raise ValidationError({"settled": "Invalid filter value."}) from e
 
         return queryset.exclude(batch__processed=True)
 
diff --git a/website/sales/api/v2/filters.py b/website/sales/api/v2/filters.py
--- a/website/sales/api/v2/filters.py
+++ b/website/sales/api/v2/filters.py
@@ -1,4 +1,5 @@
 from rest_framework import filters
+from rest_framework.exceptions import ValidationError
 
 from utils.snippets import extract_date_range, strtobool
 
@@ -10,7 +11,10 @@ def filter_queryset(self, request, queryset, view):
         active = request.query_params.get("active", None)
 
         if active is not None:
-            queryset = queryset.filter(active=strtobool(active))
+            try:
+                queryset = queryset.filter(active=strtobool(active))
+            except ValueError as e:
+                raise ValidationError({"active": "Invalid filter value."}) from e
 
         return queryset
 
@@ -35,7 +39,10 @@ def filter_queryset(self, request, queryset, view):
         locked = request.query_params.get("locked", None)
 
         if locked is not None:
-            queryset = queryset.filter(locked=strtobool(locked))
+            try:
+                queryset = queryset.filter(locked=strtobool(locked))
+            except ValueError as e:
+                raise ValidationError({"locked": "Invalid filter value."}) from e
 
         return queryset
 


Test patch: