搜索¶
查询结果集¶
Wagtail 搜索基于 Django 的 QuerySet API 。在开发过程中可以对加入查询索引的字段采用类似于 Django 模型及字段过滤的操作。
搜索页面¶
Wagtail 提供页面搜索的快捷方法:.search()
和 QuerySet
。这两个方法可以任何 PageQuerySet
对象上使用。例如:
# Search future EventPages 在未来事件页中搜索
>>> from wagtail.core.models import EventPage
>>> EventPage.objects.filter(date__gt=timezone.now()).search("Hello world!")
PageQuerySet
其它方法也可以结合 search()
使用。例如:
# Search all live EventPages that are under the events index 在事件索引页下属页面中搜索
>>> EventPage.objects.live().descendant_of(events_index).search("Event")
[<EventPage: Event 1>, <EventPage: Event 2>]
注解
search()
方法将原来 QuerySet
转换成 Wagtail 的 SearchResults
类实例 (依赖于底层实现平台)。这意味着过滤操作必须在 search()
执行之前。
搜索图片、文档及定制的模型¶
Wagtail 的文档和图片各自模型 QuerySets 中定义了 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>]
调用 search
方法时可以传递一个 QuerySet 参数对搜索结果进行过滤:
>>> 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>]
只搜索特定字段¶
缺省情况下,搜索会匹配 index.SearchField
中定义的所有字段。
如果只想对其中个别字段进行搜索,可使用 fields
参数定义所要搜索的字段集合:
# Search just the title field
>>> EventPage.objects.search("Event", fields=["title"])
[<EventPage: Event 1>, <EventPage: Event 2>]
分面检索(Faceted search)¶
Wagtail 支持分面检索,分面检索基于可分类字段(如分类或页面类型)。
.facet(field_name)
方法返加一个 OrderedDict
对象。键是给定字段相关联对象的 ID,值是每个 ID 引用的次数。结果按引用次数降序排序。
例如,按页面类型查看搜索结果中每种类型有多少页面:
>>> Page.objects.search("Test").facet("content_type_id")
# Note: The keys correspond to the ID of a ContentType object; the values are the
# number of pages returned for that type
OrderedDict([
('2', 4), # 4 pages have content_type_id == 2
('1', 2), # 2 pages have content_type_id == 1
])
改变搜索方式¶
查询操作符¶
查询操作符定义多个术语查找过程中的搜索方式。有两个取值:
“or”(或) - 查询结果至少匹配所提供术语中的一个 (这是 Elasticsearch 搜索的缺省方式)
“and” (与)- 查询结果要匹配所有提供术语 (这是数据库搜索的缺省方式)
这两种方式有各自的优缺点。 “or” 操作会返回尽可能多的集合或忽略了所有术语间的相关性。而 “and” 操作只返回严格匹配所有术语的记录,会此要求太严格而返回过少的记录集。
我们推荐在需要按相关性进行排序时使用 “or” 操作符。而按其它形式排序时使用 “and” 操作符(注意:底层平台为数据库时不支持按相关性进行排序)。
下面是一个使用 operator
关键字参数的例子:
# The database contains a "Thing" model with the following items:
# - Hello world
# - Hello
# - World
# Search with the "or" operator
>>> s = get_search_backend()
>>> s.search("Hello world", Things, operator="or")
# All records returned as they all contain either "hello" or "world"
[<Thing: Hello World>, <Thing: Hello>, <Thing: World>]
# Search with the "and" operator
>>> s = get_search_backend()
>>> s.search("Hello world", Things, operator="and")
# Only "hello world" returned as that's the only item that contains both terms
[<Thing: Hello world>]
对于页面、图片和文档模型,在 QuerySet’s 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>]
词组搜索(Phrase)¶
词组或短语搜索是将查找的术语集当作词组来查找。这样搜索出的结果应符合这些术语出现的顺序。
例如:
>>> 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>]
要了解更多使用术语双引号语法进行词组搜索的方法,请参考 查询字符串的解析。
搜索中复杂查询¶
通过使用查询类,Wagtail 还支持构造复杂 Python 对象查询方法。参考下面例子:
PlainText(query_string, operator=None, boost=1.0)
这个类将查询条件字符串分隔成几个术语。这是没使用查询类的缺省参数形式。
查询类包含查询字符串、分隔符及提升权重值(boost).
例如:
>>> from wagtail.search.query import PlainText
>>> Page.objects.search(PlainText("Hello world"))
# Multiple plain text queries can be combined. This example will match both "hello world" and "Hello earth"
>>> Page.objects.search(PlainText("Hello") & (PlainText("world") | PlainText("earth")))
Phrase(query_string)
这个类将字符串转换成词组。参考上一节的描述。
例如:
# This example will match both the phrases "hello world" and "Hello earth"
>>> Page.objects.search(Phrase("Hello world") | Phrase("Hello earth"))
Boost(query, boost)
这个类提供查询的权重值。
例如:
>>> from wagtail.search.query import PlainText, Boost
# This example will match both the phrases "hello world" and "Hello earth" but matches for "hello world" will be ranked higher
>>> Page.objects.search(Boost(Phrase("Hello world"), 10.0) | Phrase("Hello earth"))
注意,底层平台使用 PostgreSQL 或数据库时不支持。
查询字符串的解析¶
前面章节展示了程度中构造词组查询的方式,许多搜索引擎 (包括 Wagtail 台管理界面) 支持双引号让最终用户定义短语词组的搜索方式。 除了词组搜索的这种形式之外,还允许用户使用冒号形式的增加过滤条件。
这两个查询字符串的解析方法在实现时可通过调用 parse_query_string
工具函数实现,函数使用用户输入字符串做为参数,返回查询对象和过滤器的字典:
例如:
>>> from wagtail.search.utils import parse_query_string
>>> filters, query = parse_query_string('my query string "this is a phrase" this-is-a:filter', operator='and')
>>> filters
{
'this-is-a': 'filter',
}
>>> 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']
# Parse query
filters, query = parse_query_string(query_string, operator='and')
# Published filter
# An example filter that accepts either `published:yes` or `published:no` and filters the pages accordingly
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})
设置顺序¶
缺省情况下,如果底层平台支持查询结果将按相关性的顺序进行排序。 如果不使用这种排序方法,需要将 search()
方法的 order_by_relevance
关键字参数设为 False
。
例如:
# Get a list of events ordered by date
>>> EventPage.objects.order_by('date').search("Event", order_by_relevance=False)
# Events ordered by date
[<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),
("Haloween", 1.7),
("Christmas", 1.5),
注意,分值是在查询过程中判定出来的,只有相同的查询,分值的大小才有意义。
页面搜索视图示例¶
下面是一个 Django 视图可用来在网站上增加”搜索”页面:
# views.py
from django.shortcuts import render
from wagtail.core.models import Page
from wagtail.search.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)
# Log the query so Wagtail can suggest promoted results
Query.get(search_query).add_hit()
else:
search_results = Page.objects.none()
# Render template
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
模块说明。