How to write a GraphQL query that will use a range filter on an integer field using `django-filters`?

2.6k views Asked by At

I am using graphene-python, django-filters and relay in my GraphQL API. Let's imagine I have a type FrameType which has an integer field time_offset and I would like to be able to use a range on it - ask only for frames which have the time_offset within the given range. I prepared my schema.py according to the graphene-python docs with a custom FilterSet:

import django_filters
from graphene import ObjectType, relay
from graphene_django import DjangoObjectType, filter
from my_app.blabla import models



class FrameFilter(django_filters.FilterSet):
    time_offset = django_filters.RangeFilter()

    class Meta:
        model = models.Frame
        fields = ("time_offset",)


class FrameType(DjangoObjectType):
    class Meta:
        model = models.Frame
        filterset_class = FrameFilter
        interfaces = (relay.Node,)


class Query(ObjectType):
    frames = filter.DjangoFilterConnectionField(FrameType)

    class Meta:
        abstract = True

However, I have no idea how to query the timeOffset field now. I found no examples online for the django_filters.RangeFilter field. This is a query I tried:

query Frame {
  frames(first: 20, timeOffset: "{\"gt\":\"4350\", \"lt\":\"5000\"}") {
    edges {
      node {
        timeOffset
    }
  }
}

... also with these alternatives:

timeOffset: "{\"gt\":4350, \"lt\":5000}"
timeOffset: "{\"start\":\"4350\", \"end\":\"5000\"}"
timeOffset: "{\"min\":\"4350\", \"max\": \"4500\"}"
timeOffset: "[\"4350\", \"5000\"]"
timeOffset: "[4350, 5000]"
timeOffset: "[4350]"
timeOffset: "4350,5000"

These queries don't raise any error, but they don't filter either (all results are returned). I am lost, I'm not sure if I still haven't found the proper syntax, or maybe there's some mistake in my backend code. How should I use and query the django_filters.RangeFilter on a field?

4

There are 4 answers

0
JPG On BEST ANSWER

Sadly, this isn't possible. But, there is a workaround for it

Adjust your filter class as

def custom_range_filter_method(queryset, field_name, value):
    if value:
        queryset = queryset.filter(**{f'{field_name}__range': value.split(',')})
    return queryset


class FrameFilter(django_filters.FilterSet):
    time_offset = filters.Filter(method=custom_range_filter_method)

    class Meta:
        model = models.Frame
        fields = ("time_offset",)

Now query the schema with

query Frame {
  frames(first: 20, timeOffset: "4350,5000") {
    edges {
      node {
        timeOffset
    }
  }
}

Reference

0
Scorpionk On

You can handle the range option at Django's queryset level without disturbing the existing relay query.

In your case,

  1. Pass start_time_offset and end_time_offset arguments to DjangoConnectionField
  2. Override resolve_frames
  3. filter on django queryset if start_time_offset or end_time_offset is provided by user else return objects.all()
class Query(ObjectType):
    frames = filter.DjangoFilterConnectionField(FrameType, start_time_offset=graphene.Int(), end_time_offset=graphene.Int())

    def resolve_frames(self, info, start_time_offset=None, end_time_offset=None, **kwargs):
        if start_time_offset and end_time_offset:
            return Frame.objects.filter(time_offset__range=(start_time_offset, end_time_offset))
        elif start_time_offset:
            return Frame.objects.filter(time_offset__gte=start_time_offset)
        elif end_time_offset:
            return Frame.objects.filter(time_offset__lte=end_time_offset)
        return Frame.objects.all()

Now you can query on it with your regular filters provided by relay:

query Frame {
  frames(last: 5, startTimeOffset: 4350, endTimeOffset:5000) {
    edges {
      node {
        timeOffset
    }
  }
}
0
r.aj On

It's a bit old but since it might help others, you can check this thread, it used django filterset for DateRangeFiled and I think you can use a similar approach for integers using filterset RangeFilter. Also check this for more info about filterset in graphene.

0
shlomo gordon On

All you need to do is update your FrameType class as follows:

class FrameType(DjangoObjectType):
    class Meta:
        model = models.Frame
        filterset_fields = {
            'time_offset': ['range']
        }
        interfaces = (relay.Node,)

You do not need a custom filterset for this. Then you can query as follows:

query Frame {
    frames(first: 20, timeOffset_Range: ["4350", "5000"]) {
    edges {
      node {
        timeOffset
    }
  }
}