Graphene-django: DateFromToRangeFilter

Created on 23 Jan 2017  路  10Comments  路  Source: graphql-python/graphene-django

Does anyone have an idea for (or have implemented) a DateFromToRangeFilter that works?

Example, filtering a collection of Articles based on a published_date attribute range (between 2 dates)

With Django Rest Framework when using this filter we send the values as published_0 (for from) and published_1 (for to) to get a range.

Not quite sure how to accomplish that with graphene, which expects this to be a simple string OOB, e.g. published: String

Most helpful comment

Thanks, as I mentioned in my edit, I did get your filter to work, unfortunately it's not exactly what I needed .

In the end I just did more direct filtering in the query resolution method:

all_occurrence_assigned_from_to = graphene.List(
    OccurrenceNode, 
    from_date=graphene.String(), 
    to_date=graphene.String(), 
    assigned_to_id=graphene.Int()
)
def resolve_all_occurrence_assigned_from_to(self, info, **kwargs):
    return Occurrence.objects.filter(
        start_time__gte=dateparse.parse_datetime(kwargs['from_date']),
        end_time__lte=dateparse.parse_datetime(kwargs['to_date']),
        event__assigned_to__id=kwargs['assigned_to_id']
    )

These filters are all that's really required, it'd have been nice to have some others that come with django_filters, but I'll leave them out for now.

Cheers!

All 10 comments

What sort of field do you currently have that is returning Articles?

If you were to use a DjangoFilterConnectionField you could define a custom FilterSet

Take a look here: http://django-filter.readthedocs.io/en/develop/ref/filterset.html#filter-for-lookup

Otherwise, it would be on you in a resolver to handle arguments, and return an appropriate set of Articles.

I'm already using a FilterSet and the other fields I'm allowing filtering on work great but can't really seem to get a range filter to come across from DRF implementation without hassle.
I'll try the link you sent thought...get back to you. Tnx

The link I provided was very literally a DateRangeFilter example for FilterSet.

Yeah saw that now...tnx. My bad ;)

Haha ok :) It looks like DjangoRestFramework may be using this behind the scenes too http://django-filter.readthedocs.io/en/develop/ref/filters.html#datefromtorangefilter so wahoo!

As far as I can see graphehe_django does not have support for range lookups at all. If I look in form_converter.py it doesn't have support for ranges. Basically graphene expects the value to be a string (and maybe I can send the two values as a JSON string or a list) but then again, there's no support for the DateRangeField which knows how to extract start and stop dates from a list of 2 values.

UPDATE:

This is what I had to do to make a date range filter work. Don't know if this is something that could be added to either docs or source...maybe with some tweaks to make it more generic but anyhow.

