技术笔记——Django+Nginx+uwsgi搭建自己的博客(十四)

上一篇博客中,我们将博客系统的用户系统全部迁移到了auth.User模型下。本来打算介绍下使用BootStrap框架美化前端的内容,然而感觉个人的前端技术实在是粗糙,且对js/css一点好感都没有,因此这里只放个改版之后的图片好了:

可以看到,在导航栏的最右侧出现了一个搜索栏,在此输入关键字,我们可以在搜索结果页面中看到高亮的关键字:

这就是我们在此篇博客中要介绍的内容——使用haystack搜索框架对博客进行全文搜索。

haystack是基于Django实现的一套搜索框架,支持whoosh, solr和elasticsearch这三大搜索引擎。在该框架中将基本的创建索引、过滤结果等做了很好的封装,用户可以在框架支持的后端中自行选择喜欢的搜索引擎,而无需关注具体引擎的使用方法。作为一种即插即用型的框架,haystack被设计成了Django的一个app,使其可以十分方便地在Django中调用。

首先让我们来安装haystack。安装分为两部分:1、安装haystack框架本身;2、安装其所支持的搜索引擎。打开控制台,输入以下命令安装haystack和whoosh:

pip install haystack
pip install whoosh

在安装好后,我们就可以在工程中调用haystack了。打开myblog/settings.py文件,在INSTALLED_APPS中添加一行:

# myblog/settings.py
INSTALLED_APPS = [
    # ...
    # haystack
    'haystack'
    # ...
]

这样就将haystack框架“插”到了我们的博客上。

然后我们对haystack进行初步设置,在settings.py中继续添加以下内容:

# myblog/settings.py
# ...
# haystack
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
        'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
    },
}
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# ...

在HAYSTACK_CONNECTIONS里,我们对haystack使用的引擎进行配置。ENGINE可选的值有三项:'haystack.backends.solr_backend.SolrEngine','haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine'和'haystack.backends.whoosh_backend.WhooshEngine',分别对应solr,elasticsearch和whoosh。当然,根据引擎的版本不同,此配置的名称也略有不同,可以查阅haystack的官方文档。

在选择了引擎后,我们要根据不同的引擎来设定引擎相关的其他配置。由于我们采用了whoosh,因此这里只介绍whoosh的配置。whoosh的相关配置只有一个:‘PATH’,该配置决定了whoosh索引文件的存放位置,这里我们把它放在settings.py的同级目录下。

紧接着HAYSTACK_CONNECTIONS的是另外两个配置:HAYSTACK_SEARCH_RESULTS_PER_PAGE和HAYSTACK_SIGNAL_PROCESSOR。前者顾名思义是每页显示搜索结果的个数,这里设为10;后者是信号处理器,这里设置为'haystack.signals.RealtimeSignalProcessor',即实时处理器,每当索引的模型发生变化(save和delete)都会更新索引,实现搜索的即时更新。

接下来,我们要为搜索的模型建立索引。我们想对哪个模型对象进行搜索,就要在对应的模型下建立索引。我们要对blogs模型进行搜索,因此我们在blogs目录下建立search_indexes.py文件(文件名一定不能错),内容如下:

# blogs/search_indexes.py
from haystack import indexes
from .models import Blog

class NoteIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    blogcontent = indexes.CharField(model_attr='content')

    def get_model(self):
        return Blog

    def index_queryset(self, using=None):
        return self.get_model().objects.all()

在这个文件里,我们建立了一个NoteIndex的类,其中包含两个对象以及两个方法。在介绍这个类之前,我们要先简要讲一些文档型数据库的概念。

与关系型数据库不同,文档型数据库存储的单位不是一行行记录,而是一篇篇文档。对于关系型数据库来说,一个对象的各个属性值可能分散在多个表中,需要将多表联合起来才能得到该对象的全貌。如我们的blogs,博客的作者存储在auth.User中,内容存储在blogs自身中,而comment又存储在comment中。这样,如果我们想看到博客的全貌——博客作者、博客内容、博客评论就需要三个表联合查询才可得到;而对于文档数据库,它会将博客作者、博客内容和博客评论全部存储在一篇文档中,即每篇博客均是一篇独立的文档,从而更加便于索引。haystack提供的SearchIndex即可看成为一个文档型数据库,每个SearchIndex存储的文档均对应原模型中的一个对象;通过查找SearchIndex,也就得到了原模型中的对象。

然而,为了便于从关系型数据库中拿到数据,haystack提供的SearchIndex仍然使用了field(这里我不太清楚这个field应该翻译成字段还是像有的资料一样翻译成文档域。)的概念。因此,一个比较好理解的解释就是,SearchIndex仍然可以看成普通的关系型数据库,只不过有一个字段存储了描述整个对象的文档,而其余字段则用于对搜索范围进行指定。

有了以上的概念,我们就可以介绍NoteIndex类了。该类包含两个成员:text和blogcontent。在haystack的SearchIndex中,必须有且只有一个field的document属性为True,这标志着该字段为文档字段,即存储描述对象的文档;而use_template设为True,这使得我们可以通过数据模板来设定要对对象的哪些内容进行搜索。blogcontent为普通字段,通过model_attr参数来从原模型中获得数据,从而对搜索结果进行筛选。

