问题描述
我正在使用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(**{…},**{…})
构造代替丑陋的{…} | {…}
构造