搜索 QuerySets

Wagtail 搜索基于 Django 的 QuerySet API 构建。如果模型和要过滤的字段已添加到搜索索引中,您应该能够搜索任何 Django 查询集。

搜索 Pages

Wagtail 提供了搜索页面的快捷方式: .search() QuerySet 方法。您可以在任何 PageQuerySet 上调用此方法。例如:

# Search future EventPages
>>> from wagtail.models import EventPage
>>> EventPage.objects.filter(date__gt=timezone.now()).search("Hello world!")

PageQuerySet 的所有其他方法都可以与 search() 一起使用。例如:

# 搜索事件索引下的所有实时事件页面
>>> EventPage.objects.live().descendant_of(events_index).search("Event")
[<EventPage: Event 1>, <EventPage: Event 2>]

注意:
search() 方法会将您的 QuerySet 转换为 Wagtail 的 SearchResults 类之一的实例(取决于后端)。这意味着您必须在调用 search() 之前执行过滤。
{class="text-success"}

search 方法的标准行为是只返回完整单词的匹配;例如,搜索“hel”将不会返回包含单词“hello”的结果。例外情况是回调数据库搜索后端,它在数据库没有可用的全文搜索扩展,并且没有指定备选后端时使用。这将执行基本的子字符串匹配,并将返回包含忽略所有单词边界的搜索项的结果。

自动完成搜索

Wagtail 提供了一个单独的方法,对特定的自动完成字段执行部分匹配。这主要用于在用户输入查询时实时向他们推荐页面——不建议用于普通搜索,因为自动补全往往会在搜索的特定术语之外添加不需要的结果。

>>> EventPage.objects.live().autocomplete("Eve")
[<EventPage: Event 1>, <EventPage: Event 2>]

搜索图像、文档和自定义模型

Wagtail 的文档和图像模型在其 QuerySet 上提供了 search 方法,就像页面一样:

>>> from wagtail.images.models import Image

>>> Image.objects.filter(uploaded_by_user=user).search("Hello")
[<Image: Hello>, <Image: Hello world!>]

自定义模型 可以直接在搜索后端使用 search 方法进行搜索:

>>> from myapp.models import Book
>>> from wagtail.search.backends import get_search_backend

# Search books
>>> s = get_search_backend()
>>> s.search("Great", Book)
[<Book: Great Expectations>, <Book: The Great Gatsby>]

您还可以将 QuerySet 传递到 search 方法中,该方法允许您向搜索结果添加过滤器:

>>> from myapp.models import Book
>>> from wagtail.search.backends import get_search_backend

# Search books
>>> s = get_search_backend()
>>> s.search("Great", Book.objects.filter(published_date__year__lt=1900))
[<Book: Great Expectations>]

默认情况下, Wagtail 将搜索已使用 index.SearchField 建立索引的所有字段。

可以使用 fields 关键字参数将其限制为特定的字段集:

# 仅搜索标题字段
>>> EventPage.objects.search("Event", fields=["title"])
[<EventPage: Event 1>, <EventPage: Event 2>]

Wagtail 支持分面搜索,这是一种基于分类字段(例如类别或页面类型)的过滤。

.facet(field_name) 方法返回 OrderedDict 。键是指定字段已引用的相关对象的 ID,值是找到每个 ID 的引用数。结果按参考文献数量降序排列。

例如,要在搜索结果中查找最常见的页面类型:

>>> Page.objects.search("Test").facet("content_type_id")

# 注意:键对应于ContentType对象的ID;值是
# 该类型返回的页数
OrderedDict([
    ('2', 4),  # 4 个页面的 content_type_id == 2
    ('1', 2),  # 2 个页面的 content_type_id == 1
])

改变搜索行为

搜索运算符

搜索运算符指定当用户输入多个搜索词时搜索应如何表现。有两个可能的值:

  • “or” - 结果必须至少匹配一个术语(Elasticsearch 的默认值)
  • “and” - 结果必须匹配所有术语(数据库搜索的默认值)

两种运营商都有优点和缺点。“或”运算符将返回更多结果,但可能包含许多不相关的结果。“and”运算符仅返回包含所有搜索词的结果,但要求用户更精确地查询。

