carltongibson / django-filter

A generic system for filtering Django QuerySets based on user selections

Home Page:https://django-filter.readthedocs.io/en/main/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Filter repeated key parameters as NumberField

Enorio opened this issue · comments

Given the following Model and Filter:

class Bar(models.Model):
    foo = models.ForeignKey(...)

class BarFilter(FilterSet):
    class Meta:
        model = Bar
        fields = ['foo']
        filter_overrides = {
            models.ForeignKey: {
                'filter_class': filters.NumberFilter
            }
        }

I need to filter Bar given multiple Foo foreign key ids.
For example GET .../bar/?foo=1&foo=2&foo=3

From what I tested, the endpoint will only filter foo=3, I'm guessing always the last value.
I've done some debug in the code, and there is a step somewhere that converts the list received in only one value.

I've tried adding a 'lookup_expr': 'in' as an extra, but raises an exception saying 'decimal.Decimal' object is not iterable
I also used the ModelMultipleChoiceFilter, but I don't want to return an error saying that some id doesn't exist.

Basically, what I want is given the endpoint, I can filter Foreign Keys from their id, and if some value doesn't exist, ignores it. For example:

foo_a = Foo(id=1)
foo_b = Foo(id=2)
foo_c = Foo(id=3)

bar_a = Bar(foo=foo_a)
bar_b = Bar(foo=foo_b)
bar_c = Bar(foo=foo_c)
GET .../bar/ = [bar_a, bar_b, bar_c]

GET .../bar/?foo=1&foo=2 = [bar_a, bar_b]

GET .../bar/?foo=4 = []

I'm using django-filter 21.1 and django-rest-framework, if it helps.

I also used the ModelMultipleChoiceFilter, but I don't want to return an error saying that some id doesn't exist.

OK, so then you'll want to define a widget that returns a list of items from the request.GET QueryDict:

class ManyWiget(Widget):
    def value_from_datadict(self, data, files, name):
        return data.getlist(name)

Then you'll want a Form field that uses that widget, again expecting a list.

class ManyIntField(Field):
    widget = ManyWidget 

    def to_python(self, value):
        if not value:
            return []
        elif not isinstance(value, (list, tuple)):
            raise ValidationError(
                self.error_messages["invalid_list"], code="invalid_list"
            )
        return [int(val) for val in value]

You'll need to think about validation there; you don't want to accept just any data.

Then you'll need a filter to use the field:

class ManyIntFilter(Filter):
    field = ManyIntField
    lookup_expr = "in"

But with that you'll be able to filter on many items, without erroring just because an ID doesn't exist.