@@ -245,15 +245,19 @@ def filter_queryset(self, queryset):
245
245
`empty_value` to any filters.
246
246
"""
247
247
for name , value in self .form .cleaned_data .items ():
248
- field_name = self .filters [name ].field_name
249
- if value == self .empty_value :
248
+ filter_field = self .filters [name ]
249
+ field_name = filter_field .field_name
250
+
251
+ if isinstance (filter_field , QuerySearchFilter ):
252
+ queryset = filter_field .filter (queryset , value )
253
+ elif value == self .empty_value :
250
254
queryset = queryset .filter (** {f"{ field_name } __in" : EMPTY_VALUES })
251
255
elif value == self .any_value :
252
256
queryset = queryset .filter (~ Q (** {f"{ field_name } __in" : EMPTY_VALUES }))
253
257
elif value == self .other_value and hasattr (queryset , "less_common" ):
254
258
return queryset .less_common (name )
255
259
else :
256
- queryset = self . filters [ name ] .filter (queryset , value )
260
+ queryset = filter_field .filter (queryset , value )
257
261
258
262
return queryset
259
263
@@ -266,7 +270,7 @@ def filter_for_lookup(cls, field, lookup_type):
266
270
return super ().filter_for_lookup (field , lookup_type )
267
271
268
272
269
- def parse_query_string_to_lookups (query_string , default_lookup_expr , default_field ):
273
+ def parse_query_string_to_lookups (query_string , default_lookup_expr , search_fields ):
270
274
"""Parse a query string and convert it into queryset lookups using Q objects."""
271
275
lookups = Q ()
272
276
terms = shlex .split (query_string )
@@ -295,11 +299,14 @@ def parse_query_string_to_lookups(query_string, default_lookup_expr, default_fie
295
299
field_name = field_name [1 :]
296
300
negated = True
297
301
302
+ lookups &= Q (
303
+ ** {f"{ field_name } __{ lookup_expr } " : search_value }, _negated = negated
304
+ )
305
+
298
306
else :
299
307
search_value = term
300
- field_name = default_field
301
-
302
- lookups &= Q (** {f"{ field_name } __{ lookup_expr } " : search_value }, _negated = negated )
308
+ for field_name in search_fields :
309
+ lookups |= Q (** {f"{ field_name } __{ lookup_expr } " : search_value })
303
310
304
311
return lookups
305
312
@@ -323,18 +330,22 @@ class QuerySearchFilter(django_filters.CharFilter):
323
330
324
331
field_class = QuerySearchField
325
332
333
+ def __init__ (self , search_fields = None , lookup_expr = "icontains" , * args , ** kwargs ):
334
+ super ().__init__ (lookup_expr = lookup_expr , * args , ** kwargs )
335
+ self .search_fields = search_fields or []
336
+
326
337
def filter (self , qs , value ):
327
338
if not value :
328
339
return qs
329
340
330
341
lookups = parse_query_string_to_lookups (
331
342
query_string = value ,
332
343
default_lookup_expr = self .lookup_expr ,
333
- default_field = self .field_name ,
344
+ search_fields = self .search_fields ,
334
345
)
335
346
336
347
try :
337
- return qs .filter (lookups )
348
+ return qs .filter (lookups ). distinct ()
338
349
except FieldError :
339
350
return qs .none ()
340
351
@@ -347,7 +358,7 @@ class ProjectFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
347
358
]
348
359
349
360
search = QuerySearchFilter (
350
- label = "Search" , field_name = "name" , lookup_expr = "icontains"
361
+ label = "Search" , search_fields = [ "name" , "labels__name" ] , lookup_expr = "icontains"
351
362
)
352
363
sort = django_filters .OrderingFilter (
353
364
label = "Sort" ,
@@ -508,7 +519,7 @@ class ResourceFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
508
519
509
520
search = QuerySearchFilter (
510
521
label = "Search" ,
511
- field_name = "path" ,
522
+ search_fields = [ "path" ] ,
512
523
lookup_expr = "icontains" ,
513
524
)
514
525
sort = django_filters .OrderingFilter (
@@ -615,15 +626,7 @@ def filter(self, qs, value):
615
626
if value .startswith ("pkg:" ):
616
627
return qs .for_package_url (value )
617
628
618
- if ":" in value :
619
- return super ().filter (qs , value )
620
-
621
- search_fields = ["type" , "namespace" , "name" , "version" ]
622
- lookups = Q ()
623
- for field_names in search_fields :
624
- lookups |= Q (** {f"{ field_names } __{ self .lookup_expr } " : value })
625
-
626
- return qs .filter (lookups )
629
+ return super ().filter (qs , value )
627
630
628
631
629
632
class GroupOrderingFilter (django_filters .OrderingFilter ):
@@ -662,7 +665,9 @@ class PackageFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
662
665
]
663
666
664
667
search = DiscoveredPackageSearchFilter (
665
- label = "Search" , field_name = "name" , lookup_expr = "icontains"
668
+ label = "Search" ,
669
+ search_fields = ["type" , "namespace" , "name" , "version" ],
670
+ lookup_expr = "icontains" ,
666
671
)
667
672
sort = GroupOrderingFilter (
668
673
label = "Sort" ,
@@ -746,7 +751,7 @@ class DependencyFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
746
751
]
747
752
748
753
search = QuerySearchFilter (
749
- label = "Search" , field_name = "name" , lookup_expr = "icontains"
754
+ label = "Search" , search_fields = [ "name" ] , lookup_expr = "icontains"
750
755
)
751
756
sort = GroupOrderingFilter (
752
757
label = "Sort" ,
@@ -803,7 +808,7 @@ class Meta:
803
808
804
809
class ProjectMessageFilterSet (FilterSetUtilsMixin , django_filters .FilterSet ):
805
810
search = QuerySearchFilter (
806
- label = "Search" , field_name = "description" , lookup_expr = "icontains"
811
+ label = "Search" , search_fields = [ "description" ] , lookup_expr = "icontains"
807
812
)
808
813
sort = django_filters .OrderingFilter (
809
814
label = "Sort" ,
@@ -855,7 +860,7 @@ class RelationFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
855
860
856
861
search = QuerySearchFilter (
857
862
label = "Search" ,
858
- field_name = "to_resource__path" ,
863
+ search_fields = [ "to_resource__path" ] ,
859
864
lookup_expr = "icontains" ,
860
865
)
861
866
sort = django_filters .OrderingFilter (
0 commit comments