Django过滤器精确匹配多字段:ManyToManyField using ModelMultipleChoiceFilter

Posted

技术标签:

【中文标题】Django过滤器精确匹配多字段:ManyToManyField using ModelMultipleChoiceFilter【英文标题】:Django filter exact match for multi field: ManyToManyField using ModelMultipleChoiceFilter 【发布时间】:2020-09-25 19:11:48 【问题描述】:

我在我的项目中使用 Django 过滤器 (django-filter)。我有以下模型,其中一个组合 (Work) 有一个多对多 instrumentations 字段和一个 through 模型。每个仪器中都有几个仪器。

models.py:

class Work(models.Model):

    instrumentations = models.ManyToManyField(Instrument,
        through='Instrumentation',
        blank=True)

class Instrument(models.Model):

    name = models.CharField(max_length=100)

class Instrumentation(models.Model):

    players = models.IntegerField(validators=[MinValueValidator(1)])
    work = models.ForeignKey(Work, on_delete=models.CASCADE)
    instrument = models.ForeignKey(Instrument, on_delete=models.CASCADE)

views.py:

import django_filters

class WorkFilter(django_filters.FilterSet):

    instrument = django_filters.ModelMultipleChoiceFilter(
        field_name="instrumentation__instrument",
        queryset=Instrument.objects.all())

我的过滤器工作正常:它会抓取用户在过滤器表单中选择的乐器的所有部分。

但是,我想添加使用这些精确工具过滤乐曲的可能性。例如,如果一首乐曲包含小提琴、圆号和大提琴而没有其他内容,我想得到它,但不是为小提琴、圆号、大提琴和打击乐而写的乐曲。有可能实现吗?

我还希望用户从界面中选择是否执行精确搜索,但我想这是目前的次要问题。


更新type_of_search使用ChoiceFilter

我取得了一些的进步;使用下面的代码,我可以让用户在两种搜索之间进行选择。现在,我需要找出哪个查询将只抓取具有该组确切乐器的乐曲。

class WorkFilter(django_filters.FilterSet):

    # ...

    CHOICES = 
        ('exact', 'exact'), ('not_exact', 'not_exact')
    

    type_of_search = django_filters.ChoiceFilter(label="Exact match?", choices=CHOICES, method="filter_instruments")

    def filter_instruments(self, queryset, name, value):
        if value == 'exact':
            return queryset.??
        elif value == 'not_exact':
            return queryset.??

我知道我想要的查询类似于:

Work.objects.filter(instrumentations__name='violin').filter(instrumentations__name='viola').filter(instrumentations__name='horn')

我只是不知道如何将其“翻译”成django_filters 语言。


更新2'exact'查询使用QuerySet.annotate

感谢this question,我想这是我正在寻找的查询:

from django.db.models import Count

instrument_list = ['...'] # How do I grab them from the form?
instruments_query = Work.objects.annotate(count=Count('instrumentations__name')).filter(count=len(instrument_list))

for instrument in instrument_list:
    instruments_query = instruments_query.filter(instrumentations__name=instrument_list)

我觉得我很接近,我只是不知道如何与django_filters集成。


更新 3WorkFilter 如果搜索准确则返回空

class WorkFilter(django_filters.FilterSet):

    genre = django_filters.ModelChoiceFilter(
        queryset=Genre.objects.all(),
        label="Filter by genre")

    instrument = django_filters.ModelMultipleChoiceFilter(
        field_name="instrumentation__instrument",
        queryset=Instrument.objects.all(),
        label="Filter by instrument")

    CHOICES = 
        ('exact', 'exact'), ('not_exact', 'not_exact')
    

    type_of_search = django_filters.ChoiceFilter(label="Exact match?", choices=CHOICES, method="filter_instruments")

    def filter_instruments(self, queryset, name, value):
        instrument_list = self.data.getlist('instrumentation__instrument')

        if value == 'exact':
            queryset = queryset.annotate(count=Count('instrumentations__name')).filter(count=len(instrument_list))

            for instrument in instrument_list:
                queryset = queryset.filter(instrumentations__name=instrument)

        elif value == 'not_exact':
            pass  # queryset = ...

        return queryset

    class Meta:
        model = Work
        fields = ['genre', 'title', 'instrument', 'instrumentation']

【问题讨论】:

【参考方案1】:

您可以使用self.data.getlist('instrument') 获取instrument_list

这就是您将instrument_list 用于'exact' 查询的方式:

type_of_search = django_filters.ChoiceFilter(label="Exact match?", choices=CHOICES, method=lambda queryset, name, value: queryset)

instrument = django_filters.ModelMultipleChoiceFilter(
    field_name="instrumentation__instrument",
    queryset=Instrument.objects.all(),
    label="Filter by instrument",
    method="filter_instruments")

def filter_instruments(self, queryset, name, value):
    if not value:
        return queryset

    instrument_list = self.data.getlist('instrument')  # [v.pk for v in value]
    type_of_search = self.data.get('type_of_search')

    if type_of_search == 'exact':
        queryset = queryset.annotate(count=Count('instrumentations')).filter(count=len(instrument_list))

        for instrument in instrument_list:
            queryset = queryset.filter(instrumentations__pk=instrument)
    else:
        queryset = queryset.filter(instrumentations__pk__in=instrument_list).distinct()

    return queryset

【讨论】:

我已邀请到一个存储库,其中包含用于运行 Django 应用程序的最少代码,包括 sqlite 示例数据:) 现在它正在做它需要做的事情。再次感谢您,很好的解决方案。

以上是关于Django过滤器精确匹配多字段:ManyToManyField using ModelMultipleChoiceFilter的主要内容,如果未能解决你的问题,请参考以下文章

CASCADE 究竟如何与 Django 中的多对多字段一起工作

Django 多对多字段过滤器列表

django字段查询参数及聚合函数

Django ORM:构造查询,该查询将在多对多字段的最后位置的对象中的字段上查找匹配项

Django 管理中的精确字段搜索

django怎么模糊匹配json中的数据?