From cc220ba8be8ec263cd55936d77c6e39b61559d5e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rn=20K=C3=BCnsem=C3=B6ller?=
 <jkuensem@physik.uni-bielefeld.de>
Date: Wed, 31 Mar 2021 20:03:57 +0200
Subject: [PATCH] TMSS-230: Enable generic filtering by default, and improve
 custom filters on SUs

---
 .../tmss/tmssapp/viewsets/lofar_viewset.py    | 64 ++++++-------------
 .../tmss/tmssapp/viewsets/specification.py    | 31 +++++++--
 2 files changed, 45 insertions(+), 50 deletions(-)

diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/lofar_viewset.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/lofar_viewset.py
index fa74b76b1a3..86631f7c703 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/lofar_viewset.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/lofar_viewset.py
@@ -19,50 +19,27 @@ from rest_framework.decorators import action
 from lofar.common import json_utils
 from lofar.sas.tmss.tmss.tmssapp.viewsets.permissions import TMSSPermissions, IsProjectMemberFilterBackend
 from lofar.sas.tmss.tmss.tmssapp.models import permissions
-from django_filters.rest_framework import DjangoFilterBackend
+from django_filters.rest_framework import DjangoFilterBackend, FilterSet, CharFilter
+from django_filters import filterset
 from rest_framework.filters import OrderingFilter
+from django.contrib.postgres.fields import JSONField, ArrayField
+from copy import deepcopy
 
-#class TMSSPermissionsMixin:
-
-    # def __init__(self, *args, **kwargs):
-    #     self.permission_classes = (TMSSPermissions,)
-    #     self.filter_backends = (IsProjectMemberFilterBackend,)
-    #     self.extra_action_permission_classes = self._create_extra_action_permission_classes()
-    #     super(TMSSPermissionsMixin, self).__init__(*args, **kwargs)
-    #
-    # # TODO: Cache this method to avoid redundancy and overhead.
-    # def _create_extra_action_permission_classes(self):
-    #     extra_action_permission_classes = []
-    #     extra_actions = [a.__name__ for a in self.get_extra_actions()]
-    #     for ea in extra_actions:  # Create permission classes
-    #         permission_name = f'{ea}_{self.serializer_class.Meta.model.__name__.lower()}'
-    #         permission_class_name = f'Can {ea} {self.serializer_class.Meta.model.__name__.lower()}'
-    #         new_permission_class = type(f'{permission_class_name}', (permissions.TMSSBasePermissions,), {
-    #             # TODO: Is it necessary to have both permissions and object permissions?
-    #             # TODO: Find a way to use the "%(app_label)s." syntax.
-    #             'permission_name': permission_name,
-    #             'has_permission': lambda self, request, view: request.user.has_perm(f'tmssapp.{self.permission_name}'),
-    #             'has_object_permission': lambda self, request, view, obj: request.user.has_perm(f'tmssapp.{self.permission_name}'),
-    #         })
-    #         new_permission_class.__setattr__(self, 'permission_name', permission_name)
-    #         extra_action_permission_classes.append({ea: new_permission_class},)
-    #     return extra_action_permission_classes
-    #
-    # # TODO: Refactoring.
-    # def get_model_permissions(self):
-    #     extra_actions = [a.__name__ for a in self.get_extra_actions()]
-    #     if self.action in extra_actions:
-    #         for ea_permission_class in self.extra_action_permission_classes:
-    #             if ea_permission_class.get(self.action):
-    #                 return [permissions.TMSSBasePermissions, ea_permission_class.get(self.action),]
-    #             else:
-    #                 return [permissions.TMSSBasePermissions,]
-    #     else:
-    #         return [permissions.TMSSBasePermissions, ]
-
-    #def get_permissions(self):
-    #    self.get_extra_action_permission_classes()
-    #    return super(TMSSPermissionsMixin, self).get_permissions()
+class LOFARDefaultFilterSet(FilterSet):
+    FILTER_DEFAULTS = deepcopy(filterset.FILTER_FOR_DBFIELD_DEFAULTS)
+    FILTER_DEFAULTS.update({
+        JSONField: {
+            'filter_class': CharFilter
+        },
+        ArrayField: {
+            'filter_class': CharFilter,
+            'extra': lambda f: {'lookup_expr': 'icontains'}
+        },
+    })
+
+
+class LOFARFilterBackend(DjangoFilterBackend):
+    default_filter_set = LOFARDefaultFilterSet
 
 
 class LOFARViewSet(viewsets.ModelViewSet):
