Django过滤器:具有不同查找表达式的自定义过滤器方法

问题描述

我正在使用Django filters和Django Rest Framework来创建API视图的过滤器。 它们具有很好的语法,我在下面的类Meta中定义的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','in']}

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

解决方法

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

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,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,{})(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',['exact','in','gt','lt','lte','range'],lambda queryset,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','in']}

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