carltongibson / django-filter

A generic system for filtering Django QuerySets based on user selections

Home Page:https://django-filter.readthedocs.io/en/main/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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