Created
March 2, 2022 21:54
-
-
Save yuekui/e27e6cd3a81d40cd611c7c1e712cd5fd to your computer and use it in GitHub Desktop.
GroupFilterSet to reduce inner joins for performance.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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