From a4ebb703e15a345865e2efd97b359c101e59df10 Mon Sep 17 00:00:00 2001
From: Lucas Brandstaetter <lucas@brandstaetter.tech>
Date: Thu, 28 Nov 2024 01:53:56 +0100
Subject: [PATCH] Update FilteredListView with additional filters

- Add possibility to exclude by prepending filter wiht '!'
- Add capability to filter and exclude using Q expressions
- Add trace logging for query filters
---
 src/core/views/list_views.py | 28 ++++++++++++++++++++++------
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/core/views/list_views.py b/src/core/views/list_views.py
index e5f1bc614..da8d19277 100644
--- a/src/core/views/list_views.py
+++ b/src/core/views/list_views.py
@@ -1,3 +1,4 @@
+import logging
 import string
 from typing import Any
 
@@ -12,6 +13,8 @@ from django.views.generic import ListView
 from core.models.tags import TagItem
 from core.paginators import AlphabetPaginator
 
+logger = logging.getLogger(__name__)
+
 
 class FilteredListView(ListView):
     filters: dict[str, tuple[str, str]] | None = None
@@ -54,27 +57,40 @@ class FilteredListView(ListView):
             },
         }
 
-    def get_queryset(self, queryset: QuerySet | None = None) -> QuerySet[Any]:
+    def get_queryset(self, queryset: QuerySet | None = None, query_filters: dict[str, str | list | bool] | None = None) -> QuerySet[Any]:
         queryset = super().get_queryset() if queryset is None else queryset
         ordering = self.get_ordering()
         if ordering:
             if isinstance(ordering, str):
                 ordering = (ordering,)
             queryset = queryset.order_by(*ordering)
-        query_filters = {}
+        query_filters = query_filters or {}
         get_parameters = getattr(self.request, 'GET', {})
         for parameter, (query_parameter, parameter_type) in self.filters.items():
             value: str | bool | list[str]
             if value := get_parameters.get(parameter, self.kwargs.get(parameter)):
                 self.params[parameter] = str(value)
                 match parameter_type:
-                    case 'bool':
-                        value = value == 'true'
                     case 'list':
                         value = value.split(',')
                         value = [x.strip() for x in value]
-                query_filters[query_parameter] = value
-        queryset = queryset.filter(**query_filters)
+                        query_filters[f'{query_parameter}__in'] = value
+                    case 'bool':
+                        value = value == 'true'
+                        query_filters[query_parameter] = value
+                    case _:
+                        query_filters[query_parameter] = value
+        logger.trace(f'Query Filters: {query_filters}')
+        for key, value in query_filters.items():
+            match key[0]:
+                case '!':
+                    queryset = queryset.exclude(**{key[1:]: value})
+                case 'X':
+                    queryset = queryset.exclude(value)
+                case 'Q':
+                    queryset = queryset.filter(value)
+                case _:
+                    queryset = queryset.filter(**{key: value})
         return queryset
 
 
-- 
GitLab