Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django-rest-framework- Filtering using 'or' on multiple values from one url parameter

I have a tagging system in place for a model that my API exposes. The models look something like this:

class TaggableModel(models.Model):
    name = models.CharField(max_length=255)
    tags = models.ManyToManyField(Tag, related_name="taggable_models")

class Tag(models.Model):
    tag = models.CharField(max_length=32)

I've then set up a serializer and view that look like:

class TaggableModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = TaggableModel
        fields = ('id', 'name', 'tags',)
        read_only_fields = ('id',)

class TaggableModelViewSet(viewsets.ModelViewSet):
    queryset = TaggableModel.objects.all()
    serializer_class = TaggableModelSerializer
    permission_classes = (AllowAny,)
    filter_backend = [DjangoFilterBackend]
    filterset_fields = ['tags']

If I want to grab all TaggableModels that have tag ids 1, 2, or 3, I can do so via:

https://my-api-domain/api/taggable-models?tags=1&tags=2&tags=3

Is there a way to split on a delimiter, so I can pass it all as one parameter? e.g.:

https://my-api-domain/api/taggable-models?tags=1,2,3

It looks like I can write my own custom DjangoFilterBackend filters, but I am a bit unsure as to where to start. Or perhaps there is an easier way to accomplish this?

like image 732
MarkD Avatar asked Sep 09 '19 01:09

MarkD


People also ask

What is 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. We can also create our own FilterSet class with customized settings.

How do you use DjangoFilterBackend?

To use DjangoFilterBackend , first install django-filter . Then add 'django_filters' to Django's INSTALLED_APPS : INSTALLED_APPS = [ ... 'django_filters', ... ] Or add the filter backend to an individual View or ViewSet.

What is mixin in Django REST framework?

mixins are classes that generally inherit from object (unless you are django core developer) mixins are narrow in scope as in they have single responsibility. They do one thing and do it really well. mixins provide plug-in functionality. although mixins work through inheritence, they DONT create a subtyping relation.


2 Answers

Sure you can do this by having custom filterset class with specific field 'widget' (that's how it is called in django-filters)

Here's a sample you can try:

# filters.py

from django_filters.rest_framework import FilterSet, filters
from django_filters.widgets import CSVWidget

from .your_models import Tag, TaggableModel

class TaggableModelFilterSet(FilterSet):
    tags = filters.ModelMultipleChoiceFilter(
        queryset=Tag.objects.all(), widget=CSVWidget,
        help_text=_("A list of ids, comma separated, identifying tags"),
        method='filter_tags'
    )

    class Meta:
        model = TaggableModel
        fields = ['tags']

    def filter_tags(self, queryset, name, value):
        if value:
            queryset = queryset.filter(tags__in=value)
        return queryset
# views.py

class TaggableModelViewSet(viewsets.ModelViewSet):
    queryset = TaggableModel.objects.all()
    serializer_class = TaggableModelSerializer
    permission_classes = (AllowAny,)
    filter_backends = [DjangoFilterBackend]
    filter_class = TaggableModelFilterSet
like image 147
Gabriel Muj Avatar answered Oct 17 '22 16:10

Gabriel Muj


There is an even simpler way to achieve this using the django-filter package. Deep within the django-filter documentation, it mentions that you can use "a dictionary of field names mapped to a list of lookups".

Your code would be updated like so:

# views.py

from django_filters.rest_framework import DjangoFilterBackend

class TaggableModelViewSet(viewsets.ModelViewSet):
    queryset = TaggableModel.objects.all()
    serializer_class = TaggableModelSerializer
    permission_classes = (AllowAny,)
    filter_backend = [DjangoFilterBackend]
    filterset_fields = {
        'tags': ["in", "exact"] # note the 'in' field
    }

Now in the URL you would add __in to the filter before supplying your list of parameters and it would work as you expect:

https://my-api-domain/api/taggable-models?tags__in=1,2,3

The django-filter documentation on what lookup filters are available is quite poor, but the in lookup filter is mentioned in the Django documentation itself.

like image 1
Josh Correia Avatar answered Oct 17 '22 16:10

Josh Correia