Since filtering by non-field attributes such as property
inevitably converts the QuerySet
to list
(or similar), I like to postpone it and do the filtering on object_list
in get_context_data
method. To keep the filtering logic inside the filterset
class, I use a simple trick. I've defined a decorator
def attr_filter(func):
def wrapper(self, queryset, name, value, force=False, *args, **kwargs):
if force:
return func(self, queryset, name, value, *args, **kwargs)
else:
return queryset
return wrapper
which is used on django-filter
non-field filtering methods. Thanks to this decorator, the filtering basically does nothing (or skips) the non-field filtering methods (because of force=False
default value).
Next, I defined a Mixin
to be used in the view
class.
class FilterByAttrsMixin:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
filtered_list = self.filter_qs_by_attributes(self.object_list, self.filterset)
context.update({
'object_list': filtered_list,
})
return context
def filter_qs_by_attributes(self, queryset, filterset_instance):
if hasattr(filterset_instance.form, 'cleaned_data'):
for field_name in filter_instance.filters:
method_name = f'attr_filter_{field_name}'
if hasattr(filterset_instance, method_name):
value = filterset_instance.form.cleaned_data[field_name]
if value:
queryset = getattr(filterset_instance, filter_method_name)(queryset, field_name, value, force=True)
return queryset
It basically just returns to your filterset
and runs all methods called attr_filter_<field_name>
, this time with force=True
.
In summary, you need to:
- Inherit the
FilterByAttrsMixin
in your view
class
- call your filtering method
attr_filter_<field_name>
- use
attr_filter
decorator on the filtering method
Simple example (given that I have model
called MyModel
with property
called is_static
that I want to filter by:
model:
class MyModel(models.Model):
...
@property
def is_static(self):
...
view:
class MyFilterView(FilterByAttrsMixin, django_filters.views.FilterView):
...
filterset_class = MyFiltersetClass
...
filter:
class MyFiltersetClass(django_filters.FilterSet):
is_static = django_filters.BooleanFilter(
method='attr_filter_is_static',
)
class Meta:
model = MyModel
fields = [...]
@attr_filter
def attr_filter_is_static(self, queryset, name, value):
return [instance for instance in queryset if instance.is_static]