【问题标题】:Django filters: custom filter methods with different lookup expressionsDjango 过滤器:具有不同查找表达式的自定义过滤器方法
【发布时间】:2020-08-12 13:38:00
【问题描述】:

我正在使用Django filters 和 Django Rest Framework 来为 API 视图创建过滤器。 它们有一个很好的语法,我在下面的类元中定义的age 字段过滤器中显示 fields = {'age': ['exact', 'gte', 'lte', 'lt', 'gt', 'in']} 允许一次为字段创建所有这些大于或等于、小于或等于、小于、大于等过滤器...

使用像artworks_count 这样的自定义字段,我也在下面显示,但是,我似乎需要为每个查找表达式添加不同的方法,这是相当重复的。我只是想知道是否没有更好的方法来做到这一点? (也许使用 NumberFilter 以外的其他东西?)将 lookup_expression 作为参数并允许我使用它构造单个方法

class UserFilter(django_rest_filters.FilterSet):


    artworks_count__gte = django_rest_filters.NumberFilter(method="filter_artworks_count__gte")

    class Meta:
        model = User
        fields = {'age': ['exact', 'gte', 'lte', 'lt', 'gt', 'in']}

    def filter_artwork_count__gte(self, qs, name, value):
        return qs.annotate(art_count=Count('artworks')).filter(art_count__gte=value)

【问题讨论】:

    标签: django django-rest-framework django-filters


    【解决方案1】:

    您可以创建自己的基本过滤器,您可以从中继承和定义模型字段过滤器。

    from django_filters import (
        BaseInFilter,
        BaseRangeFilter,
        BooleanFilter,
        FilterSet,
        NumberFilter,
    )
    
    
    ### first, create filterset class attributes
    
    def create_custom_method_filters(filter_name, base_filter_class, lookups, func):
        custom_method_name = f'filter_{filter_name}'
    
        def custom_method(self, queryset, name, value):
            return func(queryset, name, value)
        boolean_lookups = ('isnull', 'isempty')
        in_lookups = ('in',)
        range_lookups = ('range',)
        d = dict(
            **{
                f'{filter_name}__{lookup}' if lookup != 'exact' else filter_name: base_filter_class(method=custom_method_name)
                for lookup in lookups
                if lookup not in boolean_lookups + in_lookups + range_lookups
            },
            **{
                f'{filter_name}__{lookup}': BooleanFilter(method=custom_method_name)
                for lookup in lookups
                if lookup in boolean_lookups
            },
            **{
                f'{filter_name}__{lookup}': type('InFilter', (BaseInFilter, base_filter_class), {})(method=custom_method_name)
                for lookup in lookups
                if lookup in in_lookups
            },
            **{
                f'{filter_name}__{lookup}': type('RangeFilter', (BaseRangeFilter, base_filter_class), {})(method=custom_method_name)
                for lookup in lookups
                if lookup in range_lookups
            },
            **{
                custom_method_name: custom_method,
            },
        )
        return d
    
    ### second, create your base filter for the specific filter
    
    UserFilterBase = type(
        'UserFilterBase',
        (FilterSet,),
        create_custom_method_filters(
            'artworks_count',
            NumberFilter,
            ['exact', 'in', 'gt', 'gt', 'lt', 'lte', 'range'],
            lambda queryset, name, value: queryset.annotate(artworks_count=Count('artworks')).filter(**{name: value}),
        ),
    )
    
    ### Then, inherit from that base class and use the filter meta as usual.
    
    
    class UserFilter(UserFilterBase):
        class Meta:
            model = User
            fields = {'age': ['exact', 'gte', 'lte', 'lt', 'gt', 'in']}
    

    在 Python 3.9+ 中,您可以将丑陋的 dict(**{…}, **{…}) 构造替换为更好的 {…} | {…} 构造

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-20
      • 2016-07-01
      • 2018-02-23
      • 2010-10-14
      • 1970-01-01
      相关资源
      最近更新 更多