我们建议在按相关性排序时使用“或”运算符,在按其他内容排序时使用“and”运算符(注意:数据库后端当前不支持按相关性排序)。

以下是使用 operator 关键字参数的示例:

# 数据库包含一个  "Thing"  型号,其中包含以下项目:
 - 你好世界
 - 你好
 - 世界


# 使用  "or"  运算符搜索
>>> s = get_search_backend()
>>> s.search("Hello world", Things, operator="or")

# 返回的所有记录都包含  "hello"  或  "world"
[<Thing: Hello World>, <Thing: Hello>, <Thing: World>]


# 使用  "and"  运算符搜索
>>> s = get_search_backend()
>>> s.search("Hello world", Things, operator="and")

# 仅返回  "hello world" ,因为这是唯一包含这两个术语的项目
[<Thing: Hello world>]

对于页面、图像和文档模型,QuerySet 的 search 方法也支持 operator 关键字参数:

>>> Page.objects.search("Hello world", operator="or")

# All pages containing either "hello" or "world" are returned
[<Page: Hello World>, <Page: Hello>, <Page: World>]

短语搜索

短语搜索用于查找整个句子或短语而不是单个术语。这些术语必须一起出现并以相同的顺序出现。

For example:

>>> from wagtail.search.query import Phrase

>>> Page.objects.search(Phrase("Hello world"))
[<Page: Hello World>]

>>> Page.objects.search(Phrase("World hello"))
[<Page: World Hello day>]

如果您希望使用双引号语法实现短语查询,请参阅 查询字符串解析

模糊匹配

模糊匹配将返回包含与搜索词相似的词的文档(由 Levenshtein edit distance 测量)。

对于三到五个字母的术语最多允许进行一次编辑(换位、插入或删除字符),对于较长的术语允许进行两次编辑,较短的术语必须完全匹配。

For example:

>>> from wagtail.search.query import Fuzzy

>>> Page.objects.search(Fuzzy("Hallo"))
[<Page: Hello World>]

仅 Elasticsearch 搜索后端支持模糊匹配。

复杂的搜索查询

通过使用搜索查询类, Wagtail 还支持将搜索查询构建为 Python 对象,该对象可以由其他搜索查询包装并与其他搜索查询组合。提供以下课程:

PlainText(query_string, operator=None, boost=1.0)

此类包装了一串单独的术语。这与没有查询类的搜索相同。

它需要一个查询字符串、运算符和提升。

For example:

>>> from wagtail.search.query import PlainText
>>> Page.objects.search(PlainText("Hello world"))

# 可以组合多个纯文本查询。此示例将匹配  "hello world"  和  "Hello earth"
>>> Page.objects.search(PlainText("Hello") & (PlainText("world") | PlainText("earth")))

Phrase(query_string)

此类包装一个包含短语的字符串。请参阅上一节了解其工作原理。

For example:

# 此示例将匹配短语  "hello world"  和  "Hello earth"
>>> Page.objects.search(Phrase("Hello world") | Phrase("Hello earth"))

Boost(query, boost)

该类提高了另一个查询的分数。

For example:

>>> from wagtail.search.query import PlainText, Boost

# 此示例将匹配短语  "hello world"  和  "Hello earth" ,但  "hello world"  的匹配排名更高
>>> Page.objects.search(Boost(Phrase("Hello world"), 10.0) | Phrase("Hello earth"))

请注意,PostgreSQL 或数据库搜索后端不支持此功能。

查询字符串解析

前面的部分展示了如何手动构建短语搜索查询,但是很多搜索引擎(包括 Wagtail admin,尝试一下!)支持通过用双引号括起短语来编写短语查询。除了短语之外,您可能还希望允许用户使用冒号语法 ( hello world published:yes ) 将过滤器添加到查询中。

这两个功能可以使用 parse_query_string 实用函数来实现。该函数接受用户输入的查询字符串并返回查询对象和过滤器 QueryDict:

示例:

>>> from wagtail.search.utils import parse_query_string
>>> filters, query = parse_query_string('my query string "this is a phrase" this_is_a:filter key:value1 key:value2', operator='and')

