首页 > 解决方案 > 如何在Django中使用关键字搜索后显示的对象结果排序

问题描述

实际上我有一个工作代码,但我面临的问题是如何根据多个规则对查询集的结果进行排序。这是我的 models.py :

class Node(MPTTModel):
    parent              = TreeForeignKey('self', on_delete=models.CASCADE, blank=True, null=True, related_name='children')
    name                = models.TextField(blank=True, null=True)`
    viewed_by           = models.ManyToManyField(CustomUser, related_name='viewed_by',  blank=True)
    bookmarked_by       = models.ManyToManyField(CustomUser, related_name='bookmarked_by', blank=True)
    thumbs_up           = models.ManyToManyField(CustomUser, related_name='thumbs_up', blank=True)

在我的views.py中,我设法查询了数据库并根据所有匹配的单词显示了所有结果,但这里缺少的一点是我只设法按书签数量对结果进行排序。对于即:我有这两个对象:

Object 1 : name = How to use django forms ?
Object 2 : name = Using django forms for searching the database.

对象 1 被 20 个用户添加书签,对象 2 被 10 个用户添加书签,我在搜索栏中输入:使用 django 表单数据库 结果我有第一个对象作为列表中显示的第一个答案,即使第二个有与搜索的关键字匹配更多。所以我在这里要做的是首先根据匹配关键字的数量对结果进行排序,然后按书签数量对其进行排序。到目前为止,这是我的观点:

search_text_imported = request.session['import_search_text']
if search_text_imported != '':
    result_list = []
    get_result_list = [x for x in search_text_imported.split() if len(x) > 2]
    for keyword in get_result_list:
        tree_list = Node.objects.filter((Q(name__icontains=keyword) | Q(Tags__icontains=keyword)), tree_type='root', published=True ).annotate(num_bookmarks=Count('bookmarked_by')).order_by('-num_bookmarks')
        result_list += list(tree_list)
        result = list(OrderedDict.fromkeys(result_list))
    context = {
    'tree_result': result,
    }

如果这里有什么遗漏,请告诉我,任何帮助将不胜感激。

标签: djangodjango-modelssearchqueryset

解决方案


您遇到的问题是由于您通过将查询结果连接在一起来创建结果列表,如果您将查询结果连接在一起,则对它们进行排序并不重要。您可以将代码更改为仅执行单个排序查询,方法是首先创建 Q 过滤器,然后将其传递给单个查询

filter = Q()
for keyword in keywords:
    filter |= Q(name__icontains=keyword)
    filter |= Q(Tags__icontains=keyword
result = Node.objects.filter(
    filter,
    tree_type='root',
    published=True
).annotate(
    num_bookmarks=Count('bookmarked_by')
).order_by('-num_bookmarks')

按匹配的关键字数量排序是一个难题。一个潜在的解决方案是根据是否匹配,为每个关键字用 1 或 0 注释每个节点,然后将它们全部相加

from functools import reduce
from operator import add

from django.db.models import Case, When, Value, F

cases = {}
for i, keyword in enumerate(keywords):
    cases[f'keyword_match_{i}'] = Case(
        When(name__icontains=keyword, then=1),
        default=Value(0),
        output_field=models.IntegerField(),
    )

Node.objects.annotate(**cases).annotate(
    matches=reduce(add, (F(name) for name in cases))
).order_by('-matches')

全部一起

filter = Q()
cases = {}
for i, keyword in enumerate(keywords):
    filter |= Q(name__icontains=keyword)
    filter |= Q(Tags__icontains=keyword
    # Case is basically an "if" statement
    # If the condition matches then we set the annotated value to 1
    cases[f'keyword_match_{i}'] = Case(
        When(name__icontains=keyword, then=1),
        default=Value(0),
        output_field=models.IntegerField(),
    )
result = Node.objects.filter(
    filter,
    tree_type='root',
    published=True
).annotate(
    **cases
).annotate(
    num_bookmarks=Count('bookmarked_by'),
    keywords_matched=reduce(add, (F(name) for name in cases))
).order_by('-keywords_matched', '-num_bookmarks')

推荐阅读