Skip to content

Instantly share code, notes, and snippets.

@yuekui
Created March 2, 2022 21:54
Show Gist options
  • Save yuekui/e27e6cd3a81d40cd611c7c1e712cd5fd to your computer and use it in GitHub Desktop.
Save yuekui/e27e6cd3a81d40cd611c7c1e712cd5fd to your computer and use it in GitHub Desktop.
GroupFilterSet to reduce inner joins for performance.
from django.db.models import Q
from django.db.models.constants import LOOKUP_SEP
from django_filters import rest_framework as django_filters
from django_filters.constants import EMPTY_VALUES
class GroupFilterSet(django_filters.FilterSet):
def filter_queryset(self, queryset):
"""
Group the fitlers by the first join table to
reduce inner join queries for performance.
This would avoid filter chaining like:
`Model.objects.filter(table_foo__id__in=[xx,xx]).filter(table_foo__name__in=[xx,xx])`
Instead, it would be grouped as:
`Model.objects.filter(table_foo__id__in=[xx,xx], table_foo__name__in=[xx,xx])`
Inspired by discussion at:
https://github.com/carltongibson/django-filter/issues/745
https://github.com/carltongibson/django-filter/pull/1167
"""
groups = {}
is_distincted = False
for name, value in self.form.cleaned_data.items():
if value in EMPTY_VALUES:
continue
f = self.filters[name]
# Do not merge Qs for customized filter method due to complexity.
if f._method or not f.__class__.filter == django_filters.Filter.filter:
queryset = self.filters[name].filter(queryset, value)
continue
# Use the joined table name as key
group_name = f.field_name.split(LOOKUP_SEP)[0]
q = Q(**{LOOKUP_SEP.join([f.field_name, f.lookup_expr]): value})
if f.exclude:
q = ~q
# Check if there's any join query with the same table
if group_name in groups:
groups[group_name] = groups[group_name] & q
else:
groups[group_name] = q
if f.distinct:
is_distincted = True
for q in groups.values():
queryset = queryset.filter(q)
if is_distincted:
queryset = queryset.distinct()
return queryset
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment