|
|
|
|
@ -1,7 +1,7 @@
|
|
|
|
|
# encoding: utf-8
|
|
|
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
|
|
|
|
|
|
|
|
# ZYY: 导入必要的模块和函数
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
@ -34,12 +34,14 @@ from whoosh.qparser import QueryParser
|
|
|
|
|
from whoosh.searching import ResultsPage
|
|
|
|
|
from whoosh.writing import AsyncWriter
|
|
|
|
|
|
|
|
|
|
# ZYY: 尝试导入whoosh模块,如果失败则抛出异常
|
|
|
|
|
try:
|
|
|
|
|
import whoosh
|
|
|
|
|
except ImportError:
|
|
|
|
|
raise MissingDependency(
|
|
|
|
|
"The 'whoosh' backend requires the installation of 'Whoosh'. Please refer to the documentation.")
|
|
|
|
|
|
|
|
|
|
# ZYY: 检查whoosh版本是否满足要求
|
|
|
|
|
# Handle minimum requirement.
|
|
|
|
|
if not hasattr(whoosh, '__version__') or whoosh.__version__ < (2, 5, 0):
|
|
|
|
|
raise MissingDependency(
|
|
|
|
|
@ -52,16 +54,20 @@ DATETIME_REGEX = re.compile(
|
|
|
|
|
LOCALS = threading.local()
|
|
|
|
|
LOCALS.RAM_STORE = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ZYY: 自定义的HtmlFormatter类,用于在whoosh中统一结果格式
|
|
|
|
|
class WhooshHtmlFormatter(HtmlFormatter):
|
|
|
|
|
"""
|
|
|
|
|
This is a HtmlFormatter simpler than the whoosh.HtmlFormatter.
|
|
|
|
|
We use it to have consistent results across backends. Specifically,
|
|
|
|
|
Solr, Xapian and Elasticsearch are using this formatting.
|
|
|
|
|
"""
|
|
|
|
|
"""
|
|
|
|
|
ZYY: 这是一个比whoosh.HtmlFormatter更简单的HtmlFormatter。
|
|
|
|
|
我们使用它来确保不同后端(如Solr, Xapian和Elasticsearch)之间的结果一致性。
|
|
|
|
|
"""
|
|
|
|
|
template = '<%(tag)s>%(t)s</%(tag)s>'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ZYY: Whoosh搜索后端类
|
|
|
|
|
class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
# Word reserved by Whoosh for special use.
|
|
|
|
|
RESERVED_WORDS = (
|
|
|
|
|
@ -70,7 +76,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
'OR',
|
|
|
|
|
'TO',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# ZYY: Whoosh保留的特殊用途字符
|
|
|
|
|
# Characters reserved by Whoosh for special use.
|
|
|
|
|
# The '\\' must come first, so as not to overwrite the other slash
|
|
|
|
|
# replacements.
|
|
|
|
|
@ -80,6 +86,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def __init__(self, connection_alias, **connection_options):
|
|
|
|
|
# ZYY: 初始化Whoosh搜索后端
|
|
|
|
|
super(
|
|
|
|
|
WhooshSearchBackend,
|
|
|
|
|
self).__init__(
|
|
|
|
|
@ -107,9 +114,13 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
"""
|
|
|
|
|
Defers loading until needed.
|
|
|
|
|
"""
|
|
|
|
|
"""
|
|
|
|
|
ZYY: 延迟加载直到需要时才加载
|
|
|
|
|
"""
|
|
|
|
|
from haystack import connections
|
|
|
|
|
new_index = False
|
|
|
|
|
|
|
|
|
|
# ZYY: 确保索引存在
|
|
|
|
|
# Make sure the index is there.
|
|
|
|
|
if self.use_file_storage and not os.path.exists(self.path):
|
|
|
|
|
os.makedirs(self.path)
|
|
|
|
|
@ -144,12 +155,14 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
|
|
|
|
|
self.setup_complete = True
|
|
|
|
|
|
|
|
|
|
# ZYY: 构建索引模式
|
|
|
|
|
def build_schema(self, fields):
|
|
|
|
|
schema_fields = {
|
|
|
|
|
ID: WHOOSH_ID(stored=True, unique=True),
|
|
|
|
|
DJANGO_CT: WHOOSH_ID(stored=True),
|
|
|
|
|
DJANGO_ID: WHOOSH_ID(stored=True),
|
|
|
|
|
}
|
|
|
|
|
# ZYY: 获取Haystack中硬编码的键数量,用于后续的错误处理
|
|
|
|
|
# Grab the number of keys that are hard-coded into Haystack.
|
|
|
|
|
# We'll use this to (possibly) fail slightly more gracefully later.
|
|
|
|
|
initial_key_count = len(schema_fields)
|
|
|
|
|
@ -173,6 +186,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
schema_fields[field_class.index_fieldname] = NUMERIC(
|
|
|
|
|
stored=field_class.stored, numtype=float, field_boost=field_class.boost)
|
|
|
|
|
elif field_class.field_type == 'boolean':
|
|
|
|
|
# ZYY: BOOLEAN字段不支持field_boost
|
|
|
|
|
# Field boost isn't supported on BOOLEAN as of 1.8.2.
|
|
|
|
|
schema_fields[field_class.index_fieldname] = BOOLEAN(
|
|
|
|
|
stored=field_class.stored)
|
|
|
|
|
@ -184,13 +198,14 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
stored=field_class.stored,
|
|
|
|
|
field_boost=field_class.boost)
|
|
|
|
|
else:
|
|
|
|
|
# ZYY: 使用中文分析器
|
|
|
|
|
# schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True)
|
|
|
|
|
schema_fields[field_class.index_fieldname] = TEXT(
|
|
|
|
|
stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True)
|
|
|
|
|
if field_class.document is True:
|
|
|
|
|
content_field_name = field_class.index_fieldname
|
|
|
|
|
schema_fields[field_class.index_fieldname].spelling = True
|
|
|
|
|
|
|
|
|
|
# ZYY: 如果没有找到任何字段,则抛出错误
|
|
|
|
|
# Fail more gracefully than relying on the backend to die if no fields
|
|
|
|
|
# are found.
|
|
|
|
|
if len(schema_fields) <= initial_key_count:
|
|
|
|
|
@ -198,7 +213,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
"No fields were found in any search_indexes. Please correct this before attempting to search.")
|
|
|
|
|
|
|
|
|
|
return (content_field_name, Schema(**schema_fields))
|
|
|
|
|
|
|
|
|
|
# ZYY: 更新索引
|
|
|
|
|
def update(self, index, iterable, commit=True):
|
|
|
|
|
if not self.setup_complete:
|
|
|
|
|
self.setup()
|
|
|
|
|
@ -212,11 +227,13 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
except SkipDocument:
|
|
|
|
|
self.log.debug(u"Indexing for object `%s` skipped", obj)
|
|
|
|
|
else:
|
|
|
|
|
# ZYY: 确保所有数据都是unicode格式
|
|
|
|
|
# Really make sure it's unicode, because Whoosh won't have it any
|
|
|
|
|
# other way.
|
|
|
|
|
for key in doc:
|
|
|
|
|
doc[key] = self._from_python(doc[key])
|
|
|
|
|
|
|
|
|
|
# ZYY: Whoosh 2.5.0+不支持文档boost
|
|
|
|
|
# Document boosts aren't supported in Whoosh 2.5.0+.
|
|
|
|
|
if 'boost' in doc:
|
|
|
|
|
del doc['boost']
|
|
|
|
|
@ -227,6 +244,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
if not self.silently_fail:
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
# ZYY: 记录错误信息
|
|
|
|
|
# We'll log the object identifier but won't include the actual object
|
|
|
|
|
# to avoid the possibility of that generating encoding errors while
|
|
|
|
|
# processing the log message:
|
|
|
|
|
@ -240,10 +258,12 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
"object": get_identifier(obj)}})
|
|
|
|
|
|
|
|
|
|
if len(iterable) > 0:
|
|
|
|
|
# ZYY: 无论如何都提交更改,以避免锁定问题
|
|
|
|
|
# For now, commit no matter what, as we run into locking issues
|
|
|
|
|
# otherwise.
|
|
|
|
|
writer.commit()
|
|
|
|
|
|
|
|
|
|
# ZYY: 从索引中删除文档
|
|
|
|
|
def remove(self, obj_or_string, commit=True):
|
|
|
|
|
if not self.setup_complete:
|
|
|
|
|
self.setup()
|
|
|
|
|
@ -266,6 +286,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
e,
|
|
|
|
|
exc_info=True)
|
|
|
|
|
|
|
|
|
|
# ZYY: 清空索引
|
|
|
|
|
def clear(self, models=None, commit=True):
|
|
|
|
|
if not self.setup_complete:
|
|
|
|
|
self.setup()
|
|
|
|
|
@ -302,8 +323,9 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
else:
|
|
|
|
|
self.log.error(
|
|
|
|
|
"Failed to clear Whoosh index: %s", e, exc_info=True)
|
|
|
|
|
|
|
|
|
|
# ZYY: 删除索引文件
|
|
|
|
|
def delete_index(self):
|
|
|
|
|
# ZYY: 根据Whoosh邮件列表的建议,如果要从索引中删除所有内容,更高效的方法是直接删除索引文件
|
|
|
|
|
# Per the Whoosh mailing list, if wiping out everything from the index,
|
|
|
|
|
# it's much more efficient to simply delete the index files.
|
|
|
|
|
if self.use_file_storage and os.path.exists(self.path):
|
|
|
|
|
@ -311,9 +333,11 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
elif not self.use_file_storage:
|
|
|
|
|
self.storage.clean()
|
|
|
|
|
|
|
|
|
|
# ZYY: 重新创建索引
|
|
|
|
|
# Recreate everything.
|
|
|
|
|
self.setup()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ZYY: 优化索引
|
|
|
|
|
def optimize(self):
|
|
|
|
|
if not self.setup_complete:
|
|
|
|
|
self.setup()
|
|
|
|
|
@ -321,12 +345,15 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
self.index = self.index.refresh()
|
|
|
|
|
self.index.optimize()
|
|
|
|
|
|
|
|
|
|
# ZYY: 计算分页
|
|
|
|
|
def calculate_page(self, start_offset=0, end_offset=None):
|
|
|
|
|
# Prevent against Whoosh throwing an error. Requires an end_offset
|
|
|
|
|
# greater than 0.
|
|
|
|
|
# ZYY: 防止Whoosh抛出错误,要求end_offset大于0
|
|
|
|
|
if end_offset is not None and end_offset <= 0:
|
|
|
|
|
end_offset = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ZYY: 确定页码
|
|
|
|
|
# Determine the page.
|
|
|
|
|
page_num = 0
|
|
|
|
|
|
|
|
|
|
@ -341,6 +368,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
if page_length and page_length > 0:
|
|
|
|
|
page_num = int(start_offset / page_length)
|
|
|
|
|
|
|
|
|
|
# ZYY: Whoosh使用1-based的页码
|
|
|
|
|
# Increment because Whoosh uses 1-based page numbers.
|
|
|
|
|
page_num += 1
|
|
|
|
|
return page_num, page_length
|
|
|
|
|
@ -368,7 +396,8 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
**kwargs):
|
|
|
|
|
if not self.setup_complete:
|
|
|
|
|
self.setup()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ZYY: 空查询应该返回无结果
|
|
|
|
|
# A zero length query should return no results.
|
|
|
|
|
if len(query_string) == 0:
|
|
|
|
|
return {
|
|
|
|
|
@ -377,7 +406,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query_string = force_str(query_string)
|
|
|
|
|
|
|
|
|
|
# ZYY: 单字符查询(非通配符)会被停用词过滤器过滤掉,应该返回无结果
|
|
|
|
|
# A one-character query (non-wildcard) gets nabbed by a stopwords
|
|
|
|
|
# filter and should yield zero results.
|
|
|
|
|
if len(query_string) <= 1 and query_string != u'*':
|
|
|
|
|
@ -389,6 +418,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
reverse = False
|
|
|
|
|
|
|
|
|
|
if sort_by is not None:
|
|
|
|
|
# ZYY: 确定是否需要反转结果,以及Whoosh是否能处理排序要求
|
|
|
|
|
# Determine if we need to reverse the results and if Whoosh can
|
|
|
|
|
# handle what it's being asked to sort by. Reversing is an
|
|
|
|
|
# all-or-nothing action, unfortunately.
|
|
|
|
|
@ -445,6 +475,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
if models and len(models):
|
|
|
|
|
model_choices = sorted(get_model_ct(model) for model in models)
|
|
|
|
|
elif limit_to_registered_models:
|
|
|
|
|
# ZYY: 使用窄查询限制结果到当前路由器处理的模型
|
|
|
|
|
# Using narrow queries, limit the results to only models handled
|
|
|
|
|
# with the current routers.
|
|
|
|
|
model_choices = self.build_models_list()
|
|
|
|
|
@ -461,6 +492,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
narrow_searcher = None
|
|
|
|
|
|
|
|
|
|
if narrow_queries is not None:
|
|
|
|
|
# ZYY: 潜在的高开销操作
|
|
|
|
|
# Potentially expensive? I don't see another way to do it in
|
|
|
|
|
# Whoosh...
|
|
|
|
|
narrow_searcher = self.index.searcher()
|
|
|
|
|
@ -486,6 +518,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
searcher = self.index.searcher()
|
|
|
|
|
parsed_query = self.parser.parse(query_string)
|
|
|
|
|
|
|
|
|
|
# ZYY: 处理无效/停用词查询,优雅恢复
|
|
|
|
|
# In the event of an invalid/stopworded query, recover gracefully.
|
|
|
|
|
if parsed_query is None:
|
|
|
|
|
return {
|
|
|
|
|
@ -502,6 +535,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
'reverse': reverse,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ZYY: 处理已缩小范围的查询结果
|
|
|
|
|
# Handle the case where the results have been narrowed.
|
|
|
|
|
if narrowed_results is not None:
|
|
|
|
|
search_kwargs['filter'] = narrowed_results
|
|
|
|
|
@ -521,7 +555,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
'hits': 0,
|
|
|
|
|
'spelling_suggestion': None,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ZYY: Whoosh 2.5.1如果请求的页码过高,会返回错误的页码
|
|
|
|
|
# Because as of Whoosh 2.5.1, it will return the wrong page of
|
|
|
|
|
# results if you request something too high. :(
|
|
|
|
|
if raw_page.pagenum < page_num:
|
|
|
|
|
@ -559,7 +593,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
'hits': 0,
|
|
|
|
|
'spelling_suggestion': spelling_suggestion,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ZYY: 查找类似文档
|
|
|
|
|
def more_like_this(
|
|
|
|
|
self,
|
|
|
|
|
model_instance,
|
|
|
|
|
@ -572,7 +606,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
**kwargs):
|
|
|
|
|
if not self.setup_complete:
|
|
|
|
|
self.setup()
|
|
|
|
|
|
|
|
|
|
# ZYY: 延迟模型将具有不同的类名("RealClass_Deferred_fieldname"),该类名不会在我们的注册表中
|
|
|
|
|
# Deferred models will have a different class ("RealClass_Deferred_fieldname")
|
|
|
|
|
# which won't be in our registry:
|
|
|
|
|
model_klass = model_instance._meta.concrete_model
|
|
|
|
|
@ -589,6 +623,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
if models and len(models):
|
|
|
|
|
model_choices = sorted(get_model_ct(model) for model in models)
|
|
|
|
|
elif limit_to_registered_models:
|
|
|
|
|
# ZYY: 使用窄查询限制结果到当前路由器处理的模型
|
|
|
|
|
# Using narrow queries, limit the results to only models handled
|
|
|
|
|
# with the current routers.
|
|
|
|
|
model_choices = self.build_models_list()
|
|
|
|
|
@ -608,6 +643,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
narrow_searcher = None
|
|
|
|
|
|
|
|
|
|
if narrow_queries is not None:
|
|
|
|
|
# ZYY: 潜在的高开销操作
|
|
|
|
|
# Potentially expensive? I don't see another way to do it in
|
|
|
|
|
# Whoosh...
|
|
|
|
|
narrow_searcher = self.index.searcher()
|
|
|
|
|
@ -642,10 +678,11 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
raw_results = results[0].more_like_this(
|
|
|
|
|
field_name, top=end_offset)
|
|
|
|
|
|
|
|
|
|
# ZYY: 处理已缩小范围的查询结果
|
|
|
|
|
# Handle the case where the results have been narrowed.
|
|
|
|
|
if narrowed_results is not None and hasattr(raw_results, 'filter'):
|
|
|
|
|
raw_results.filter(narrowed_results)
|
|
|
|
|
|
|
|
|
|
# ZYY: Whoosh 2.5.1如果请求的页码过高,会返回错误的页码
|
|
|
|
|
try:
|
|
|
|
|
raw_page = ResultsPage(raw_results, page_num, page_length)
|
|
|
|
|
except ValueError:
|
|
|
|
|
@ -674,7 +711,7 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
narrow_searcher.close()
|
|
|
|
|
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
# ZYY: 处理查询结果
|
|
|
|
|
def _process_results(
|
|
|
|
|
self,
|
|
|
|
|
raw_page,
|
|
|
|
|
@ -684,174 +721,153 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
result_class=None):
|
|
|
|
|
from haystack import connections
|
|
|
|
|
results = []
|
|
|
|
|
|
|
|
|
|
# ZYY: 获取命中数
|
|
|
|
|
# It's important to grab the hits first before slicing. Otherwise, this
|
|
|
|
|
# can cause pagination failures.
|
|
|
|
|
hits = len(raw_page)
|
|
|
|
|
|
|
|
|
|
if result_class is None:
|
|
|
|
|
result_class = SearchResult
|
|
|
|
|
|
|
|
|
|
facets = {}
|
|
|
|
|
spelling_suggestion = None
|
|
|
|
|
unified_index = connections[self.connection_alias].get_unified_index()
|
|
|
|
|
indexed_models = unified_index.get_indexed_models()
|
|
|
|
|
|
|
|
|
|
for doc_offset, raw_result in enumerate(raw_page):
|
|
|
|
|
score = raw_page.score(doc_offset) or 0
|
|
|
|
|
app_label, model_name = raw_result[DJANGO_CT].split('.')
|
|
|
|
|
additional_fields = {}
|
|
|
|
|
model = haystack_get_model(app_label, model_name)
|
|
|
|
|
|
|
|
|
|
if model and model in indexed_models:
|
|
|
|
|
for key, value in raw_result.items():
|
|
|
|
|
index = unified_index.get_index(model)
|
|
|
|
|
string_key = str(key)
|
|
|
|
|
|
|
|
|
|
if string_key in index.fields and hasattr(
|
|
|
|
|
index.fields[string_key], 'convert'):
|
|
|
|
|
# Special-cased due to the nature of KEYWORD fields.
|
|
|
|
|
if index.fields[string_key].is_multivalued:
|
|
|
|
|
if value is None or len(value) == 0:
|
|
|
|
|
additional_fields[string_key] = []
|
|
|
|
|
else:
|
|
|
|
|
additional_fields[string_key] = value.split(
|
|
|
|
|
',')
|
|
|
|
|
else:
|
|
|
|
|
additional_fields[string_key] = index.fields[string_key].convert(
|
|
|
|
|
value)
|
|
|
|
|
# ZYY: 如果result_class为None,则将其设置为SearchResult
|
|
|
|
|
if result_class is None:
|
|
|
|
|
result_class = SearchResult
|
|
|
|
|
facets = {}
|
|
|
|
|
spelling_suggestion = None
|
|
|
|
|
# ZYY: 获取统一索引
|
|
|
|
|
unified_index = connections[self.connection_alias].get_unified_index()
|
|
|
|
|
# ZYY: 获取已索引的模型列表
|
|
|
|
|
indexed_models = unified_index.get_indexed_models()
|
|
|
|
|
for doc_offset, raw_result in enumerate(raw_page):
|
|
|
|
|
# ZYY: 获取文档得分,如果没有则为0
|
|
|
|
|
score = raw_page.score(doc_offset) or 0
|
|
|
|
|
app_label, model_name = raw_result[DJANGO_CT].split('.')
|
|
|
|
|
additional_fields = {}
|
|
|
|
|
# ZYY: 根据app_label和model_name获取模型
|
|
|
|
|
model = haystack_get_model(app_label, model_name)
|
|
|
|
|
if model and model in indexed_models:
|
|
|
|
|
for key, value in raw_result.items():
|
|
|
|
|
index = unified_index.get_index(model)
|
|
|
|
|
string_key = str(key)
|
|
|
|
|
# ZYY : 检查字段是否在索引中且具有convert方法,特殊处理KEYWORD字段
|
|
|
|
|
if string_key in index.fields and hasattr(
|
|
|
|
|
index.fields[string_key], 'convert'):
|
|
|
|
|
# ZYY: 处理多值字段
|
|
|
|
|
if index.fields[string_key].is_multivalued:
|
|
|
|
|
if value is None or len(value) == 0:
|
|
|
|
|
additional_fields[string_key] = []
|
|
|
|
|
else:
|
|
|
|
|
additional_fields[string_key] = self._to_python(value)
|
|
|
|
|
|
|
|
|
|
del (additional_fields[DJANGO_CT])
|
|
|
|
|
del (additional_fields[DJANGO_ID])
|
|
|
|
|
|
|
|
|
|
if highlight:
|
|
|
|
|
sa = StemmingAnalyzer()
|
|
|
|
|
formatter = WhooshHtmlFormatter('em')
|
|
|
|
|
terms = [token.text for token in sa(query_string)]
|
|
|
|
|
|
|
|
|
|
whoosh_result = whoosh_highlight(
|
|
|
|
|
additional_fields.get(self.content_field_name),
|
|
|
|
|
terms,
|
|
|
|
|
sa,
|
|
|
|
|
ContextFragmenter(),
|
|
|
|
|
formatter
|
|
|
|
|
)
|
|
|
|
|
additional_fields['highlighted'] = {
|
|
|
|
|
self.content_field_name: [whoosh_result],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = result_class(
|
|
|
|
|
app_label,
|
|
|
|
|
model_name,
|
|
|
|
|
raw_result[DJANGO_ID],
|
|
|
|
|
score,
|
|
|
|
|
**additional_fields)
|
|
|
|
|
results.append(result)
|
|
|
|
|
else:
|
|
|
|
|
hits -= 1
|
|
|
|
|
|
|
|
|
|
if self.include_spelling:
|
|
|
|
|
if spelling_query:
|
|
|
|
|
spelling_suggestion = self.create_spelling_suggestion(
|
|
|
|
|
spelling_query)
|
|
|
|
|
additional_fields[string_key] = value.split(
|
|
|
|
|
',')
|
|
|
|
|
else:
|
|
|
|
|
additional_fields[string_key] = index.fields[string_key].convert(
|
|
|
|
|
value)
|
|
|
|
|
else:
|
|
|
|
|
spelling_suggestion = self.create_spelling_suggestion(
|
|
|
|
|
query_string)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'results': results,
|
|
|
|
|
'hits': hits,
|
|
|
|
|
'facets': facets,
|
|
|
|
|
'spelling_suggestion': spelling_suggestion,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def create_spelling_suggestion(self, query_string):
|
|
|
|
|
spelling_suggestion = None
|
|
|
|
|
reader = self.index.reader()
|
|
|
|
|
corrector = reader.corrector(self.content_field_name)
|
|
|
|
|
cleaned_query = force_str(query_string)
|
|
|
|
|
|
|
|
|
|
if not query_string:
|
|
|
|
|
return spelling_suggestion
|
|
|
|
|
|
|
|
|
|
# Clean the string.
|
|
|
|
|
for rev_word in self.RESERVED_WORDS:
|
|
|
|
|
cleaned_query = cleaned_query.replace(rev_word, '')
|
|
|
|
|
|
|
|
|
|
for rev_char in self.RESERVED_CHARACTERS:
|
|
|
|
|
cleaned_query = cleaned_query.replace(rev_char, '')
|
|
|
|
|
|
|
|
|
|
# Break it down.
|
|
|
|
|
query_words = cleaned_query.split()
|
|
|
|
|
suggested_words = []
|
|
|
|
|
|
|
|
|
|
for word in query_words:
|
|
|
|
|
suggestions = corrector.suggest(word, limit=1)
|
|
|
|
|
|
|
|
|
|
if len(suggestions) > 0:
|
|
|
|
|
suggested_words.append(suggestions[0])
|
|
|
|
|
|
|
|
|
|
spelling_suggestion = ' '.join(suggested_words)
|
|
|
|
|
additional_fields[string_key] = self._to_python(value)
|
|
|
|
|
del (additional_fields[DJANGO_CT])
|
|
|
|
|
del (additional_fields[DJANGO_ID])
|
|
|
|
|
if highlight:
|
|
|
|
|
sa = StemmingAnalyzer()
|
|
|
|
|
formatter = WhooshHtmlFormatter('em')
|
|
|
|
|
terms = [token .text for token in sa(query_string)]
|
|
|
|
|
# ZYY: 使用Whoosh进行高亮显示
|
|
|
|
|
whoosh_result = whoosh_highlight(
|
|
|
|
|
additional_fields.get(self.content_field_name),
|
|
|
|
|
terms,
|
|
|
|
|
sa,
|
|
|
|
|
ContextFragmenter(),
|
|
|
|
|
formatter
|
|
|
|
|
)
|
|
|
|
|
additional_fields['highlighted'] = {
|
|
|
|
|
self.content_field_name: [whoosh_result],
|
|
|
|
|
}
|
|
|
|
|
result = result_class(
|
|
|
|
|
app_label,
|
|
|
|
|
model_name,
|
|
|
|
|
raw_result[DJANGO_ID],
|
|
|
|
|
score,
|
|
|
|
|
**additional_fields)
|
|
|
|
|
results.append(result)
|
|
|
|
|
else:
|
|
|
|
|
hits -= 1
|
|
|
|
|
if self.include_spelling:
|
|
|
|
|
if spelling_query:
|
|
|
|
|
# ZYY: 根据spelling_query创建拼写建议
|
|
|
|
|
spelling_suggestion = self.create_spelling_suggestion(
|
|
|
|
|
spelling_query)
|
|
|
|
|
else:
|
|
|
|
|
# ZYY: 根据query_string创建拼写建议
|
|
|
|
|
spelling_suggestion = self.create_spelling_suggestion(
|
|
|
|
|
query_string)
|
|
|
|
|
return {
|
|
|
|
|
'results': results,
|
|
|
|
|
'hits': hits,
|
|
|
|
|
'facets': facets,
|
|
|
|
|
'spelling_suggestion': spelling_suggestion,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ZYY: 创建拼写建议的方法
|
|
|
|
|
def create_spelling_suggestion(self, query_string):
|
|
|
|
|
spelling_suggestion = None
|
|
|
|
|
reader = self.index.reader()
|
|
|
|
|
corrector = reader.corrector(self.content_field_name)
|
|
|
|
|
cleaned_query = force_str(query_string)
|
|
|
|
|
if not query_string:
|
|
|
|
|
return spelling_suggestion
|
|
|
|
|
|
|
|
|
|
def _from_python(self, value):
|
|
|
|
|
"""
|
|
|
|
|
Converts Python values to a string for Whoosh.
|
|
|
|
|
|
|
|
|
|
Code courtesy of pysolr.
|
|
|
|
|
"""
|
|
|
|
|
if hasattr(value, 'strftime'):
|
|
|
|
|
if not hasattr(value, 'hour'):
|
|
|
|
|
value = datetime(value.year, value.month, value.day, 0, 0, 0)
|
|
|
|
|
elif isinstance(value, bool):
|
|
|
|
|
if value:
|
|
|
|
|
value = 'true'
|
|
|
|
|
else:
|
|
|
|
|
value = 'false'
|
|
|
|
|
elif isinstance(value, (list, tuple)):
|
|
|
|
|
value = u','.join([force_str(v) for v in value])
|
|
|
|
|
elif isinstance(value, (six.integer_types, float)):
|
|
|
|
|
# Leave it alone.
|
|
|
|
|
pass
|
|
|
|
|
# ZYY: 清理字符串,移除保留字和保留字符
|
|
|
|
|
for rev_word in self.RESERVED_WORDS:
|
|
|
|
|
cleaned_query = cleaned_query.replace(rev_word, '')
|
|
|
|
|
for rev_char in self.RESERVED_CHARACTERS:
|
|
|
|
|
cleaned_query = cleaned_query.replace(rev_char, '')
|
|
|
|
|
# ZYY: 分解查询词
|
|
|
|
|
query_words = cleaned_query.split()
|
|
|
|
|
suggested_words = []
|
|
|
|
|
for word in query_words:
|
|
|
|
|
suggestions = corrector.suggest(word, limit=1)
|
|
|
|
|
if len(suggestions) > 0:
|
|
|
|
|
suggested_words.append(suggestions[0])
|
|
|
|
|
spelling_suggestion = ' '.join(suggested_words)
|
|
|
|
|
return spelling_suggestion
|
|
|
|
|
|
|
|
|
|
# ZYY: 将Python值转换为Whoosh使用的字符串
|
|
|
|
|
def _from_python(self, value):
|
|
|
|
|
if hasattr(value, 'strftime'):
|
|
|
|
|
if not hasattr(value, 'hour'):
|
|
|
|
|
value = datetime(value.year, value.month, value.day, 0, 0, 0)
|
|
|
|
|
elif isinstance(value, bool):
|
|
|
|
|
if value:
|
|
|
|
|
value = ' true'
|
|
|
|
|
else:
|
|
|
|
|
value = force_str(value)
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
def _to_python(self, value):
|
|
|
|
|
"""
|
|
|
|
|
Converts values from Whoosh to native Python values.
|
|
|
|
|
|
|
|
|
|
A port of the same method in pysolr, as they deal with data the same way.
|
|
|
|
|
"""
|
|
|
|
|
if value == 'true':
|
|
|
|
|
return True
|
|
|
|
|
elif value == 'false':
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
if value and isinstance(value, six.string_types):
|
|
|
|
|
possible_datetime = DATETIME_REGEX.search(value)
|
|
|
|
|
|
|
|
|
|
if possible_datetime:
|
|
|
|
|
date_values = possible_datetime.groupdict()
|
|
|
|
|
|
|
|
|
|
for dk, dv in date_values.items():
|
|
|
|
|
date_values[dk] = int(dv)
|
|
|
|
|
|
|
|
|
|
return datetime(
|
|
|
|
|
date_values['year'],
|
|
|
|
|
date_values['month'],
|
|
|
|
|
date_values['day'],
|
|
|
|
|
date_values['hour'],
|
|
|
|
|
date_values['minute'],
|
|
|
|
|
date_values['second'])
|
|
|
|
|
|
|
|
|
|
value = 'false'
|
|
|
|
|
elif isinstance(value, (list, tuple)):
|
|
|
|
|
value = u','.join([force_str(v) for v in value])
|
|
|
|
|
elif isinstance(value, (six.integer_types, float)):
|
|
|
|
|
# ZYY: 整数和浮点数保持不变
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
value = force_str(value)
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
# ZYY: 将Whoosh的值转换为Python原生值
|
|
|
|
|
def _to_python(self, value):
|
|
|
|
|
if value == 'true ':
|
|
|
|
|
return True
|
|
|
|
|
elif value == 'false':
|
|
|
|
|
return False
|
|
|
|
|
if value and isinstance(value, six.string_types):
|
|
|
|
|
possible_datetime = DATETIME_REGEX.search(value)
|
|
|
|
|
if possible_datetime:
|
|
|
|
|
date_values = possible_datetime.groupdict()
|
|
|
|
|
for dk, dv in date_values.items():
|
|
|
|
|
date_values[dk] = int(dv)
|
|
|
|
|
return datetime(
|
|
|
|
|
date_values['year'],
|
|
|
|
|
date_values['month'],
|
|
|
|
|
date_values ['day'],
|
|
|
|
|
date_values['hour'],
|
|
|
|
|
date_values['minute'],
|
|
|
|
|
date_values['second'])
|
|
|
|
|
try:
|
|
|
|
|
# Attempt to use json to load the values.
|
|
|
|
|
# ZYY: 尝试使用json加载值
|
|
|
|
|
converted_value = json.loads(value)
|
|
|
|
|
|
|
|
|
|
# Try to handle most built-in types.
|
|
|
|
|
# ZYY: 尝试处理大多数内置类型
|
|
|
|
|
if isinstance(
|
|
|
|
|
converted_value,
|
|
|
|
|
(list,
|
|
|
|
|
@ -863,182 +879,139 @@ class WhooshSearchBackend(BaseSearchBackend):
|
|
|
|
|
complex)):
|
|
|
|
|
return converted_value
|
|
|
|
|
except BaseException:
|
|
|
|
|
# If it fails (SyntaxError or its ilk) or we don't trust it,
|
|
|
|
|
# continue on.
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
# ZYY: 如果失败或不信任,则继续
|
|
|
|
|
pass
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WhooshSearchQuery(BaseSearchQuery):
|
|
|
|
|
# ZYY: 将datetime对象转换为Whoosh使用的字符串
|
|
|
|
|
def _convert_datetime(self, date):
|
|
|
|
|
if hasattr(date, 'hour'):
|
|
|
|
|
return force_str(date.strftime('%Y%m%d%H%M%S'))
|
|
|
|
|
else:
|
|
|
|
|
return force_str(date.strftime('%Y%m%d000000'))
|
|
|
|
|
return force_str(date.strftime('%Y%m%d000000'))
|
|
|
|
|
|
|
|
|
|
# ZYY: 清理查询片段,处理保留字和保留字符
|
|
|
|
|
def clean(self, query_fragment):
|
|
|
|
|
"""
|
|
|
|
|
Provides a mechanism for sanitizing user input before presenting the
|
|
|
|
|
value to the backend.
|
|
|
|
|
|
|
|
|
|
Whoosh 1.X differs here in that you can no longer use a backslash
|
|
|
|
|
to escape reserved characters. Instead, the whole word should be
|
|
|
|
|
quoted.
|
|
|
|
|
"""
|
|
|
|
|
words = query_fragment.split()
|
|
|
|
|
cleaned_words = []
|
|
|
|
|
|
|
|
|
|
for word in words:
|
|
|
|
|
if word in self.backend.RESERVED_WORDS:
|
|
|
|
|
word = word.replace(word, word.lower())
|
|
|
|
|
|
|
|
|
|
for char in self.backend.RESERVED_CHARACTERS:
|
|
|
|
|
if char in word:
|
|
|
|
|
word = "'%s'" % word
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
cleaned_words.append(word)
|
|
|
|
|
|
|
|
|
|
cleaned_words. append(word)
|
|
|
|
|
return ' '.join(cleaned_words)
|
|
|
|
|
|
|
|
|
|
# ZYY: 构建查询片段
|
|
|
|
|
def build_query_fragment(self, field, filter_type, value):
|
|
|
|
|
from haystack import connections
|
|
|
|
|
query_frag = ''
|
|
|
|
|
is_datetime = False
|
|
|
|
|
|
|
|
|
|
if not hasattr(value, 'input_type_name'):
|
|
|
|
|
# Handle when we've got a ``ValuesListQuerySet``...
|
|
|
|
|
# ZYY: 处理ValuesListQuerySet的情况
|
|
|
|
|
if hasattr(value, 'values_list'):
|
|
|
|
|
value = list(value)
|
|
|
|
|
|
|
|
|
|
if hasattr(value, 'strftime'):
|
|
|
|
|
if hasattr(value, ' strftime'):
|
|
|
|
|
is_datetime = True
|
|
|
|
|
|
|
|
|
|
if isinstance(value, six.string_types) and value != ' ':
|
|
|
|
|
# It's not an ``InputType``. Assume ``Clean``.
|
|
|
|
|
# ZYY: 假设为Clean类型
|
|
|
|
|
value = Clean(value)
|
|
|
|
|
else:
|
|
|
|
|
value = PythonData(value)
|
|
|
|
|
|
|
|
|
|
# Prepare the query using the InputType.
|
|
|
|
|
prepared_value = value.prepare(self)
|
|
|
|
|
|
|
|
|
|
if not isinstance(prepared_value, (set, list, tuple)):
|
|
|
|
|
# Then convert whatever we get back to what pysolr wants if needed.
|
|
|
|
|
prepared_value = self.backend._from_python(prepared_value)
|
|
|
|
|
|
|
|
|
|
# 'content' is a special reserved word, much like 'pk' in
|
|
|
|
|
# Django's ORM layer. It indicates 'no special field'.
|
|
|
|
|
if field == 'content':
|
|
|
|
|
index_fieldname = ''
|
|
|
|
|
else:
|
|
|
|
|
index_fieldname = u'%s:' % connections[self._using].get_unified_index(
|
|
|
|
|
).get_index_fieldname(field)
|
|
|
|
|
|
|
|
|
|
filter_types = {
|
|
|
|
|
'content': '%s',
|
|
|
|
|
'contains': '*%s*',
|
|
|
|
|
'endswith': "*%s",
|
|
|
|
|
'startswith': "%s*",
|
|
|
|
|
'exact': '%s',
|
|
|
|
|
'gt': "{%s to}",
|
|
|
|
|
'gte': "[%s to]",
|
|
|
|
|
'lt': "{to %s}",
|
|
|
|
|
'lte': "[to %s]",
|
|
|
|
|
'fuzzy': u'%s~',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if value.post_process is False:
|
|
|
|
|
query_frag = prepared_value
|
|
|
|
|
else:
|
|
|
|
|
if filter_type in [
|
|
|
|
|
'content',
|
|
|
|
|
'contains',
|
|
|
|
|
'startswith',
|
|
|
|
|
'endswith',
|
|
|
|
|
'fuzzy']:
|
|
|
|
|
if value.input_type_name == 'exact':
|
|
|
|
|
query_frag = prepared_value
|
|
|
|
|
else:
|
|
|
|
|
# Iterate over terms & incorportate the converted form of
|
|
|
|
|
# each into the query.
|
|
|
|
|
terms = []
|
|
|
|
|
|
|
|
|
|
if isinstance(prepared_value, six.string_types):
|
|
|
|
|
possible_values = prepared_value.split(' ')
|
|
|
|
|
# ZYY: 使用InputType准备查询
|
|
|
|
|
prepared_value = value.prepare(self)
|
|
|
|
|
if not isinstance(prepared_value, (set, list, tuple )):
|
|
|
|
|
# ZYY: 转换为pysolr需要的格式
|
|
|
|
|
prepared_value = self.backend._from_python(prepared_value)
|
|
|
|
|
# ZYY: 'content'是特殊保留字,表示无特殊字段
|
|
|
|
|
if field == 'content':
|
|
|
|
|
index_fieldname = ''
|
|
|
|
|
else:
|
|
|
|
|
index_fieldname = u'%s:' % connections[self._using].get_unified_index(
|
|
|
|
|
).get_index_fieldname(field)
|
|
|
|
|
filter_types = {
|
|
|
|
|
'content': '%s',
|
|
|
|
|
'contains': '*%s*',
|
|
|
|
|
' endswith': "*%s",
|
|
|
|
|
'startswith': "%s*",
|
|
|
|
|
'exact': '%s',
|
|
|
|
|
'gt': "{%s to}",
|
|
|
|
|
'gte': "[%s to]",
|
|
|
|
|
'lt': "{to %s}",
|
|
|
|
|
'lte': "[to %s]",
|
|
|
|
|
'fuzzy': u'%s~',
|
|
|
|
|
}
|
|
|
|
|
if value.post_process is False:
|
|
|
|
|
query_frag = prepared_value
|
|
|
|
|
else:
|
|
|
|
|
if filter_type in [
|
|
|
|
|
'content',
|
|
|
|
|
'contains',
|
|
|
|
|
'startswith ',
|
|
|
|
|
'endswith',
|
|
|
|
|
'fuzzy']:
|
|
|
|
|
if value.input_type_name == 'exact':
|
|
|
|
|
query_frag = prepared_value
|
|
|
|
|
else:
|
|
|
|
|
# ZYY: 遍历词项并合并转换后的形式
|
|
|
|
|
terms = []
|
|
|
|
|
if isinstance(prepared_value, six.string_types):
|
|
|
|
|
possible_values = prepared_value.split(' ')
|
|
|
|
|
else:
|
|
|
|
|
if is_datetime is True:
|
|
|
|
|
prepared_value = self._convert_datetime(
|
|
|
|
|
prepared_value)
|
|
|
|
|
possible_values = [prepared_value]
|
|
|
|
|
for possible_value in possible_values:
|
|
|
|
|
terms.append(
|
|
|
|
|
filter_types[filter_type] %
|
|
|
|
|
self.backend._from_python(possible_value))
|
|
|
|
|
if len(terms) == 1:
|
|
|
|
|
query_frag = terms[0]
|
|
|
|
|
else:
|
|
|
|
|
query_frag = u"(%s)" % " AND ".join(terms)
|
|
|
|
|
elif filter_type == 'in':
|
|
|
|
|
in_options = []
|
|
|
|
|
for possible_value in prepared_value:
|
|
|
|
|
is_datetime = False
|
|
|
|
|
if hasattr(possible_value, 'strftime'):
|
|
|
|
|
is_datetime = True
|
|
|
|
|
pv = self.backend._from_python (possible_value)
|
|
|
|
|
if is_datetime is True:
|
|
|
|
|
prepared_value = self._convert_datetime(
|
|
|
|
|
prepared_value)
|
|
|
|
|
|
|
|
|
|
possible_values = [prepared_value]
|
|
|
|
|
|
|
|
|
|
for possible_value in possible_values:
|
|
|
|
|
terms.append(
|
|
|
|
|
filter_types[filter_type] %
|
|
|
|
|
self.backend._from_python(possible_value))
|
|
|
|
|
|
|
|
|
|
if len(terms) == 1:
|
|
|
|
|
query_frag = terms[0]
|
|
|
|
|
else:
|
|
|
|
|
query_frag = u"(%s)" % " AND ".join(terms)
|
|
|
|
|
elif filter_type == 'in':
|
|
|
|
|
in_options = []
|
|
|
|
|
|
|
|
|
|
for possible_value in prepared_value:
|
|
|
|
|
is_datetime = False
|
|
|
|
|
|
|
|
|
|
if hasattr(possible_value, 'strftime'):
|
|
|
|
|
is_datetime = True
|
|
|
|
|
|
|
|
|
|
pv = self.backend._from_python(possible_value)
|
|
|
|
|
|
|
|
|
|
if is_datetime is True:
|
|
|
|
|
pv = self._convert_datetime(pv)
|
|
|
|
|
|
|
|
|
|
if isinstance(pv, six.string_types) and not is_datetime:
|
|
|
|
|
in_options.append('"%s"' % pv)
|
|
|
|
|
pv = self._convert_datetime(pv)
|
|
|
|
|
if isinstance(pv, six.string_types) and not is_datetime:
|
|
|
|
|
in_options.append('"%s"' % pv)
|
|
|
|
|
else:
|
|
|
|
|
in_options.append('%s' % pv)
|
|
|
|
|
query_frag = "(%s)" % " OR ".join(in_options)
|
|
|
|
|
elif filter_type == 'range':
|
|
|
|
|
start = self.backend._from_python(prepared_value[0])
|
|
|
|
|
end = self.backend._from_python(prepared_value[1])
|
|
|
|
|
if hasattr(prepared_value [0], 'strftime'):
|
|
|
|
|
start = self._convert_datetime(start)
|
|
|
|
|
if hasattr(prepared_value[1], 'strftime'):
|
|
|
|
|
end = self._convert_datetime(end)
|
|
|
|
|
query_frag = u"[%s to %s]" % (start, end)
|
|
|
|
|
elif filter_type == 'exact':
|
|
|
|
|
if value.input_type_name == 'exact':
|
|
|
|
|
query_frag = prepared_value
|
|
|
|
|
else:
|
|
|
|
|
in_options.append('%s' % pv)
|
|
|
|
|
|
|
|
|
|
query_frag = "(%s)" % " OR ".join(in_options)
|
|
|
|
|
elif filter_type == 'range':
|
|
|
|
|
start = self.backend._from_python(prepared_value[0])
|
|
|
|
|
end = self.backend._from_python(prepared_value[1])
|
|
|
|
|
|
|
|
|
|
if hasattr(prepared_value[0], 'strftime'):
|
|
|
|
|
start = self._convert_datetime(start)
|
|
|
|
|
|
|
|
|
|
if hasattr(prepared_value[1], 'strftime'):
|
|
|
|
|
end = self._convert_datetime(end)
|
|
|
|
|
|
|
|
|
|
query_frag = u"[%s to %s]" % (start, end)
|
|
|
|
|
elif filter_type == 'exact':
|
|
|
|
|
if value.input_type_name == 'exact':
|
|
|
|
|
query_frag = prepared_value
|
|
|
|
|
prepared_value = Exact(prepared_value).prepare(self)
|
|
|
|
|
query_frag = filter_types[filter_type] % prepared_value
|
|
|
|
|
else:
|
|
|
|
|
prepared_value = Exact(prepared_value).prepare(self)
|
|
|
|
|
if is_datetime is True:
|
|
|
|
|
prepared_value = self._convert_datetime(prepared_value)
|
|
|
|
|
query_frag = filter_types[filter_type] % prepared_value
|
|
|
|
|
else:
|
|
|
|
|
if is_datetime is True:
|
|
|
|
|
prepared_value = self._convert_datetime(prepared_value)
|
|
|
|
|
|
|
|
|
|
query_frag = filter_types[filter_type] % prepared_value
|
|
|
|
|
|
|
|
|
|
if len(query_frag) and not isinstance(value, Raw):
|
|
|
|
|
if not query_frag.startswith('(') and not query_frag.endswith(')'):
|
|
|
|
|
query_frag = "(%s)" % query_frag
|
|
|
|
|
|
|
|
|
|
if len(query_frag) and not isinstance(value, Raw):
|
|
|
|
|
if not query_frag.startswith('(') and not query_frag.endswith(')'):
|
|
|
|
|
query_frag = "(%s)" % query_frag
|
|
|
|
|
return u"%s%s" % (index_fieldname, query_frag)
|
|
|
|
|
|
|
|
|
|
# if not filter_type in ('in', 'range'):
|
|
|
|
|
# # 'in' is a bit of a special case, as we don't want to
|
|
|
|
|
# # convert a valid list/tuple to string. Defer handling it
|
|
|
|
|
# # until later...
|
|
|
|
|
# value = self.backend._from_python(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WhooshEngine(BaseEngine):
|
|
|
|
|
backend = WhooshSearchBackend
|
|
|
|
|
query = WhooshSearchQuery
|
|
|
|
|
query = WhooshSearchQuery
|