我正在使用 djangorestframework 和 mysql 数据库。 我有一个基于搜索查询参数返回列表的视图。 我使用rest_frameworks.filters SearchFilter 进行基于搜索的过滤。 这是我的看法:
from rest_framework import filters
from rest_framework.generics import ListAPIView
...
class FooListView(ListAPIView):
serializer_class = SymbolSerializer
queryset = Symbol.objects.all()
filter_backends = [filters.SearchFilter]
search_fields = ['field_A', 'field_B', 'field_C']
要调用的示例 URL 是:
http://localhost:8000/symbols/symbols/?search=bird
现在一切正常,但我需要一个filters.SearchFilter不支持的功能。 我希望我的搜索按 search_fields 的优先级排序。
例如这里有两条记录:
foo1:{“field_A”:“任何”,“field_B”:“许多”,“field_C”:“酒吧”,“id”:3}
foo2:{“field_A”:“许多”,“field_B”:“任何”,“field_C”:“酒吧”,“id”:4}
现在,当我使用 search='many' 参数进行搜索时,我希望视图返回一个列表,其中 foo2 记录高于 foo1 (像这样 [foo2, foo1] ),因为我希望搜索的优先级是field_A 分数,但它只是返回一个按 id ([foo1, foo2]) 排序的列表。
有什么帮助吗?
我刚刚遇到了同样的问题。
我的解决方案是利用来自
此响应的灵感稍微调整 DRF
filters.SearchFilter
的搜索逻辑,并最终得到以下自定义过滤器类:
class PriorizedSearchFilter(filters.SearchFilter):
def filter_queryset(self, request, queryset, view):
"""Override to return priorized results."""
# Copy paste from DRF
search_fields = getattr(view, 'search_fields', None)
search_terms = self.get_search_terms(request)
if not search_fields or not search_terms:
return queryset
orm_lookups = [
self.construct_search(six.text_type(search_field))
for search_field in search_fields
]
base = queryset
conditions = []
# Will contain a queryset for each search term
querysets = list()
for search_term in search_terms:
queries = [
models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups
]
# Conditions for annotated priority value. Priority == inverse of the search field's index.
# Example:
# search_fields = ['field_A', 'field_B', 'field_C']
# Priorities are field_A = 2, field_B = 1, field_C = 0
when_conditions = [models.When(queries[i], then=models.Value(len(queries) - i - 1)) for i in range(len(queries))]
# Generate queryset result for this search term, with annotated priority
querysets.append(
queryset.filter(reduce(operator.or_, queries))
.annotate(priority=models.Case(
*when_conditions,
output_field=models.IntegerField(),
default=models.Value(-1)) # Lowest possible priority
)
)
# Intersect all querysets and order by highest priority
queryset = reduce(operator.and_, querysets).order_by('-priority')
# Copy paste from DRF
if self.must_call_distinct(queryset, search_fields):
# Filtering against a many-to-many field requires us to
# call queryset.distinct() in order to avoid duplicate items
# in the resulting queryset.
# We try to avoid this if possible, for performance reasons.
queryset = distinct(queryset, base)
return queryset
使用
filter_backends = [PrioritizedSearchFilter]
即可完成。
Maximiliano 的答案几乎是完美的,但是有一个错误,其中有重复的条目。如果搜索词与多个字段中的对象匹配,则
queryset
的distinct
会将具有不同优先级注释的同一对象视为不同的对象。因此,搜索过滤器将返回重复的条目。
从 Django 3.2 开始,解决方案就像将
annotate
替换为 alias
一样简单。来自它的文档,
与
相同,但不是在 QuerySet 中注释对象,而是保存表达式以供以后与其他 QuerySet 方法重用。 (...)annotate()
可与alias()
、annotate()
、exclude()
、filter()
和order_by()
结合使用。update()
换句话说,
alias
解决了问题,因为它可以与order_by
一起使用,但被distinct
忽略。