Using django-filters, I see various solutions for how to submit multiple arguments of the same type in a single query string, for example for multiple IDs. They all suggest using a separate field that contains a comma-separated list of values, e.g.:
http://example.com/api/cities?ids=1,2,3
Is there a general solution for using a single parameter but submitted one or more times? E.g.:
http://example.com/api/cities?id=1&id=2&id=3
I tried using MultipleChoiceFilter, but it expects actual choices to be defined whereas I want to pass arbitrary IDs (some of which may not even exist in the DB).
Here is a reusable solution using a custom Filter and a custom Field.
The custom Field reuses Django's MultipleChoiceField but replaces the validation functions.
Instead, it validates using another Field class that we pass to the constructor.
from django.forms.fields import MultipleChoiceField
class MultipleValueField(MultipleChoiceField):
    def __init__(self, *args, field_class, **kwargs):
        self.inner_field = field_class()
        super().__init__(*args, **kwargs)
    def valid_value(self, value):
        return self.inner_field.validate(value)
    def clean(self, values):
        return values and [self.inner_field.clean(value) for value in values]
The custom Filter uses MultipleValueField and forwards the field_class argument.
It also sets the default value of lookup_expr to in.
from django_filters.filters import Filter
class MultipleValueFilter(Filter):
    field_class = MultipleValueField
    def __init__(self, *args, field_class, **kwargs):
        kwargs.setdefault('lookup_expr', 'in')
        super().__init__(*args, field_class=field_class, **kwargs)
To use this filter, simply create a MultipleValueFilter with the appropriate field_class. For example, to filter City by id, we can use a IntegerField, like so:
from django.forms.fields import IntegerField
class CityFilterSet(FilterSet):
    id = MultipleValueFilter(field_class=IntegerField)
    name = filters.CharFilter(lookup_expr='icontains')
    class Meta:
        model = City
        fields = ['name']
                        Solved using a custom filter, inspired by Jerin's answer:
class ListFilter(Filter):
    def filter(self, queryset, value):
        try:
            request = self.parent.request
        except AttributeError:
            return None
        values = request.GET.getlist(self.name)
        values = {int(item) for item in values if item.isdigit()}
        return super(ListFilter, self).filter(queryset, Lookup(values, 'in'))
If the values were to be non-digit, e.g. color=blue&color=red then the isdigit() validation is of course not necessary.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With