From 8a7aac7f45e13e9d0207142091661826892e944f Mon Sep 17 00:00:00 2001 From: Filip Stenbacka Date: Wed, 30 Aug 2023 21:53:19 +0300 Subject: [PATCH] Added an extended CharFilter that can filter null/~null if given certain keywords. This resolves issue #178. --- teknologr/api/filters.py | 30 ++++++++++++++++++++------ teknologr/api/tests_filter.py | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/teknologr/api/filters.py b/teknologr/api/filters.py index d6f1ccd1..bed9b6fa 100644 --- a/teknologr/api/filters.py +++ b/teknologr/api/filters.py @@ -6,12 +6,30 @@ class BooleanFilter(django_filters.BooleanFilter): ''' - The defualt null label is for some reason 'unknown', but I want it to be '' + The default null label is for some reason 'unknown', but I want it to be '' ''' def __init__(self, *args, **kwargs): kwargs['widget'] = django_filters.widgets.BooleanWidget super().__init__(*args, **kwargs) +class CharFilterWithKeywords(django_filters.CharFilter): + ''' + Take a CharFilter. You can filter on a specific phrase or leave it empty, but what if you want to filter null/empty values? This filter applies predetermined null/~null filters if the value is a certain keyword: + - '-' => isnull=True + - '*' => isnull=False + + Not suitable for fields where the keywords are possible values, and does not apply if the 'method' argument is given in the constructor. + ''' + + def filter(self, qs, value): + q = Q(**{f'{self.field_name}__isnull': True}) | Q(**{f'{self.field_name}__exact': ''}) + if value == '-': + return qs.filter(q) + if value == '*': + return qs.exclude(q) + return super().filter(qs, value) + + class BaseFilter(django_filters.rest_framework.FilterSet): ''' Base filter class that takes care of normal users from using staff-only filters. @@ -92,11 +110,11 @@ class MemberFilter(BaseFilter): method='filter_address', label='Adressen innehåller', ) - email = django_filters.CharFilter( + email = CharFilterWithKeywords( lookup_expr='icontains', label='E-postadressen innehåller', ) - degree_programme = django_filters.CharFilter( + degree_programme = CharFilterWithKeywords( lookup_expr='icontains', label='Studieprogrammet innehåller', ) @@ -116,7 +134,7 @@ class MemberFilter(BaseFilter): birth_date = django_filters.DateFromToRangeFilter( label='Född mellan', ) - student_id = django_filters.CharFilter( + student_id = CharFilterWithKeywords( label='Studienummer', ) dead = BooleanFilter( @@ -135,10 +153,10 @@ class MemberFilter(BaseFilter): lookup_expr='icontains', label='Kommentaren innehåller', ) - username = django_filters.CharFilter( + username = CharFilterWithKeywords( label='Användarnamn', ) - bill_code = django_filters.CharFilter( + bill_code = CharFilterWithKeywords( label='BILL-konto', ) diff --git a/teknologr/api/tests_filter.py b/teknologr/api/tests_filter.py index 77feab7e..f04f01ea 100644 --- a/teknologr/api/tests_filter.py +++ b/teknologr/api/tests_filter.py @@ -810,6 +810,46 @@ def setUp(self): dead=False, ) +class MemberFilterUsernameNullTest(BaseAPITest, TestCases): + def setUp(self): + super().setUp() + + self.query = 'username=-' + self.n_normal = 3 # Filter does not work for normal users + self.n_staff = 2 + + # Should only be found by staff + Member.objects.create( + given_names='Test', + surname='von Test', + username='', + ) + Member.objects.create( + given_names='Test', + surname='von Test', + username=None, + ) + +class MemberFilterUsernameAnyTest(BaseAPITest, TestCases): + def setUp(self): + super().setUp() + + self.query = 'username=*' + self.n_normal = 3 # Filter does not work for normal users + self.n_staff = 1 + + # Should not be found by staff + Member.objects.create( + given_names='Test', + surname='von Test', + username='', + ) + Member.objects.create( + given_names='Test', + surname='von Test', + username=None, + ) + class MemberFilterBillCodeTest(BaseAPITest, TestCases): def setUp(self):