# Alternatively..
# filters, query = parse_query_string("my query string 'this is a phrase' this_is_a:filter key:test1 key:test2", operator='and')

>>> filters
<QueryDict: {'this_is_a': ['filter'], 'key': ['value1', 'value2']}>>

# Get a list of values associated to a particular key using getlist method
>>> filters.getlist('key')
['value1', 'value2']

# Get a dict representation using dict method
# NOTE: dict method will reduce multiple values for a particular key to the last assigned value
>>> filters.dict()
{
    'this_is_a': 'filter',
    'key': 'value2'
}

>>> query
And([
    PlainText("my query string", operator='and'),
    Phrase("this is a phrase"),
])

以下是如何在搜索视图中使用此函数的示例:

from wagtail.search.utils import parse_query_string

def search(request):
    query_string = request.GET['query']

    # 解析查询
    filters, query = parse_query_string(query_string, operator='and')

    # 已发布的过滤器
    # 接受 `published:yes` 或 `published:no` 并相应地过滤页面的过滤器示例
    published_filter = filters.get('published')
    published_filter = published_filter and published_filter.lower()
    if published_filter in ['yes', 'true']:
        pages = pages.filter(live=True)
    elif published_filter in ['no', 'false']:
        pages = pages.filter(live=False)

    # Search
    pages = pages.search(query)

    return render(request, 'search_results.html', {'pages': pages})

Custom ordering

默认情况下,搜索结果按相关性排序(如果后端支持)。为了保留 QuerySet 的现有排序,需要在 search() 方法上将 order_by_relevance 关键字参数设置为 False

示例:

# 获取按日期排序的事件列表
>>> EventPage.objects.order_by('date').search("Event", order_by_relevance=False)

# 按日期排序的事件
[<EventPage: Easter>, <EventPage: Halloween>, <EventPage: Christmas>]

用评分注释结果

对于每个匹配的结果,Elasticsearch 都会计算一个“评分”,该数字表示结果与用户查询的相关程度。结果通常根据分数排序。

在某些情况下,访问分数很有用(例如以编程方式组合不同模型的两个查询)。您可以通过调用 SearchQuerySet 上的 .annotate_score(field) 方法将分数添加到每个结果中。

示例:

>>> events = EventPage.objects.search("Event").annotate_score("_score")
>>> for event in events:
...    print(event.title, event._score)
...
("Easter", 2.5),
("Halloween", 1.7),
("Christmas", 1.5),

请注意,分数本身是任意的,它仅适用于比较同一查询的结果。

页面搜索视图示例

下面是一个 Django 视图示例,可用于向您的站点添加“搜索”页面:

# views.py

from django.shortcuts import render

from wagtail.models import Page
from wagtail.contrib.search_promotions.models import Query


def search(request):
    # Search
    search_query = request.GET.get('query', None)
    if search_query:
        search_results = Page.objects.live().search(search_query)

        # 记录查询,以便 Wagtail 可以建议升级结果
        Query.get(search_query).add_hit()
    else:
        search_results = Page.objects.none()

    # 渲染模板
    return render(request, 'search_results.html', {
        'search_query': search_query,
        'search_results': search_results,
    })

这是一个与之配套的模板:

{% extends "base.html" %}
{% load wagtailcore_tags %}

{% block title %}Search{% endblock %}

{% block content %}
    <form action="{% url 'search' %}" method="get">
        <input type="text" name="query" value="{{ search_query }}">
        <input type="submit" value="Search">
    </form>

    {% if search_results %}
        <ul>
            {% for result in search_results %}
                <li>
                    <h4><a href="{% pageurl result %}">{{ result }}</a></h4>
                    {% if result.search_description %}
                        {{ result.search_description|safe }}
                    {% endif %}
                </li>
            {% endfor %}
        </ul>
    {% elif search_query %}
        No results found
    {% else %}
        Please type something into the search box
    {% endif %}
{% endblock %}

“推广搜索结果”允许编辑者将相关内容明确链接到搜索词,因此结果页面除了搜索引擎的结果之外还可以包含精选内容。

此功能由 [search_promotions] contrib 模块提供。