django中如何进行优先搜索?

问题描述 投票:0回答:2

我正在使用 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]) 排序的列表。

有什么帮助吗?

mysql django django-rest-framework django-filter django-rest-framework-filters
2个回答
2
投票

我刚刚遇到了同样的问题。

我的解决方案是利用来自

此响应
的灵感稍微调整 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]
即可完成。


0
投票

Maximiliano 的答案几乎是完美的,但是有一个错误,其中有重复的条目。如果搜索词与多个字段中的对象匹配,则

queryset
distinct
会将具有不同优先级注释的同一对象视为不同的对象。因此,搜索过滤器将返回重复的条目。

从 Django 3.2 开始,解决方案就像将

annotate
替换为
alias
一样简单。来自它的文档

annotate()
相同,但不是在 QuerySet 中注释对象,而是保存表达式以供以后与其他 QuerySet 方法重用。 (...)
alias()
可与
annotate()
exclude()
filter()
order_by()
update()
结合使用。

换句话说,

alias
解决了问题,因为它可以与
order_by
一起使用,但被
distinct
忽略。

© www.soinside.com 2019 - 2024. All rights reserved.