2323from django .apps import apps
2424from django .core .validators import EMPTY_VALUES
2525from django .db import models
26+ from django .db .models import Q
2627from django .db .models .fields import BLANK_CHOICE_DASH
2728from django .utils .http import urlencode
2829from django .utils .translation import gettext as _
4142scanpipe_app = apps .get_app_config ("scanpipe" )
4243
4344PAGE_VAR = "page"
45+ EMPTY_VAR = "_EMPTY_"
46+ ANY_VAR = "_ANY_"
47+ OTHER_VAR = "_OTHER_"
4448
4549
4650class FilterSetUtilsMixin :
47- empty_value = "_EMPTY_"
48- other_value = "_OTHER_"
51+ empty_value = EMPTY_VAR
52+ any_value = ANY_VAR
53+ other_value = OTHER_VAR
4954
5055 @staticmethod
5156 def remove_field_from_query_dict (query_dict , field_name , remove_value = None ):
@@ -122,6 +127,8 @@ def filter_queryset(self, queryset):
122127 field_name = self .filters [name ].field_name
123128 if value == self .empty_value :
124129 queryset = queryset .filter (** {f"{ field_name } __in" : EMPTY_VALUES })
130+ elif value == self .any_value :
131+ queryset = queryset .filter (~ Q (** {f"{ field_name } __in" : EMPTY_VALUES }))
125132 elif value == self .other_value and hasattr (queryset , "less_common" ):
126133 return queryset .less_common (name )
127134 else :
@@ -170,6 +177,16 @@ class BulmaDropdownWidget(BulmaLinkWidget):
170177 extra_css_class = "dropdown-item"
171178
172179
180+ class HasValueDropdownWidget (BulmaDropdownWidget ):
181+ def __init__ (self , attrs = None , choices = ()):
182+ super ().__init__ (attrs )
183+ self .choices = (
184+ ("" , "All" ),
185+ (EMPTY_VAR , "None" ),
186+ (ANY_VAR , "Any" ),
187+ )
188+
189+
173190class ProjectFilterSet (FilterSetUtilsMixin , django_filters .FilterSet ):
174191 search = django_filters .CharFilter (
175192 label = "Search" , field_name = "name" , lookup_expr = "icontains"
@@ -268,8 +285,8 @@ def filter(self, qs, value):
268285class InPackageFilter (django_filters .ChoiceFilter ):
269286 def __init__ (self , * args , ** kwargs ):
270287 kwargs ["choices" ] = (
271- ("true" , "Yes " ),
272- ("false" , "No " ),
288+ ("true" , "In a package " ),
289+ ("false" , "Not in a package " ),
273290 )
274291 super ().__init__ (* args , ** kwargs )
275292
@@ -314,6 +331,14 @@ def filter(self, qs, value):
314331
315332
316333class ResourceFilterSet (FilterSetUtilsMixin , django_filters .FilterSet ):
334+ dropdown_widget = [
335+ "status" ,
336+ "type" ,
337+ "compliance_alert" ,
338+ "in_package" ,
339+ "relation_map_type" ,
340+ ]
341+
317342 search = django_filters .CharFilter (
318343 label = "Search" ,
319344 field_name = "path" ,
@@ -338,14 +363,13 @@ class ResourceFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
338363 ],
339364 )
340365 compliance_alert = django_filters .ChoiceFilter (
341- choices = CodebaseResource . Compliance . choices + [( "_EMPTY_" , "EMPTY " )]
366+ choices = [( EMPTY_VAR , "None " )] + CodebaseResource . Compliance . choices ,
342367 )
343- in_package = InPackageFilter (label = "In a Package " )
344- status = StatusFilter (empty_label = "All" )
368+ in_package = InPackageFilter (label = "In a package " )
369+ status = StatusFilter ()
345370 relation_map_type = RelationMapTypeFilter (
346371 label = "Relation map type" ,
347372 field_name = "related_from__map_type" ,
348- empty_label = "All" ,
349373 )
350374
351375 class Meta :
@@ -387,9 +411,16 @@ def __init__(self, *args, **kwargs):
387411 if status_filter := self .filters .get ("status" ):
388412 status_filter .extra .update ({"choices" : self .get_status_choices ()})
389413
414+ # Set the `BulmaDropdownWidget`` widget for defined ``dropdown_widget``.
415+ for field_name in self .dropdown_widget :
416+ self .filters [field_name ].extra ["widget" ] = BulmaDropdownWidget ()
417+
418+ license_expression_filer = self .filters ["detected_license_expression" ]
419+ license_expression_filer .extra ["widget" ] = HasValueDropdownWidget ()
420+
390421 def get_status_choices (self ):
391422 default_choices = [
392- ("_EMPTY_" , "No status" ),
423+ (EMPTY_VAR , "No status" ),
393424 ("any" , "Any status" ),
394425 ]
395426 status_values = (
@@ -446,11 +477,19 @@ class Meta:
446477 "vcs_url" ,
447478 "type" ,
448479 "declared_license_expression" ,
480+ "declared_license_expression_spdx" ,
449481 "other_license_expression" ,
482+ "other_license_expression_spdx" ,
450483 "extracted_license_statement" ,
451484 "copyright" ,
452485 ]
453486
487+ def __init__ (self , * args , ** kwargs ):
488+ super ().__init__ (* args , ** kwargs )
489+ license_expression_filer = self .filters ["declared_license_expression" ]
490+ license_expression_filer .extra ["widget" ] = HasValueDropdownWidget ()
491+ self .filters ["copyright" ].extra ["widget" ] = HasValueDropdownWidget ()
492+
454493
455494class DependencyFilterSet (FilterSetUtilsMixin , django_filters .FilterSet ):
456495 search = django_filters .CharFilter (
0 commit comments