how to use custom filter class in another filter class
lidyum opened this issue · comments
I use django-filter
I have two models named Person and Member. Person has been defined as foreign key in Member. As an example here, I added less fields, normally the PersonModel model has a lot more fields. What I want to do here is to use all the filter fields I created for person in the filter class I created for Member.
models.py
class PersonModel(models.Model):
name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
class MemberModel(models.Model)
register_no = models.CharField(max_length=20, unique=True)
person = filters.ForeignKey(PersonModel, on_delete=models.PROTECT, related_name='members')
filters.py
class PersonFilter(filters.FilterSet):
name= filters.CharFilter(field_name='name')
last_name= filters.CharFilter(field_name='last_name')
class MemberFilter(filters.FilterSet):
register_no = filters.CharFilter(field_name='register_no')
#
# I want to use PersonFilter here
# for example: person = PersonFilter(lookup_expr="person") or how?
#
# I don't want to do it this way:
# person_name = filters.CharFilter(field_name='person__name')
# person_last_name = filters.CharFilter(field_name='person__last_name')
FilterSets can make use of inheritance, e.g.:
>>> from django_filters import *
>>> class Parent(FilterSet):
... f1 = CharFilter()
... f2 = CharFilter()
...
>>> class Child(Parent):
... pass
...
>>> Child.base_filters
OrderedDict([('f1', <django_filters.filters.CharFilter object at 0x106951780>), ('f2', <django_filters.filters.CharFilter object at 0x106953fd0>)])
base_filters
How we will connect Person and Member relation?
There's nothing built in for nesting filter sets directly.
But did you try a method filter (say) that instantiated the sun filter set and then used the result qs
of that for the related field?
I did it like this;
class MemberFilter(PersonFilter):
def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
super().__init__(data, queryset, request=request, prefix=prefix)
person_filters = PersonFilter.get_filters()
person_fields = list(person_filters.keys())
for f in person_fields:
person_filter_cls = person_filters[f]
field_name = person_filter_cls.field_name
self.filters[f].field_name = f"person__{field_name}"
this solution does not work. It gives an error when I extend the Member Filter class
class MemberFilter(NaturalPersonFilter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Automatically add fields from PersonFilter to MemberFilter
# for name, filter_ in NaturalPersonFilter.base_filters.items():
for name, filter_ in NaturalPersonFilter.base_filters.items():
self.filters[name] = deepcopy(filter_)
self.filters[name].model = MemberModel
self.filters[name].field_name = f'person__{self.filters[name].field_name}'
class ActivityMemberFilter(MemberFilter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Automatically add fields from MemberFilter to ActivityMemberFilter
for name, filter_ in MemberFilter.base_filters.items():
self.filters[name] = filter_
self.filters[name].model = ActivityMemberModel
self.filters[name].field_name = f'member__{self.filters[name].field_name}'
for name, value in self.get_filters().items():
print(name+":"+str(value), "-->", value.field_name)
# here all fields like this;
# member__name
# member__last_name
#........
# this is how it should be;
# member__person__name
# member__person__last_name
Why not do it in two passes...
def filter_queryset(...):
people_qs = PeopleFilter(request.GET, ...).qs
member_filter = MemberFilter(request.GET, queryset=Member.objects.filter(people__in=people_qs), ...)
...
You could pull that into the filter set to hide it from the view, but that looks pretty straightforward to my eye... 🤔
I use django rest framework and i have too many views.
class MemberPaginateView(generics.ListAPIView):
"""
Paginate members
Paginate members
"""
queryset = MemberModel.objects.all()
serializer_class = MemberSerializer
pagination_class = StandardPagination
filter_backends = (DjangoFilterBackend, OrderingFilter)
filterset_class = MemberFilter
ordering_fields = ['person__name', 'person__last_name']
ordering = ['person__name', 'person__last_name']
def get_queryset(self):
return super().get_queryset().distinct()
The backend defines a filter queryset method. You can override that.
well, thank you very much. Since there are too many views, the method you mentioned is not suitable for me. I will try to handle it in this filter class