如何使用ModelMultipleChoiceFilter?

时间:2014-10-06 04:56:56

标签: python django django-rest-framework django-filter

我一直试图让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的网站?

2 个答案:

答案 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.extraFilter.__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)
)

RaceJudge以外的多对多字段:

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:是的,我知道这不是整套可能的比赛。但美国人口普查收集的内容!