miki725 / django-url-filter

Django URL Filter provides a safe way to filter data via human-friendly URLs.
http://django-url-filter.readthedocs.io/
Other
333 stars 77 forks source link

Support filtering on aggregate fields. #32

Open ahmohamed opened 7 years ago

ahmohamed commented 7 years ago

I am not sure how complex to implement this, but it would be really useful if we can filter on aggregate (annotated) fields.

Exmaple: Consider these models

from django.db import models

class Reporter(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    email = models.EmailField()

class Article(models.Model):
    headline = models.CharField(max_length=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(Reporter)

And the DRF viewset

from rest_framework import viewsets

class ReporterViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = models.Reporter.objects.annotate(article_count=Count("reporter_set"))

    filter_backends = [DjangoFilterBackend]
    filter_fields = ['last_name', 'article_count']

We filter reporters that have at least n articles by reporter?article_count__gte=5.

This setup currently result in error Reporter has no field named 'article_count'. Since the filtering is done on the queryset not the model, I am not sure why the model is being checked.

Thanks.

miki725 commented 7 years ago

Thanks for the suggestion. Reason why it checks agains a model is because you are using a ModelFilterSet which gets the fields from the model definition in order to get the form class for the filter to use for data validation. Will try to implement in future however not sure how that will work since annotated fields might be loosing their data-type so I would not know which form class to use for those fields.

Meanwhile you should be able to manually define a filterset for that field. Something like:

class MyFilterSet(ModelFilterSet):
    article_count = Filter(form_class=forms.IntegerField())
    class Meta(object):
        model = Reporter

you can always see what filters are set on the filterset by doing a representation of the filterset:

print(repr(MyFilterSet()))
ahmohamed commented 7 years ago

Thank you for the quick response! The work around is working very fine for now.