Since the grapql query from the client can only be a String (don't know if that can be tweaked...see previous comment) I'm sending this to the graphql endpoint to filter by a range (basically a between filter I would call it).

articles(published_between: "[\"2016-12-01\",\"2016-12-31\"]") {
url
published_at
title
}

As a side note, I'm using JSON.stringify to turn a javascript array of the 2 dates into a string here.

And on the server I did this:

class ArticleFilter(django_filters.rest_framework.FilterSet):

    published_between = DateFromToRangeFilter(name='published_at')

    class Meta:
        model = ArticleModel
        fields = ['published_between', ]

Note here that DateFromToRangeFilter is a custom class that I created that looks like this:

import json
from datetime import datetime, time
from django.forms import DateField, Field
from django_filters.filters import RangeFilter
from django_filters.utils import handle_timezone

class DateRangeField(Field):

    def compress(self, data_list):
        if data_list:
            start_date, stop_date = data_list
            if start_date:
                start_date = handle_timezone(
                    datetime.combine(start_date, time.min))
            if stop_date:
                stop_date = handle_timezone(
                    datetime.combine(stop_date, time.max))
            return slice(start_date, stop_date)
        return None

    def clean(self, value):
        if value:
            clean_data = []
            values = json.loads(value)
            if isinstance(values, (list, tuple)):
                for field_value in values:
                    clean_data.append(DateField().clean(field_value))
            return self.compress(clean_data)
        else:
            return self.compress([])    


class DateFromToRangeFilter(RangeFilter):
    field_class = DateRangeField

...which is basically a copy/paste/modification of some things from django_filter and the MultiValueField from django.forms.

The key part here is the 'DateRangeField' class that it takes the string value of the two dates (which is a json string) and turns that into a python list which can then be "cleaned" and used by the builtin RangeFilter from django_filter. And...it only works with dates as of now since that was my use case but I guess that could be made more generic if applicable.

Thoughts/Comments on this approach??

Hi guys, sorry to raise this from the dead but it's the _only_ thread relating to date filters I can find.

I've tried mimicking the example above exactly, but I can't get it to work, it returns all items as if there were no filter.

I'm able to filter dates using filter_fields directly on the DjangoObjectType, but I also need to filter on a foreign key. I can filter that foreign key in a custom filter, but I cannot get the date filter to work on the custom filter. So it's one or the other...

Here's a summary of my model:

class Occurrence(models.Model):
    start_time = models.DateTimeField(_('start time'))
    end_time = models.DateTimeField(_('end time'))
    event = models.ForeignKey(Event)

This will filter date ranges no problem.

class OccurrenceNode(DjangoObjectType):
    class Meta:
        model = Occurrence
        interfaces = (relay.Node,)
        filter_fields = {
            'start_time': ['gte'],
            'end_time': ['lte']
        }

This will filter on the foreign key.

class OccurrenceFilter(django_filters.rest_framework.FilterSet):
    event__id = django_filters.ModelChoiceFilter(
        queryset=Event.objects.all().values_list('id', flat=True)
    )

    class Meta:
        model = Event
        fields = {
            'event__id': ['exact']
        }

It's frustrating because I'm close but cannot seem to get either option to filter both the foreign key and the date ranges. Any suggestions? Thanks in advance.

EDIT: I was able to get it to get your example to work, my mistake... I was using the wrong implementation of DateFromToRangeFilter while testing. I had django_filters.DateFromToRangeFilter, not DateFromToRangeFilter. Still though, I'd appreciate any input on how to merge the two filtering methods I've detailed above. Thanks.

EDIT 2: *facepalm* I was trying to filter the event__id using the row integer id, not the graphql ID. Ignore most/all of above.

@daverickdunn don't know if this "helps" you in any way but...

// this is the QUERY part
articles = DjangoFilterConnectionField(Article, filterset_class=ArticleFilter)

// this is where you define the filters that are applied to the query (which basically does queryset.filter().filter()...etc
class ArticleFilter(django_filters.rest_framework.FilterSet):

    published_between = DateFromToRangeFilter()
    belongs_to = django_filters.NumberFilter(name="belongs_to__id")

    class Meta:
        model = Article
        fields = [
            'published_between',
            'belongs_to'
        ]

Thanks, as I mentioned in my edit, I did get your filter to work, unfortunately it's not exactly what I needed .

In the end I just did more direct filtering in the query resolution method:

all_occurrence_assigned_from_to = graphene.List(
    OccurrenceNode, 
    from_date=graphene.String(), 
    to_date=graphene.String(), 
    assigned_to_id=graphene.Int()
)
def resolve_all_occurrence_assigned_from_to(self, info, **kwargs):
    return Occurrence.objects.filter(
        start_time__gte=dateparse.parse_datetime(kwargs['from_date']),
        end_time__lte=dateparse.parse_datetime(kwargs['to_date']),
        event__assigned_to__id=kwargs['assigned_to_id']
    )

These filters are all that's really required, it'd have been nice to have some others that come with django_filters, but I'll leave them out for now.

Cheers!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MilanRgm picture MilanRgm  路  3Comments

x9sheikh picture x9sheikh  路  4Comments

MythicManiac picture MythicManiac  路  3Comments

Northshoot picture Northshoot  路  4Comments

amiyatulu picture amiyatulu  路  3Comments