Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST Framework ManyToMany filter multiple values

I have two models, one defining users, the other defining labels on these users. I am using Django REST Framework to create an API. I would like to be able to query users containing at least the label ids 1 and 2.

For instance if the user's labels are: [(1,2), (1,2,3), (2,3), (1,3)] I want the query to return [(1,2), (1,2,3)].

So far, I've managed to query users with a given label (let's say id=1) by doing: /api/users/?labels=1, but I am unable to query users with labels 1 and 2. I've tried /api/users/?labels=1,2 or /api/users/?labels=1&labels=2 but it returns some invalid users, i.e. users without labels 1 or 2...

Github test repo:

https://github.com/TheDimLebowski/drf-m2m-filter

Code:

models.py

class Label(models.Model):
    name = models.CharField(max_length = 60)

class User(models.Model):
    labels = models.ManyToManyField(Label)

filters.py

class UserFilter(django_filters.FilterSet):
    labels = django_filters.filters.BaseInFilter(
        name='labels',
        lookup_type='in',
    )

    class Meta:
        model = User
        fields = ('labels',)

serializers.py

class LabelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Label
        fields = ('id','name')

class UserSerializer(serializers.ModelSerializer):
    labels = LabelSerializer(many = True)
    class Meta:
        model = User
        fields = ('labels',)

views.py

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filter_class = UserFilter
    filter_fields = ('labels',)
like image 516
TheDimLebowski Avatar asked Sep 07 '16 11:09

TheDimLebowski


People also ask

How do I filter Queryset in Django REST framework?

The simplest way to filter the queryset of any view that subclasses GenericAPIView is to override the . get_queryset() method. Overriding this method allows you to customize the queryset returned by the view in a number of different ways.

How do you use DjangoFilterBackend?

The DjangoFilterBackend class is used to filter the queryset based on a specified set of fields. This backend class automatically creates a FilterSet (django_filters. rest_framework. FilterSet) class for the given fields.

What is Q in Django REST framework?

Q object encapsulates a SQL expression in a Python object that can be used in database-related operations. Using Q objects we can make complex queries with less and simple code. For example, this Q object filters whether the question starts wiht 'what': from django. db.


2 Answers

You can chain filters. For example, if you need all users which labels contain both 1 and 2 values, you can write a query like so:

User.objects.filter(labels=1).filter(labels=2)

django-filters does not support queries like this by default so you need a custom filter.

class M2MFilter(django_filters.Filter):

    def filter(self, qs, value):
        if not value:
            return qs
    
        values = value.split(',')
        for v in values:
            qs = qs.filter(labels=v)
        return qs


class UserFilter(django_filters.FilterSet):
    labels = M2MFilter(name='labels')

    class Meta:
        model = User
        fields = ('labels',)

Now you can write labels id's comma-separated and get exactly what you need

/api/users/?labels=1,2

Here is good answer about m2m queries

like image 177
Ivan Semochkin Avatar answered Nov 04 '22 16:11

Ivan Semochkin


I have the same question and I found something like this:

from django.db.models import Q
from rest_framework import viewsets

class YourViewSet(viewsets.ModelViewSet)

    def get_queryset(self):
        # get lable infos here
        lables_info = self.request.query_params.get('lable')
        lables = lables_info.split(',')
        lable1, lable2 = lables[0], lables[1]

        return models.objects.fliter(
            Q(lable=lable1) | Q(lable=lable2)
        )
.......

Django==2.2.4

djangorestframework==3.10.2

django-filter==2.2.0

like image 42
Deron Lee Avatar answered Nov 04 '22 14:11

Deron Lee