Leo
Leo

Reputation: 5142

combining filter parameters in a FilterSet

I have created a filter for my model. How do I make it so that it filters as answered or archived? At the moment if I pass both parameters, it combines them using and - this makes sense but it is not what I am trying to achieve.

I would be happy to have just one field (ie. answered_or_archived)

from django_filters import rest_framework as filters
from conversations.models import Conversation

class ConversationFilter(filters.FilterSet):
   answered = filters.BooleanFilter(field_name="answered_at", lookup_expr="isnull", exclude=True) 
   archived = filters.BooleanFilter(field_name="is_archived")

   class Meta:
       model = Conversation
       fields = ["answered", "archived"]

Upvotes: 0

Views: 171

Answers (2)

Andrew
Andrew

Reputation: 8673

Create a custom filter field, and do the querying in there. You can do anything you would normally do, its just a queryset.

class SomeFilterField(Filter):
    field_class = forms.CharField

    def filter(self, qs, value):
        if value is None or value == other_bad_value:
            return qs

        start = some_date_value()
        end = some_date_value()
        
        # use Q() to manually construct more complicated filters
        # (a > start and a < end) or (archived=False)
        f = Q(answered_at__gte=start) & Q(answered_at__lte=end)
        # or last condition
        f = f | Q(archived=False) 

        qs = qs.filter(f)
        ... other stuff
        return qs

    def to_python(self, value):
        # custom conversion to python object, or None
        # if field_class is enough, you don't need this
        return value

Upvotes: 1

Leo
Leo

Reputation: 5142

Andrew Backer's answer was of great help and got us in the right direction.

Here is how we implemented the filter:

from django_filters import rest_framework as filters
from django.db.models import Q
from distutils import util
import datetime

from conversations.models import Conversation

class ArchivedField(filters.Filter):
    def filter(self, qs, value):          

        if value is None:
            return qs

        if bool(util.strtobool(value)):
            f = Q(is_archived=True) | Q(answered_at__isnull=False) 
        else:
            f = Q(is_archived=False) & Q(answered_at__isnull=True) 

        qs = qs.filter(f)

        return qs

    def to_python(self, value):
        return value

class ConversationFilter(filters.FilterSet):
   archived = ArchivedField(field_name="archived")

   class Meta:
       model = Conversation
       fields = ["archived"]

Upvotes: 1

Related Questions