我们再来看两个类函数:get_model方法返回该index要搜索的模型,而index_queryset则返回搜索结果。这里我们使用all(),即对全部博客进行搜索;我们在这里也可以使用filter+上面的blogcontent来指定搜索范围。

在建立好SearchIndex类后,让我们来建立刚才提到的数据模板。我们需要在blogs目录下的templates/目录建立search/indexes/blogs目录,在其中建立名为<模型名>_text.txt的文件,这里为blog_text.txt,内容如下:

{{ object.title }}
{{ object.content }}

object的含义就是我们原始的blog对象,这里我们设定对title和content字段进行搜索。

然后我们回到myblog/views.py,向主页index中添加搜索表单:

# myblog/views.py
# ...
from haystack.forms import SearchForm
# ...
def index(request):
    # ...
    searchform = SearchForm()
    # ...
    content = { # ...
                'searchform':searchform,
               }
    return render(request, 'myblog/index.html', content)

这里我们使用haystack提供的现成的SearchForm即可,大家可在前端中自行摆放表单的位置。

然后,我们在myblog/templates下建立search目录,在其中建立search.html文件,作为搜索结果页,内容如下:

<!--search.html-->
{% extends "parentTemplate.html" %}
{% load highlight %}
{% block othernavitem %}
<form method="get" action="{% url 'blogSearch' %}">
{{ searchform }}
<input type="submit" value="搜索">
</form>
{% endblock %}
{% block content %}
<style>
span.highlighted {
        color: red;
}
</style>
<div class="content-wrap">
{% for blog in page.object_list %}
    <div>
        <a href="{% url 'blogs:content' blog.object.id %}">
            {% highlight blog.object.title with query %}
        </a>
        <div>
            {% highlight blog.object.content with query %}
        </div>
    </div>
{% empty %}
    <div class="no-post">没有搜索到相关内容,请重新搜索</div>
{% endfor %}

</div>
{% endblock %}

这里的{% othernavitem %}块是前端导航栏的块,用于在搜索结果页面也显示搜索条;在最上面通过{% load highlight %}将haystack提供的highlight标签引入,用于高亮显示搜索结果。由于highlight处理的是<span>标签,因此我们需要自己为highlight标签指定一个格式,这里简单地变成红色。

在下面的for循环中,循环对象为page.object_list,此列表存储了haystack搜索得到的对象列表;随后我们使用{% highlight blog.object.title with query %}将博客的标题以及内容显示出来。在这里,我们用{% empty %}表示空搜索集,即没有找到任何东西。

接下来我们来写search.html的后端。我们在myblog/views.py里添加以下内容:

# myblog/views.py
# ...
from haystack.views import SearchView
# ...
# Create own searchview
class blogSearchView(SearchView):

    def extra_context(self):
        context = super(blogSearchView,self).extra_context()
        searchform = SearchForm()
        context['searchform'] = searchform
        return context

这里我们采用继承haystack的SearchView的方法来实现我们自己的blogSearchView。注意,官方文档推荐的是继承haystack.generic_views中的SearchView而不是haystack.views中的SearchView,然而我被这个官方推荐卡了半天之后决定放弃,要么是服务器报错要么就是没有搜索结果,因此这里暂时推荐大家用旧版。

我们在这里重载了extra_context函数,该函数用于向搜索结果页面添加一些自定义的数据,在这里我们把搜索表单加上去,使得在搜索结果页面也仍然可以看到搜索表单。

在这里需要注(吐)意(槽)的是,如果我们使用haystack的SearchView的话(无论是直接使用还是把他作为基类),search.html的路径和名字都不能变化。很奇怪haystack为什么没有把这个作为可配置的,而是hardcode在了SearchView的源码中。此外,诸如page.object_list,query之类也是hardcode在SearchView中的,也没有在文档里说的很清楚。

然后,我们在urls.py中将blogSearchView添加到url列表中:

# urls.py
urlpatterns = [
    # ...
    url(r'^search/$',blogSearchView(),name='blogSearch')

]

这样,我们的前后端代码就编写完成了。

万事俱备,只欠索引。我们回到myblog目录下,输入以下命令让haystack为我们建立blog的索引:

.manage.py rebuild_index

成功的话会看到以下画面:

以及在myblog目录下会出现whoosh_index目录,里面会存储建立好的索引文件(不用看了,内容都是乱码似的东西)。

由此,我们就使用haystack建立了简单的全文搜索系统。

个人感觉,haystack的文档做的比较一般,有些在源码中才能看到的东西并没有在文档中体现出来(如前文吐槽过的页面路径和名称);还有就算haystack和Django结合的比较紧密,有一些函数需要查阅Django的文档才能看懂官方文档在说什么。当然,如果直接照搬官方文档的话做出个简单的搜索功能没有问题,但是要是想自定义表单和搜索页面的话,个人感觉最好不要使用继承的方法,而是自己使用haystack提供的更底层API来实现。在接下来的博客中,我们将继续研究haystack以及whoosh的相关内容,敬请期待~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值