19

I am using Django REST framework with the django-filter package, but my question is mostly about django-filter. I don't understand how to use filters with "__in" lookup.

For example I have the following model:

class Book(models.Model):
   name = models.CharField(max_length=100)

class BookView(viewsets.ReadOnlyModelViewSet):
   serializer_class = BookSerializer()
   model = Book
   filter_fields = ('id', 'name')

And I can't use URLs like /v1/books/?id__in=1,2,3 to find books with id 1, 2 or 3.

How do you use Django-filter's '__in' lookup?

2
  • Read this guide: django-rest-framework.org/api-guide/filtering.html Commented Aug 2, 2013 at 12:22
  • 1
    I already read it. I understand, that can reimplement get_queryset method and filter by request.GET params, but i want to try to use django-filter declarative approach first. Commented Aug 2, 2013 at 12:28

9 Answers 9

16

There's a simple solution with django_filter now:

class BookView(viewsets.ReadOnlyModelViewSet):
   serializer_class = BookSerializer()
   model = Book
   filter_fields = {
      'id': ['exact', 'in'],
      'name': ['exact']
   }

And then you can use it in your query string exactly as you wanted: ?id__in=1,2,3.

Sign up to request clarification or add additional context in comments.

2 Comments

Embarrassing how long it took me to find this. Thanks!
this answer should be accepted and on top
10

The django-filter provides BaseInFilter to be used in conjunction with other filter classes, such as NumberFilter, CharFilter. This class only validates that the incoming request is comma-separated.So if you're using the Web Browsable API, you can send request as /book/?id__in=1%2C3 where %2C is comma.

filters.py

import django_filters


class NumberInFilter(django_filters.BaseInFilter, django_filters.NumberFilter):
    pass


class BookFilter(django_filters.FilterSet):
    id__in = NumberInFilter(field_name="id", lookup_expr="in")

views.py

from rest_framework import viewsets
from django_filters import rest_framework as filters

from book.filters import BookFilter
from book.models import Book
from book.serializers import BookSerializer


class BookViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Book.objects.all()
    filter_backends = (filters.DjangoFilterBackend, )
    filterset_class = BookFilter
    serializer_class = BookSerializer

1 Comment

Thanks for your answer. How can I do the same with AND not OR between query parameters?
5

The question is discussed in this issue: https://github.com/alex/django-filter/issues/137#issuecomment-77697870

The suggested solution would be to create a custom filter as follows:

from django_filters import Filter
from django_filters.fields import Lookup

from .models import Product

class ListFilter(Filter):
    def filter(self, qs, value):
        value_list = value.split(u',')
        return super(ListFilter, self).filter(qs, Lookup(value_list, 'in'))

class ProductFilterSet(django_filters.FilterSet):
    id = ListFilter(name='id')

    class Meta:
        model = Product
        fields = ['id']

And the you can write the following:

products/?id=7,8,9

Comments

3

The documentation for django-filter is sparse. You could try creating a custom filter and specifying the lookup type. It's rather convoluted:

class BookFilter(django_filters.FilterSet):
    id = django_filters.NumberFilter(name="id", lookup_type="in")

    class Meta:
        model = Book
        fields = ['id']

And then modify your view to use the filter class:

class BookView(viewsets.ReadOnlyModelViewSet):
    serializer_class = BookSerializer()
    model = Book
    filter_fields = ('id', 'name')
    filter_class = BookFilter

You'll then be able to lookup books via their ids (note "__in" not used):

/v1/books/?id=1,2,3
/v1/books/?id=1

Comments

3

I have just answered the same question in DjangoFilterBackend with multiple ids

For your case, this should work without having to write any logic.

from django_filters import rest_framework as filters

class NumberInFilter(filters.BaseInFilter, filters.NumberFilter):
    pass


class BookFilter(filters.FilterSet):
    id_in = NumberInFilter(field_name='id', lookup_expr='in')

    class Meta:
        model = Book
        fields = ['id_in', 'name']

class BookView(viewsets.ReadOnlyModelViewSet):
   serializer_class = BookSerializer()
   model = Book
   filter_class = BookFilter

Now you should be able to filter by a list of ids in your get parameters, such as /v1/books/?id__in=1,2,3

Comments

2

Customize PKsField and PKsFilter for your id field(AutoField), and then the query params will work: '/v1/books/?id__in=1,2,3'

from django.forms import Field
from django_filters.filters import Filter
from django.db.models import AutoField


class PKsField(Field):

    def clean(self, value): # convert '1,2,3' to {1, 2, 3}
        return set(int(v) for v in value.split(',') if v.isnumeric()) if value else ()


class PKsFilter(Filter):
    field_class = PKsField


class BookFilter(FilterSet):
    # ids = PKsFilter(name='id', lookup_type="in") # another way, query string: ?ids=1,2,3

    filter_overrides = {
        AutoField: {
            'filter_class': PKsFilter, # override default NumberFilter by the PKsFilter
            'extra': lambda f: {
                'lookup_type': 'in',
            }
        }
    }

    class Meta:
        model = Book
        fields = {
            'id': ('in',),
        }


from rest_framework import viewsets


class BookView(viewsets.ModelViewSet):
    queryset = ...
    serializer_class = ...
    filter_class = BookFilter

Hope that can help. Thx.

Comments

0

Not sure if this was ever answered: try: id=[1, 2, 3] for numbers name=["name1", "name2"] for strings

Comments

-1

Instead /v1/books/?id__in=1,2,3 you can use /v1/books/?id=1&id=2&id=3

Comments

-2

The django admin site only create urls under the template app_name/model_name/primary_key to edit an instance of the model. It doesn't provide the __in filtering through URLS.

You have to create a custom view:

def myview(request):
    # you can get parameters from request.GET or request.POST
    selected_books = None
    if request.method = "POST":
        ids = request.POST["ids"].split("_")
        selected_books = Book.objects.filter(id__in=ids)

    return render_to_response('mytemplate.html', { 'selected_books': selected_books }, context_instance = RequestContext(request) )

And in mytemplate.html:

{% for entry in selected_books %}
  ... {{ entry }} ...
{% endfor %}

In urls.py add an entry to this view.

Then try a GET request to your URL with the parameters ?ids=1_2_3

1 Comment

Thanks, but my question is about django-filter application.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.