我一直试图让ModelMultipleChoiceFilter工作几个小时,并且已经阅读了DRF和Django Filters文档。
我希望能够根据通过ManyToManyField分配给它们的标记来过滤一组网站。例如,我希望能够获得标记为“烹饪”或“养蜂”的网站列表。
以下是我当前models.py的相关摘要:
class SiteTag(models.Model):
"""Site Categories"""
name = models.CharField(max_length=63)
def __str__(self):
return self.name
class Website(models.Model):
"""A website"""
domain = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=2047)
rating = models.IntegerField(default=1, choices=RATING_CHOICES)
tags = models.ManyToManyField(SiteTag)
added = models.DateTimeField(default=timezone.now())
updated = models.DateTimeField(default=timezone.now())
def __str__(self):
return self.domain
我目前的views.py片段:
class WebsiteFilter(filters.FilterSet):
# With a simple CharFilter I can chain together a list of tags using &tag=foo&tag=bar - but only returns site for bar (sites for both foo and bar exist).
tag = django_filters.CharFilter(name='tags__name')
# THE PROBLEM:
tags = django_filters.ModelMultipleChoiceFilter(name='name', queryset=SiteTag.objects.all(), lookup_type="eq")
rating_min = django_filters.NumberFilter(name="rating", lookup_type="gte")
rating_max = django_filters.NumberFilter(name="rating", lookup_type="lte")
class Meta:
model = Website
fields = ('id', 'domain', 'rating', 'rating_min', 'rating_max', 'tag', 'tags')
class WebsiteViewSet(viewsets.ModelViewSet):
"""API endpoint for sites"""
queryset = Website.objects.all()
serializer_class = WebsiteSerializer
filter_class = WebsiteFilter
filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,)
search_fields = ('domain',)
ordering_fields = ('id', 'domain', 'rating',)
我刚刚使用查询字符串[/path/to/sites]?tags=News
进行测试,并且我100%确定存在适当的记录,因为它们与?tag
一起工作(如上所述)(缺少s
)查询。
我尝试的其他事情的一个例子是:
tags = django_filters.ModelMultipleChoiceFilter(name='tags__name', queryset=Website.objects.all(), lookup_type="in")
如何退回任何SiteTag满足name == A OR name == B OR name == C
的网站?
答案 0 :(得分:17)
我在试图解决一个几乎完全相同的问题时偶然发现了这个问题,虽然我本来可以写一个自定义过滤器,但你的问题让我很感兴趣,我不得不深入挖掘!
事实证明,ModelMultipleChoiceFilter
只对正常Filter
进行了一次更改,如下面的django_filters
源代码所示:
class ModelChoiceFilter(Filter):
field_class = forms.ModelChoiceField
class ModelMultipleChoiceFilter(MultipleChoiceFilter):
field_class = forms.ModelMultipleChoiceField
也就是说,它会将field_class
从Django的内置格式更改为ModelMultipleChoiceField
。
看一下ModelMultipleChoiceField
的源代码,__init__()
所需的参数之一是queryset
,因此您就在那里了。
另一个难题来自ModelMultipleChoiceField.clean()
方法,其中一行:key = self.to_field_name or 'pk'
。这意味着默认情况下它会传递给你的任何值(例如,"cooking"
)并尝试查找Tag.objects.filter(pk="cooking")
,显然我们希望它查看名称,并且我们可以在该行中看到,它所比较的字段由self.to_field_name
控制。
幸运的是,django_filters
的{{1}}方法在实例化实际字段时包含以下内容。
Filter.field()
特别值得注意的是self._field = self.field_class(required=self.required,
label=self.label, widget=self.widget, **self.extra)
来自**self.extra
:Filter.__init__()
,所以我们需要做的就是将额外的self.extra = kwargs
kwarg传递给{{1}它将被传递给基础to_field_name
。
所以(跳过这里的实际解决方案!),你想要的实际代码是
ModelMultipleChoiceFilter
所以你真的很接近你上面发布的代码!我不知道这个解决方案是否会再与您相关,但希望将来可能对其他人有所帮助!
答案 1 :(得分:0)
对我有用的解决方案是使用MultipleChoiceFilter
。在我的情况下,我有法官参加比赛,我希望我的API能让人们查询,比如黑人或白人评委。
过滤器最终成为:
race = filters.MultipleChoiceFilter(
choices=Race.RACES,
action=lambda queryset, value:
queryset.filter(race__race__in=value)
)
Race
是Judge
以外的多对多字段:
class Race(models.Model):
RACES = (
('w', 'White'),
('b', 'Black or African American'),
('i', 'American Indian or Alaska Native'),
('a', 'Asian'),
('p', 'Native Hawaiian or Other Pacific Islander'),
('h', 'Hispanic/Latino'),
)
race = models.CharField(
choices=RACES,
max_length=5,
)
我通常不是lambda
函数的忠实粉丝,但它在这里有意义,因为它的功能如此之小。基本上,这会设置MultipleChoiceFilter
,将值从GET参数传递到race
模型的Race
字段。它们作为列表传入,这就是in
参数有效的原因。
所以,我的用户可以这样做:
/api/judges/?race=w&race=b
他们会找回被认定为黑人或白人的法官。
PS:是的,我知道这不是整套可能的比赛。但是美国人口普查收集的内容!