@@ -71,7 +48,8 @@ class LOFARViewSet(viewsets.ModelViewSet):
     the `format=None` keyword argument for each action.
     """
     permission_classes = (TMSSPermissions,)
-    filter_backends = (DjangoFilterBackend, OrderingFilter, IsProjectMemberFilterBackend,)
+    filter_backends = (LOFARFilterBackend, OrderingFilter, IsProjectMemberFilterBackend,)
+    filter_fields = '__all__'
 
     @swagger_auto_schema(responses={403: 'forbidden'})
     def list(self, request, **kwargs):
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py
index 620742eaa77..f5e8f1d554b 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py
@@ -19,7 +19,7 @@ from rest_framework.decorators import action
 from drf_yasg.utils import swagger_auto_schema
 from drf_yasg.openapi import Parameter
 
-from lofar.sas.tmss.tmss.tmssapp.viewsets.lofar_viewset import LOFARViewSet, LOFARNestedViewSet, AbstractTemplateViewSet, LOFARCopyViewSet
+from lofar.sas.tmss.tmss.tmssapp.viewsets.lofar_viewset import LOFARViewSet, LOFARNestedViewSet, AbstractTemplateViewSet, LOFARCopyViewSet, LOFARFilterBackend
 from lofar.sas.tmss.tmss.tmssapp import models
 from lofar.sas.tmss.tmss.tmssapp import serializers
 from django.http import JsonResponse
@@ -32,7 +32,6 @@ from lofar.sas.tmss.tmss.tmssapp.subtasks import *
 from lofar.sas.tmss.tmss.tmssapp.viewsets.permissions import TMSSDjangoModelPermissions
 
 from django.urls  import resolve, get_script_prefix,Resolver404
-from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework.filters import OrderingFilter
 
 import json
@@ -312,7 +311,7 @@ class TaskConnectorTypeViewSet(LOFARViewSet):
 
 class CycleViewSet(LOFARViewSet):
     permission_classes = (TMSSDjangoModelPermissions,)      # override default project permission
-    filter_backends = (DjangoFilterBackend, OrderingFilter)                                    # override default project permission
+    filter_backends = (LOFARFilterBackend, OrderingFilter)                                    # override default project permission
     queryset = models.Cycle.objects.all()
     serializer_class = serializers.CycleSerializer
     ordering = ['start']
@@ -431,13 +430,22 @@ class SchedulingUnitDraftPropertyFilter(property_filters.PropertyFilterSet):
 
     class Meta:
         model = models.SchedulingUnitDraft
-        fields = ['project']
+        fields = '__all__'
+        filter_overrides = {
+            models.JSONField: {
+                'filter_class': property_filters.CharFilter,
+            },
+            models.ArrayField: {
+                'filter_class': property_filters.CharFilter,
+                'extra': lambda f: {'lookup_expr': 'icontains'}
+            },
+        }
 
 
 class SchedulingUnitDraftViewSet(LOFARViewSet):
     queryset = models.SchedulingUnitDraft.objects.all()
     serializer_class = serializers.SchedulingUnitDraftSerializer
-    filter_class = SchedulingUnitDraftPropertyFilter
+    filter_class = SchedulingUnitDraftPropertyFilter    # note that this breaks other filter backends from LOFARViewSet
 
     # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
     queryset = queryset.prefetch_related('copied_from') \
@@ -733,13 +741,22 @@ class SchedulingUnitBlueprintPropertyFilter(property_filters.PropertyFilterSet):
 
     class Meta:
         model = models.SchedulingUnitBlueprint
-        fields = ['start_time', 'stop_time', 'project']
+        fields = '__all__'
+        filter_overrides = {
+            models.JSONField: {
+                'filter_class': property_filters.CharFilter,
+            },
+            models.ArrayField: {
+                'filter_class': property_filters.CharFilter,
+                'extra': lambda f: {'lookup_expr': 'icontains'}
+            },
+        }
 
 
 class SchedulingUnitBlueprintViewSet(LOFARViewSet):
     queryset = models.SchedulingUnitBlueprint.objects.all()
     serializer_class = serializers.SchedulingUnitBlueprintSerializer
-    filter_class = SchedulingUnitBlueprintPropertyFilter
+    filter_class = SchedulingUnitBlueprintPropertyFilter  # note that this breaks other filter backends from LOFARViewSet
 
     # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries
     queryset = queryset.prefetch_related('task_blueprints')
-- 
GitLab