Similar Filters with different lookup expressions
Closed this issue · 5 comments
Quite often I declare filters inside a FilterSet class that are similar in nature, but just use different different lookup expressions, such as:
class MyFilterSet(FilterSet):
filter1 = CharFilter(label="Filter 1", lookup_expr="exact", method=...)
filter2 = CharFilter(label="Filter 2", lookup_expr="icontains", method=...)
If this filter would be part of a django model, I can declare it in the Meta class, such as:
class Meta:
fields = {"filter1": ["exact", "icontains"]}
Ideally I would like to also make it possible to add a list of lookup expressions to the declared filters, to avoid redefining similar filters. Is this something that is possible somehow, or is this not a good idea?
Check out LookupChoiceFilter.
price = django_filters.LookupChoiceFilter(
field_class=forms.DecimalField,
lookup_choices=[
('exact', 'Equals'),
('gt', 'Greater than'),
('lt', 'Less than'),
]
)
Check out LookupChoiceFilter.
price = django_filters.LookupChoiceFilter( field_class=forms.DecimalField, lookup_choices=[ ('exact', 'Equals'), ('gt', 'Greater than'), ('lt', 'Less than'), ] )
Looks like exactly what I want. Thanks a lot for the quick answer.
@carltongibson Just as a small Feedback: It turned out that it wasn't exactly what I needed. But I used the same idea to create a wrapper to essentially duplicate the filters with different lookup_expr set. As I wanted the exact same behavior as the filter declaration from cls.Meta.
Thanks for your help regardless
@christopher-wittlinger No problem! If you fancy you could paste your solution for others following.
@carltongibson Thanks for following up! Here's what I came up with:
Custom Filter Implementation
I created a new filter to handle multiple lookup expressions:
from django_filters import Filter
class MultipleLookupFilter(Filter):
def __init__(self, field_class, lookup_expr, **kwargs):
self.field_class = field_class
self.lookup_expr = lookup_expr
self.kwargs = kwargs
def get_filters(self, field_name) -> dict[str, Filter]:
filters = {}
for lookup_expr in self.lookup_expr:
filters[f"{field_name}__{lookup_expr}"] = self.field_class(
lookup_expr=lookup_expr,
**self.kwargs,
)
return filters
Meta Class Modification
I then modified the get_declared_filters
class method in the Meta
class to integrate the new filter:
@classmethod
def get_declared_filters(cls, bases, attrs):
filters = super().get_declared_filters(bases, attrs)
# Collect and process MultipleLookupFilters
multi_filters: list[tuple[str, MultipleLookupFilter]] = [
(filter_name, attrs.pop(filter_name))
for filter_name, obj in list(attrs.items())
if isinstance(obj, MultipleLookupFilter)
]
for field_name, filter_obj in multi_filters:
filters |= filter_obj.get_filters(field_name)
return filters
Example Usage
Here’s how I use the new filter:
activity_heat = MultipleLookupFilter(
field_class=filters.NumberFilter,
lookup_expr=["gte", "lte"],
label=_("Activity Heat"),
precision=2,
field_name="activity_heat",
)
This setup works perfectly for my use case. Ideally, it could be extended to support multiple methods and additional functionality if needed.