Possible to do an `in` `lookup_type` through the django-filter URL parser?

后端 未结 7 1275
野性不改
野性不改 2020-12-09 17:52

I\'m using django-filter with django-rest-framework and I\'m trying to instantiate a filter that accepts lists of numbers for filtering the query set down

c         


        
相关标签:
7条回答
  • 2020-12-09 18:21

    For better or worse, I created a custom filter for this:

    class IntegerListFilter(django_filters.Filter):
        def filter(self,qs,value):
            if value not in (None,''):
                integers = [int(v) for v in value.split(',')]
                return qs.filter(**{'%s__%s'%(self.name, self.lookup_type):integers})
            return qs
    

    Which is used like:

    class MyFilter(django_filters.FilterSet):   
        ids = IntegerListFilter(name='id',lookup_type='in')
        class Meta:
            model = MyModel
            fields = ('ids',)
    
    class MyModelViewSet(viewsets.ModelViewSet):
        queryset = MyModel.objects.all()
        serializer_class = MyModelSerializer
        filter_class = MyFilter
    

    Now my interface accepts comma-delimited lists of integers.

    0 讨论(0)
  • 2020-12-09 18:31

    Here's a complete solution:

    from django_filters import Filter, FilterSet
    from rest_framework.filters import DjangoFilterBackend
    from rest_framework.viewsets import ModelViewSet
    from .models import User
    from .serializers import UserSerializer
    
    
    class ListFilter(Filter):
    
        def filter(self, qs, value):
            if not value:
                return qs
    
            self.lookup_type = 'in'
            values = value.split(',')
            return super(ListFilter, self).filter(qs, values)
    
    
    class UserFilter(FilterSet):
        ids = ListFilter(name='id')
    
        class Meta:
            model = User
            fields = ['ids']
    
    
    class UserViewSet(viewsets.ModelViewSet):
        serializer_class = UserSerializer
        queryset = User.objects.all()
        filter_backends = (DjangoFilterBackend,)
        filter_class = UserFilter
    
    0 讨论(0)
  • 2020-12-09 18:33

    I know this is an old post, but there is now a better solution. The change that makes it correct is posted here.

    They added a BaseInFilter and a BaseRangeFilter. The documentation is here.

    Big picture, BaseFilter checks for CSV, and then when mixed with another filter it does what you are asking. Your code can now be written like:

    class NumberInFilter(filters.BaseInFilter, filters.NumberFilter):
        pass
    
    class MyModelViewSet(viewsets.ModelViewSet):
        ids = NumberInFilter(name='id', lookup_expr='in')
    
        class Meta:
            model = MyModel
            fields = ['ids']
    
    0 讨论(0)
  • 2020-12-09 18:33

    Uptodate solution:

    from django_filters import rest_framework as filters

    name-->field_name

    lookup_type-->lookup_expr

    class IntegerListFilter(filters.Filter):
        def filter(self,qs,value):
            if value not in (None,''):
                integers = [int(v) for v in value.split(',')]
                return qs.filter(**{'%s__%s'%(self.field_name, self.lookup_expr):integers})
            return qs
    
    class MyFilter(filters.FilterSet):   
        ids = IntegerListFilter(field_name='id',lookup_expr='in')
        class Meta:
            model = MyModel
            fields = ('ids',)
    
    class MyModelViewSet(viewsets.ModelViewSet):
        queryset = MyModel.objects.all()
        serializer_class = MyModelSerializer
        filter_class = MyFilter
    
    0 讨论(0)
  • 2020-12-09 18:43

    Based on @yndolok answer I have come to a general solution. I think filtering by a list of ids is a very common task and therefore should be included in the FilterBackend:

    class ListFilter(django_filters.Filter):
    
        """Class to filter from list of integers."""
    
        def filter(self, qs, value):
            """Filter function."""
            if not value:
                return qs
            self.lookup_type = 'in'
            try:
                map(int, value.split(','))
                return super(ListFilter, self).filter(qs, value.split(','))
            except ValueError:
                return super(ListFilter, self).filter(qs, [None])
    
    
    class FilterBackend(filters.DjangoFilterBackend):
    
        """A filter backend that includes ListFilter."""
    
        def get_filter_class(self, view, queryset=None):
            """Append ListFilter to AutoFilterSet."""
            filter_fields = getattr(view, 'filter_fields', None)
    
            if filter_fields:
                class AutoFilterSet(self.default_filter_set):
                    ids = ListFilter(name='id')
    
                    class Meta:
                        model = queryset.model
                        fields = list(filter_fields) + ["ids"]
    
                return AutoFilterSet
    
            else:
                return super(FilterBackend, self).get_filter_class(view, queryset)
    
    0 讨论(0)
  • 2020-12-09 18:46

    According to a post in the django-filter issues:

    from django_filters import Filter
    from django_filters.fields import Lookup
    
    class ListFilter(Filter):
        def filter(self, qs, value):
            return super(ListFilter, self).filter(qs, Lookup(value.split(u","), "in"))
    

    I have personally used this without any issue in my projects, and it works without having to create a per-type filter.

    0 讨论(0)
提交回复
热议问题