可以通过 django-filter URL 解析器执行“in”“lookup_type”吗?

Posted

技术标签:

【中文标题】可以通过 django-filter URL 解析器执行“in”“lookup_type”吗?【英文标题】:Possible to do an `in` `lookup_type` through the django-filter URL parser? 【发布时间】:2014-07-25 08:26:07 【问题描述】:

我将django-filter 与django-rest-framework 一起使用,并且我正在尝试实例化一个过滤器,该过滤器接受数字列表以过滤设置的查询

class MyFilter(django_filters.FilterSet):   
    ids = django_filters.NumberFilter(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

如果我传入一个逗号分隔的整数列表,则过滤器将被完全忽略。

如果我传入一个整数,它会通过 django-filter 进入 django 的表单验证器并报错:

'Decimal' object is not iterable

有没有办法创建一个可以处理整数列表并正确过滤查询集的 django-filter 对象?

【问题讨论】:

我知道这是旧的,但现在有一种受支持的方法可以做到这一点,正如我在下面发布的那样。 【参考方案1】:

无论好坏,我为此创建了一个自定义过滤器:

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

使用如下:

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

现在我的界面接受以逗号分隔的整数列表。

【讨论】:

好,我认为我使用您的拆分方法将逗号分隔的请求 ID 解析为来自 django 管理员的 export-object-ids,如 docs.djangoproject.com/en/dev/ref/contrib/admin/actions 中所述 如果它是一个公开可用的 API,这看起来有点像一个安全问题。是否可以通过请求数量惊人的 ID 来完成 DOS? 你是对的。您可以截断值长度,例如 value[0:1000].split(',')【参考方案2】:

我知道这是一篇旧帖子,但现在有更好的解决方案。使其正确的更改发布在here。

他们添加了BaseInFilterBaseRangeFilter。文档是here。

大图,BaseFilter 检查 CSV,然后当与另一个过滤器混合时,它会按照您的要求进行操作。你的代码现在可以写成这样:

class NumberInFilter(filters.BaseInFilter, filters.NumberFilter):
    pass

class MyModelViewSet(viewsets.ModelViewSet):
    ids = NumberInFilter(name='id', lookup_expr='in')

    class Meta:
        model = MyModel
        fields = ['ids']

【讨论】:

【参考方案3】:

这是一个完整的解决方案:

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

【讨论】:

这曾经对我有用,但现在它停止了,我不知道为什么。我现在尝试执行此操作时得到“int()参数必须是字符串或数字,而不是'列表'”。【参考方案4】:

根据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"))

我个人在我的项目中使用它没有任何问题,它无需创建每个类型的过滤器即可工作。

【讨论】:

【参考方案5】:

根据@yndolok 的回答,我得出了一个通用的解决方案。我认为通过 id 列表进行过滤是一项非常常见的任务,因此应该包含在 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)

【讨论】:

【参考方案6】:

最新解决方案:

从 django_filters 导入 rest_framework 作为过滤器

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

【讨论】:

【参考方案7】:

正如我在这里DjangoFilterBackend with multiple ids 所回答的那样,现在很容易制作一个接受列表并验证内容的过滤器

例如:


from django_filters import rest_framework as filters


class NumberInFilter(filters.BaseInFilter, filters.NumberFilter):
    pass

class MyFilter(filters.FilterSet):
    id_in = NumberInFilter(field_name='id', lookup_expr='in')

    class Meta:
        model = MyModel
        fields = ['id_in', ]

这将接受来自 get 参数的整数列表。例如/endpoint/?id_in=1,2,3

【讨论】:

这个答案没有添加任何新内容,只是重复@Diesel 之前的答案。 确实,我一定错过了这个答案。

以上是关于可以通过 django-filter URL 解析器执行“in”“lookup_type”吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 django-filters 过滤 ModelViewSet 中的自定义 url

如何在 ListAPIView 中使用 django-filter 对过滤结果进行排序

django-filter使用分页

如何在ListAPIView中使用django-filter对过滤结果进行排序

django-filter 使用分页

Django-filter 在传递 filter_field 的特定值时获取所有记录