当前关于django-haystack的教程大多同质化,当然我的教程也是参考后实战出来的,这里我将补充说明haystack中有关page
和page_obj
的不同使用(推荐page_obj
)和搜索关键字高亮的时候其他字显示...
能够根据用户的搜索关键词对搜索结果进行排序(日期)以及高亮搜索关键字
当前环境:
当前版本:
Haystack是一个专门提供搜索功能的应用,django-haystack模块 为Django提供了模块化的搜索服务,它的主要特点是提供统一相似的API,支持 Solr、Elasticsearch、Whoosh、Xapian 等多种搜索引擎,能够在插入不同的搜索后端后不用修改所写的代码,减少代码量, 使自定义搜索的集成尽可能容易 , 专注于搜索功能 。
pip install django-haystack
Whoosh是纯Python编写的、索引文本及搜索文本的类和函数库, 它的优势是易用性强、比较小巧、配置简单方便。但Whoosh本身只有英文分词,如果需要中文分词组件 ,需要单独添加。这里使用Whoosh作为django-haystack模块的搜索引擎。
pip install whoosh
jieba的初衷时做最好的 Python 中文分词组件 ,它是一个强大的分词库,完美支持中文分词 ,所以使用jieba作为Whoosh的中文分词组件
pip install jieba
blog --> blog --> settings.py
在INSTALLED_APPS
内注册应用,注意APP注册有顺序,所以haystack要放在靠前
INSTALLED_APPS = [
...
'haystack',
...
]
import os
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.path.join(BASE_DIR, 'whoose_index'),
}
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
HATSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
haystack.backends.whoosh_backend.WhooshEngine
,后面添加jieba中文分词,会对其进行更改search_indexes.py
的haystack.indexes.RealTimeSearchIndex
添加jieba
中文分词:
复制虚拟环境下whoosh_backend.py
文件到APP目录下,即./Lib/site-packages/haystack/backends/whoosh_backend.py
复制到blog --> apps --> config根目录下
将whoosh_backend.py
更名为whoosh_cn_backend.py
,可自定义
编辑whoosh_cn_backend.py
,大致在160–170行之间,修改如下:
jieba
分词器from jieba.analyse import ChineseAnalyzer
analyzer=StemmingAnalyzer()
为analyzer=ChineseAnalyzer()
原始
...
schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True)
...
修改为
from jieba.analyse import ChineseAnalyzer
...
schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True)
...
更改blog --> blog --> settings.py下HAYSTACK_CONNECTIONS
的ENGINE
为config.whoosh_cn_backend.WhooshEngine
import os
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'config.whoosh_cn_backend.WhooshEngine',
'PATH': os.path.join(BASE_DIR, 'whoose_index'),
}
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
HATSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
规划PATH
位置,更改blog --> blog --> settings.py下HAYSTACK_CONNECTIONS
的PATH
为
import os
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'config.whoosh_cn_backend.WhooshEngine',
'PATH': os.path.join(BASE_DIR, 'templates', THEME, 'templates', 'search', 'whoose_index'),
}
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
HATSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
SearchIndex
对象是Haystack决定应在搜索索引中放置哪些数据并处理数据流的方式 ,索引是针对APP的,需要为哪个APP创建索引,就要在哪个APP目录下创建search_indexes.py
文件,且必须是这个名字,不能更改。
SearchIndex
的字段名称非常标准化 , 要构建一个SearchIndex
,通常引入indexes.SearchIndex
和 indexes.Indexable
,定义要与之一起存储数据的字段,并定义一个get_model
方法,返回需要检索的模型类。
search_indexes.py
文件中需要创建ArticleIndex
来和Article
模型进行匹配,使得 Haystack可以自动识别。也就是说,想搜索哪个模型,类名就是模型名称+Index
,即ArticleIndex
from haystack import indexes
from .models import Article
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return Article
def index_queryset(self, using=None):
return self.get_model().objects.all()
每个索引里面必须有且只能有一个字段为 document=True,这代表haystack 和搜索引擎将使用此字段的内容作为索引进行检索。
如果使用一个字段设置了
document=True
,则一般约定此字段名为text
,这是在SearchIndex
类里面一贯的命名,以防止后台混乱,当然名字你也可以随便改,不过不建议改。
haystack提供了use_template=True在text
字段,这样就允许我们使用数据模板去建立搜索引擎的索引。说得通俗点就是想用什么来作为索引进行搜索,例如想搜索模型Article
中的title
字段和body
字段,就要配置相应字段,use_template
的默认数据模板存放在templates/search/indexes/app/article_text.txt
文件中,文件内容如下:
{{ object.title }}
{{ object.body }}
注意:
app即
search_indexes.py
文件所在应用app的名称,article_text.txt 中article是模型名称,即Article,这里要全小写在加上 _text.txt
article_text.txt
更名并放到想放的位置,引入template_name
from haystack import indexes
from .models import Article
# import datetime
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True, template_name="search/indexes/config/article_text.txt")
def get_model(self):
return Article
def index_queryset(self, using=None):
# return self.get_model().objects.filter(create_date__lte=datetime.datetime.now())
return self.get_model().objects.all()
**这里我提供两种方法进行,涉及返回结果是page
还是page_obj
的不同使用,只需选择一种进行配置即可 **
所谓的page
还是page_obj
都是 SearchResult
对象列表,而不是单个模型
项目URL
from django.conf.urls import url, include
urlpatterns = [
...
url(r'^search/', include('haystack.urls')),
...
]
在header.html里
<form method="get" id="searchform" action="{% url 'haystack_search' %}">
<div class="input-group">
<input type="search" class="blog-header-search" placeholder="search..." name="q">
<button type="submit" class="blog-header-search-btn"><i class="fa fa-search"></i></button>
</div>
</form>
haystack_search
视图函数会将搜索结果传递给模板templates/search/search.html
,因此创建这个模板文件,对搜索结果进行渲染:
{% extends 'base.html' %}
{% load blog_simple_tag %}
{% block content_detail %}
<header class="blog-post-page-title">
<!--<marquee><font color="blue" size="2">You will make it!</marquee>-->
<h3>当前位于 “搜索” 页面, 关键词:{{ query }}</h3>
</header>
<div class="blog-main-post">
{% for article in page.object_list %}
<div class="index-post-br"></div>
<article class="blog-post-block">
{% if article.object.img_link %}
<header>
<div class="blog-post-block-img">
<img src="{{ article.object.img_link }}" alt="">
</div>
</header>
{% endif %}
<div class="blog-post-block-padding">
<a href="{% url 'blog:article' article.object.id %}">{% search_highlight article.object.title query %}</a>
<section>
{{ article.object.summary }}
</section>
<footer>
<span>
<i class="fa fa-folder-o"></i>
<a href="{% url 'blog:category' article.object.category %}" itemprop="url" rel="index">{{ article.object.category }}</a>
</span>
<span>
<time datetime="{{ article.object.create_date }}"><i class="fa fa-clock-o"></i>创建于{{ article.object.create_date }}</time>
{% if article.object.update_date > article.object.create_date %}
<time datetime="{{ article.object.update_date }}"><i class="fa fa-clock-o"></i> 更新于{{ article.object.update_date }}</time>
{% endif %}
</span>
</footer>
</div>
</article>
{% empty %}
<div class="blog-main-post">
<div class="no-post">没有搜索到相关内容,请重新搜索</div>
</div>
{% endfor %}
{% if page.has_previous or page.has_next %}
<nav id="pagination" class="blog-pagination" >
<article class="blog-post-page-readmore">
{% if page.has_previous %}
<a class="blog-post-page-readmore-prev" href="?q={{ query }}&page={{ page.previous_page_number }}"
data-toggle="tooltip" data-placement="top"
title="当前第 {{ page.number }} 页,共 {{ paginator.num_pages }} 页">
上一页</a>
{% endif %}
{% if page.has_next %}
<a class="blog-post-page-readmore-next" href="?q={{ query }}&page={{ page.next_page_number }}"
data-toggle="tooltip" data-placement="top"
title="当前第 {{ page.number }} 页,共 {{ paginator.num_pages }} 页">
下一页</a>
{% endif %}
</article>
</nav>
{% endif %}
</div>
{% endblock %}
{% block sidebar_toc %}
{% include 'sidebar.html' %}
{% endblock %}
query
是搜索的关键词page.object_list
是返回 SearchResult 类型的结果search_highlight article.object.title query
是关键词高亮,后面单独说page.has_previous or page.has_next
是有关页面的前后页分页配置了视图,search_indexes.py
返回时间逆序数据的索引将失效(亲测本来就不生效),以views.py
中queryset
的id
倒叙为准,所以想自定义QuerySet,还是需要采用此方法
from haystack.generic_views import SearchView
class BlogSearchView(SearchView):
context_object_name = 'search_list'
queryset = SearchQuerySet().order_by('-id')
虽然 SearchView 在 haystack.views 中也存在,但此 SearchView 并没有属性 as_view,不要混淆,不要使用
from haystack.views import SearchView
应用APP的URL
from django.conf.urls import url, include
from .views import BlogSearchView
urlpatterns = [
...
url(r'^search/$', BlogSearchView.as_view(), name='search'),
...
]
在header.html里,与page
的区别只在于action
<form method="get" id="searchform" action="{% url 'blog:search' %}">
<div class="input-group">
<input type="search" class="blog-header-search" placeholder="search..." name="q">
<button type="submit" class="blog-header-search-btn"><i class="fa fa-search"></i></button>
</div>
</form>
haystack_search
视图函数会将搜索结果传递给模板templates/search/search.html
,因此创建这个模板文件,对搜索结果进行渲染,与page
的区别只在于page_obj
替换page
,将search.html
所有page
替换为page_obj
。
HTML和CSS方式二选一即可
base.html,加入到header中
<style>
span.highlighted {
color: red;
}
</style>
blog.css,加到最后
.highlighted {
color: red;
}
之所以把标题高亮单独拿出来说明,是因为使用highlight默认的高亮方法会出现标题不能全部显示的问题,搜索关键字高亮的时候其他字显示...
原因:
myblog->venv->Lib->site-packages->haystack->utils->highlighting.py
start_offset 与 end_offset 分别代表高亮代码的开始位置与结束位置,如果高亮部分在中间的话,前面的部分就直接显示 …
if start_offset > 0:
highlighted_chunk = '...%s' % highlighted_chunk
if end_offset < len(self.text_block):
highlighted_chunk = '%s...' % highlighted_chunk
return highlighted_chunk
解决办法:
第一种:`
在上述文件中 myblog->venv->Lib->site-packages->haystack->utils->highlighting.py
加入判断语句, 大约158行,如果字符串长度小于 max_length 的值的话,我们就直接将其返回
highlighted_chunk += text[matched_so_far:]
if len(self.text_block) < self.max_length:
return self.text_block[:start_offset] + highlighted_chunk
if start_offset > 0:
highlighted_chunk = '...%s' % highlighted_chunk
if end_offset < len(self.text_block):
highlighted_chunk = '%s...' % highlighted_chunk
return highlighted_chunk
{% highlight article.object.title with query %}
{% load highlight %}
<div class="blog-post-block-padding">
<a href="{% url 'blog:article' article.object.id %}">{% highlight article.object.title with query %}</a>
<section>
{{ article.object.summary }}
</section>
第二种(推荐){% search_highlight article.object.title query %}
{% load blog_simple_tag %}
<div class="blog-post-block-padding">
<a href="{% url 'blog:article' article.object.id %}">{% search_highlight article.object.title query %}</a>
<section>
{{ article.object.summary }}
</section>
blog_simple_tag.py
@register.simple_tag
def search_highlight(text, q):
"""自定义标题搜索词高亮函数,忽略大小写"""
if len(q) > 1:
try:
text = re.sub(q, lambda a: '<span class="highlighted">{}</span>'.format(a.group()),
text, flags=re.IGNORECASE)
text = mark_safe(text)
except:
pass
return tex
使用{% highlight article.object.summary with query %}
语句或者
<section>
{% highlight article.object.summary with query %}
{% search_highlight article.object.summary query %}
</section>
如果文章摘要部分来自文章内容,则可能用到max_length参数,限制内容长度{% ... max_length 130 %}
在前面“Haystack和Whoosh配置”中已经在settings.py中配置HAYSTACK_SEARCH_RESULTS_PER_PAGE
分页功能
...
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
...
search.html
haystack中已自带分页功能,我们只需调用,确定好page还是page_obj即可
{% if page.has_previous or page.has_next %}
<nav id="pagination" class="blog-pagination" >
<article class="blog-post-page-readmore">
{% if page.has_previous %}
<a class="blog-post-page-readmore-prev" href="?q={{ query }}&page={{ page.previous_page_number }}"
data-toggle="tooltip" data-placement="top"
title="当前第 {{ page.number }} 页,共 {{ paginator.num_pages }} 页">
上一页</a>
{% endif %}
{% if page.has_next %}
<a class="blog-post-page-readmore-next" href="?q={{ query }}&page={{ page.next_page_number }}"
data-toggle="tooltip" data-placement="top"
title="当前第 {{ page.number }} 页,共 {{ paginator.num_pages }} 页">
下一页</a>
{% endif %}
</article>
</nav>
{% endif %}
python manage.py rebuild_index
或
python manage.py uodate_index