Django-filter: Allow to override retrieving of a queryset in a FilterSet

Created on 21 Jun 2017  ·  4Comments  ·  Source: carltongibson/django-filter

The title may be a little bit confusing. It would be nice to have a generic filter method which is called everytime queryset is retrieved and filtering for fields is applied. Yeah, I can override qs property, firstly get filtered queryset and then apply prefetches. But _it's not clear if this may have some side-effects and the data passed to filter is safe to access._

There's my use case.
I'm filtering a queryset by a M2M relationship (as in queryset.filter(items__field="foo")). However I want to get prefetched items filtered by that field as well.

In other words my filter looks like this:

# In my filterset
def filter_items_by_field(self, queryset, name, value):
        queryset = queryset.all().filter(
            items__field=value,
        )
        prefetched_items = Prefetch(
            'items',
            Item.objects..filter(
                field=value,
            ),
        )
        return queryset.prefetch_related(prefetched_items)

# In my vieset
def get_queryset(self):
        queryset = super().get_queryset()
        # This should be applied everytime
        prefetched_items = Prefetch(
            'items',
            Item.objects..filter(
                another_field=some_value,
            ),
        )
        return queryset.prefetch_related(prefetched_items)

A single filter like this works as expected. However, I may have several filter_items_by_* filters + I need to apply same filtering for queryset from prefetch for all filters. And multiple filters aren't a problem, because they're mutually exclusive in my case. But a filter + generic will cause an exception.

The point is that I can apply only one prefetch for the same lookup. So, in my case I'll simply get an exception.

I have a temporary workaround for this problem: all filter_items_by_* performs mandatory filtering on prefetch.
And in filter_queryset (after filtering was applied), if items aren't prefetched (what is bad since I'm inspecting queryset protected attrs), I apply prefetch with the mandatory filtering. But there should be a better way to deal with situations like this.

I hope my example is clear enough.

Most helpful comment

Hi @Alexx-G. A 2.0 release is still a ways out, but there are going to be some changes to the API that specifically address this question. In short,

  • You will want to override the .filter_queryset() method. The .qs attribute will basically be responsible for calling .filter_queryset() and caching its result.
  • You can get the filter values for prefetching from self.form.cleaned_data.

All 4 comments

Hi @Alexx-G. I think this could be possible with some coordination between the filterset and relevant filter classes. Essentially, the filter would append a Prefetch instance to its parent filterset. FilterSet.qs would then simply call prefetch_related when appropriate.

That said, what's the use case for this? I would argue that there are likely more appropriate places to handle prefetching.

I think this could be possible with some coordination between the filterset and relevant filter classes.

I've already solved my issue by overriding qs property, but again, it's not obvious and it's not documented. In other words, I can't be sure that some internal changes in the base class won't affect my filterset (ok, I've pinned version, but anyway..)

That said, what's the use case for this? I would argue that there are likely more appropriate places to handle prefetching.

I believe I described above my use case. It doesn't make sense to separate filtering and prefetching, because prefetching depends on filters. And if I separate them, I'll have to duplicate same checks for filters.

In other words, if there was a description of the public contract of the qs property or a documented hook for it, this would improve customization of the filter set.

Hi @Alexx-G. A 2.0 release is still a ways out, but there are going to be some changes to the API that specifically address this question. In short,

  • You will want to override the .filter_queryset() method. The .qs attribute will basically be responsible for calling .filter_queryset() and caching its result.
  • You can get the filter values for prefetching from self.form.cleaned_data.

Thanks a lot for your effort!

Was this page helpful?
0 / 5 - 0 ratings