Django-filter: IN lookup

Created on 25 Mar 2016  路  9Comments  路  Source: carltongibson/django-filter

Hi,

I'm using django-filter with django-rest-framework, this one doesn't work, it just returns nothing:

import django_filters
from rest_framework import filters

from ..models import MyModel


class MyModelFilter(filters.FilterSet):
    my_field = django_filters.CharFilter(lookup_expr='in')

    class Meta:
        model = MyModel

But this one does and works as expected:

from django_filters import filters as df_filters
from rest_framework import filters

from ..models import MyModel


class CharInFilter(df_filters.BaseInFilter, df_filters.CharFilter):
    pass


class MyModelFilter(filters.FilterSet):
    my_field = CharInFilter()

    class Meta:
        model = MyModel

Is this a normal and expected behaviour or am I doing something wrong here ? Thanks.

Most helpful comment

Ah, you should be filtering with ?my_field__in=Option1,Option2. This makes more sense when you have multiple lookups. eg,

class ProductFilter(filterset.Filter):
    class Meta:
        model = Product
        fields = {'price': ['exact', 'in', 'lt', 'gt'], }

Would generate price, price__in, price__lt, and price__gt filters.

All 9 comments

Hi @maxtepkeev. Yes - this is the expected behavior. Filters are not 'expression aware' and will not alter behavior according to their lookup_expr.

You can either manually create the filter class as you've done in the second example, or you can automatically generate filters with the dictionary style syntax for Meta.fields. Internally, it does the same thing as in your example.

eg,

class MyModelFilter(Filters.FilterSet):
    class Meta:
        model = MyModel
        fields = {
            'my_field': ['exact', 'in', ...],
        }

Hi @rpkilby

Thanks for reply.

IMHO that is not very obvious from the documentation, so maybe you can add some more information about that in the docs.

Also, the second example with Meta.fields doesn't work for me for some reason.

@maxtepkeev Any PRs on the docs are very welcome!

I think the underlying issue is the CharFilter is only expecting (receiving?) a single value, not a list; that comes from how the widget maps data from the GET QueryDict 鈥斅燗ll of _that_ is Django Forms internals, which isn't that clear to many people. We probably need to have some _background reading_ to explain the mechanics...

@carltongibson I'll try to work on this later tonight. Could rework this into a section on common problems and/or theory.

@maxtepkeev Here are the docs on mixing the BaseInFilter with other filter types, however it's not exactly obvious on how to get there. eg, if you were to search the docs on how to correctly use CharFilter with lookup_expr='in', you probably wouldn't find that section.
Are you still having problems with the Meta.fields syntax? Maybe post model/filter code and the traceback?

@rpkilby Yes, it was hard to find, but I've read this section and after that I implemented my current working solution, i.e.:

from django_filters import filters as df_filters
from rest_framework import filters

from ..models import MyModel


class CharInFilter(df_filters.BaseInFilter, df_filters.CharFilter):
    pass


class MyModelFilter(filters.FilterSet):
    my_field = CharInFilter()

    class Meta:
        model = MyModel

Meta.fields still doesn't work for me, it doesn't produce any tracebacks, errors or anything else, it just returns empty result set, this is how I'm trying to use it:

from rest_framework import filters

from ..models import MyModel


class MyModelFilter(filters.FilterSet):
    class Meta:
        model = MyModel
        fields = {
            'my_field': ['in'],
        }

As I said earlier, I'm using django-filter with django-rest-framework, I'm using ?my_field=Option1,Option2 syntax in URL which correctly works with my current solution but doesn't work with Meta.fields.

Ah, you should be filtering with ?my_field__in=Option1,Option2. This makes more sense when you have multiple lookups. eg,

class ProductFilter(filterset.Filter):
    class Meta:
        model = Product
        fields = {'price': ['exact', 'in', 'lt', 'gt'], }

Would generate price, price__in, price__lt, and price__gt filters.

Of course, my bad, it works now.

But if I don't like the __in part, the only option is to use the custom class like I'm doing at the moment, right ?

Yep, and that's fine if you only have the one filter for that field. If you have multiple filters for a field though, you'll need to use some naming strategy to differentiate between them. eg, price__in, price__lt, price__gt.

Yes, that makes sense. Thanks. I have no more questions.

Was this page helpful?
0 / 5 - 0 ratings