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
集成。
更新 3:WorkFilter
如果搜索准确则返回空
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 中的多对多字段一起工作