Repository: saleor/saleor

Issue:
Allow filtering by lists of enums
In GraphQL API we can filter data by enum fields:
```
orders(filter: {status: READY_TO_FULFILL})
```

It would be relatively easy to make the filtering more flexible by changing enum filters to lists: 
```
orders(filter: {status: [READY_TO_CAPTURE, READY_TO_FULFILL]})
```



Discussion:

Resolving patch:
diff --git a/saleor/graphql/core/filters.py b/saleor/graphql/core/filters.py
--- a/saleor/graphql/core/filters.py
+++ b/saleor/graphql/core/filters.py
@@ -5,14 +5,13 @@
 
 
 class DefaultMultipleChoiceField(MultipleChoiceField):
-    default_error_messages = {
-        "invalid_choice": _("One of the specified IDs was invalid (%(value)s)."),
-        "invalid_list": _("Enter a list of values."),
-    }
+    default_error_messages = {"invalid_list": _("Enter a list of values.")}
 
     def to_python(self, value):
         if not value:
             return []
+        if not isinstance(value, list):
+            value = [value]
         return value
 
     def validate(self, value):
diff --git a/saleor/graphql/discount/filters.py b/saleor/graphql/discount/filters.py
--- a/saleor/graphql/discount/filters.py
+++ b/saleor/graphql/discount/filters.py
@@ -1,24 +1,31 @@
 from datetime import date
+from typing import List
 
 import django_filters
+from django.db.models import Q
 
 from ...discount import DiscountValueType
-from ...discount.models import Sale, Voucher
-from ..core.filters import EnumFilter, ObjectTypeFilter
+from ...discount.models import Sale, Voucher, VoucherQueryset
+from ..core.filters import ListObjectTypeFilter, ObjectTypeFilter
 from ..core.types.common import DateRangeInput, IntRangeInput
 from ..utils import filter_by_query_param
 from .enums import DiscountStatusEnum, DiscountValueTypeEnum, VoucherDiscountType
 
 
-def filter_status(qs, _, value):
+def filter_status(
+    qs: VoucherQueryset, _, value: List[DiscountStatusEnum]
+) -> VoucherQueryset:
+    if not value:
+        return qs
+    query_objects = qs.none()
     today = date.today()
-    if value == DiscountStatusEnum.ACTIVE:
-        return qs.active(today)
-    if value == DiscountStatusEnum.EXPIRED:
-        return qs.expired(today)
-    if value == DiscountStatusEnum.SCHEDULED:
-        return qs.filter(start_date__gt=today)
-    return qs
+    if DiscountStatusEnum.ACTIVE in value:
+        query_objects |= qs.active(today)
+    if DiscountStatusEnum.EXPIRED in value:
+        query_objects |= qs.expired(today)
+    if DiscountStatusEnum.SCHEDULED in value:
+        query_objects |= qs.filter(start_date__gt=today)
+    return qs & query_objects
 
 
 def filter_times_used(qs, _, value):
@@ -31,11 +38,18 @@ def filter_times_used(qs, _, value):
     return qs
 
 
-def filter_discount_type(qs, _, value):
-    if value in [VoucherDiscountType.PERCENTAGE, VoucherDiscountType.FIXED]:
-        qs = qs.filter(discount_value_type=value)
-    elif value == VoucherDiscountType.SHIPPING:
-        qs = qs.filter(type=value)
+def filter_discount_type(
+    qs: VoucherQueryset, _, value: List[VoucherDiscountType]
+) -> VoucherQueryset:
+    if value:
+        query = Q()
+        if VoucherDiscountType.FIXED in value:
+            query |= Q(discount_value_type=VoucherDiscountType.FIXED.value)
+        if VoucherDiscountType.PERCENTAGE in value:
+            query |= Q(discount_value_type=VoucherDiscountType.PERCENTAGE.value)
+        if VoucherDiscountType.SHIPPING in value:
+            query |= Q(type=VoucherDiscountType.SHIPPING)
+        qs = qs.filter(query).distinct()
     return qs
 
 
@@ -70,10 +84,10 @@ def filter_voucher_search(qs, _, value):
 
 
 class VoucherFilter(django_filters.FilterSet):
-    status = EnumFilter(input_class=DiscountStatusEnum, method=filter_status)
+    status = ListObjectTypeFilter(input_class=DiscountStatusEnum, method=filter_status)
     times_used = ObjectTypeFilter(input_class=IntRangeInput, method=filter_times_used)
 
-    discount_type = EnumFilter(
+    discount_type = ListObjectTypeFilter(
         input_class=VoucherDiscountType, method=filter_discount_type
     )
     started = ObjectTypeFilter(input_class=DateRangeInput, method=filter_started)
@@ -85,7 +99,7 @@ class Meta:
 
 
 class SaleFilter(django_filters.FilterSet):
-    status = ObjectTypeFilter(input_class=DiscountStatusEnum, method=filter_status)
+    status = ListObjectTypeFilter(input_class=DiscountStatusEnum, method=filter_status)
     sale_type = ObjectTypeFilter(
         input_class=DiscountValueTypeEnum, method=filter_sale_type
     )
diff --git a/saleor/graphql/order/filters.py b/saleor/graphql/order/filters.py
--- a/saleor/graphql/order/filters.py
+++ b/saleor/graphql/order/filters.py
@@ -1,7 +1,8 @@
 import django_filters
+from django.db.models import Sum
 
 from ...order.models import Order
-from ..core.filters import EnumFilter, ObjectTypeFilter
+from ..core.filters import ListObjectTypeFilter, ObjectTypeFilter
 from ..core.types.common import DateRangeInput
 from ..payment.enums import PaymentChargeStatusEnum
 from ..utils import filter_by_query_param
@@ -9,17 +10,29 @@
 
 
 def filter_payment_status(qs, _, value):
-    qs = qs.filter(payments__is_active=True, payments__charge_status=value)
+    if value:
+        qs = qs.filter(payments__is_active=True, payments__charge_status__in=value)
     return qs
 
 
 def filter_status(qs, _, value):
-    if value not in [
-        OrderStatusFilter.READY_TO_CAPTURE,
-        OrderStatusFilter.READY_TO_FULFILL,
-    ]:
-        qs = qs.filter(status=value)
-    return qs
+    query_objects = qs.none()
+
+    if value:
+        query_objects |= qs.filter(status__in=value)
+
+    if OrderStatusFilter.READY_TO_FULFILL in value:
+        # to use & between queries both of them need to have applied the same
+        # annotate
+        qs = qs.annotate(amount_paid=Sum("payments__captured_amount"))
+        query_objects |= qs.ready_to_fulfill()
+
+    if OrderStatusFilter.READY_TO_CAPTURE in value:
+        qs = qs.distinct()
+        query_objects = query_objects.distinct()
+        query_objects |= qs.ready_to_capture()
+
+    return qs & query_objects
 
 
 def filter_customer(qs, _, value):
@@ -52,10 +65,10 @@ class Meta:
 
 
 class OrderFilter(DraftOrderFilter):
-    payment_status = EnumFilter(
+    payment_status = ListObjectTypeFilter(
         input_class=PaymentChargeStatusEnum, method=filter_payment_status
     )
-    status = ObjectTypeFilter(input_class=OrderStatusFilter, method=filter_status)
+    status = ListObjectTypeFilter(input_class=OrderStatusFilter, method=filter_status)
     customer = django_filters.CharFilter(method=filter_customer)
     created = ObjectTypeFilter(input_class=DateRangeInput, method=filter_created_range)
 


Test patch: