今日184人次阅读了68/160篇文章  |    留言板  |    RSS订阅
所谓成长,就是在听到波涛汹涌四个字,再也联想不到大海了毒鸡汤

为django博客添加站内搜索功能

2019-10-24     loonlog     577     0

本文目录

本博客网站的搜索功能就是按照这个方法设计的,大家可以测试一下本站的搜索功能,下面的文章就是方法(*^__^*) 。

  • 本搜索功能使用到了haystack,是django的开源搜索框架,该框架支持Solr,Elasticsearch,Whoosh, *Xapian*搜索引擎,不用更改代码,直接切换引擎,减少代码量。我是在读django by example这本书知道的haystack,因为我是按这本书学习的,这本书真不错。

  • 搜索引擎使用Whoosh,这是一个由纯Python实现的全文搜索引擎,没有二进制文件等,比较小巧,配置比较简单,当然性能自然略低,站内搜索使用,绰绰有余。

  • 中文分词Jieba,由于Whoosh自带的是英文分词,对中文的分词支持不是太好,故用jieba替换whoosh的分词组件。

  • django版本2.1,系统Centos 7

一:为django博客添加基本的站内搜索功能

开始之前,回顾一下我的博客文章数据模型,我要为我的博客添加搜索功能,主要对文章的题目和内容进行搜索,博客文章数据模型(大致,忽略部分细节)为:

class Post(models.Model):
    STATUS_CHOICES = (('draft', 'Draft'), ('published', 'Published'))
    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250, unique_for_date='publish')
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
      class Meta:
        ordering = ('-publish',)
      def __str__(self):
        return self.title

1、首先安装工具haystack、whoosh、jieba

pip install whoosh django-haystack jieba

2、添加 Haystack 到Django的 INSTALLED_APPS

配置Django项目的settings.py,在里面的INSTALLED_APPS添加Haystack,例如下面的代码:

INSTALLED_APPS = [
    ……  
    'haystack', #说是放在自己的app前面,放后面怎么样,我也没试过
    'blog.apps.BlogConfig',
    ……
]

注:代码重省略号代表其他应用的代码部分,在此处省略显示

3、在settings.py中继续配置引擎

import os #这一句相信你的静态变量已经导入过一次了,此次可忽略
HAYSTACK_CONNECTIONS = {  
    'default': { 
        #配制语句,这里后面还要改一次
        'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 
        # 索引文件路径 ,后面还要改一次,注意加深印象     
         'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
    },  
} 
# 指定搜索页面每页显示的结果数量
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 7
# 索引自动更新,后面会提到这个
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

其中ENGINE为使用的引擎必须要有,如果引擎是Whoosh,则PATH必须要填写,其为Whoosh 索引文件的存放文件夹。

4、创建索引

我的博客app为blog,在blog下做全文检索,则必须在blog目录下创建search_indexes.py文件,文件名不能修改。

先看下我的目录结构(我这个和django by example书中目录结构稍微不一样,我用了自己的方法建立工程,具体方法请看)

mysite/  
    manage.py  
    mysite/
        __init__.py
        settings.py    
        urls.py    
        wsgi.py
    blog/
        __init__.py  
        admin.py  
        apps.py  
        models.py    
        tests.py    
        views.py
        search_indexes.py
        migrations/   
             __init__.py

红色字体就是search_indexes.py文件所在位置,在文件中添加如下内容


import datetime  
from haystack import indexes  
from blog.models import Post
 
#类名必须为需要检索的Model_name+Index,这里需要检索Post,所以创建PostIndex
class PostIndex(indexes.SearchIndex, indexes.Indexable):
    #创建一个text字段,有且只能有一个document=True
    text = indexes.CharField(document=True, use_template=True)  
    #对标题,内容进行搜索,以下两句貌似没啥用,不要也可以,我是没搞清楚
    author = indexes.CharField(model_attr='author')   #创建一个author字段  
    publish = indexes.DateTimeField(model_attr='publish')  #创建一个publish字段  
    def get_model(self):          #重载get_model方法,必须要有!  
        return Post  
  
    def index_queryset(self, using=None):   #重载index_..函数  
        """Used when the entire index for model is updated."""  
        return self.get_model().objects.filter(publish__lte=datetime.datetime.now())

      为什么要创建索引?索引就像是一本书的目录,可以为读者提供更快速的导航与查找。在这里也是同样的道理,当数据量非常大的时候,若要从这些数据里找出所有的满足搜索条件的几乎是不太可能的,将会给服务器带来极大的负担。所以我们需要为指定的数据(文章数据模型)添加一个索引(目录),在这里是为Post(博客文章数据模型Post)创建一个索引,索引的实现细节是我们不需要关心的。

      每个索引里面必须有且只能有一个字段为 document=True,这代表haystack和搜索引擎将使用此字段的内容作为索引进行检索(primary field)。其他的字段只是附属的属性,方便调用,并不作为检索数据。直到我自己完成一个搜索器,也没有用到这些附属属性,所以我索性就都删掉了,大家学习的时候也可以先注释掉不管。具体作用我也不明白,反正我没用上。

      注意:如果使用一个字段设置了document=True,则一般约定此字段名为text,这是在SearchIndex类里面一贯的命名,以防止后台混乱,当然名字你也可以随便改,不过不建议改。

      并且,haystack提供了use_template=True在text字段,这样就允许我们使用数据模板去建立搜索引擎索引的文件,说得通俗点就是索引里面需要存放一些什么东西,例如 Post 的 title 字段,这样我们可以通过 title 内容来检索 Post 的数据了,举个例子,假如你搜索 python ,那么就可以检索出title含有python的Post了,怎么样是不是很简单?创建索引的数据模板的路径为templates/search/indexes/yourapp/Post_text.txt(相信你已经搞定templates了,这里yourapp,我就替换成我的blog了,另外Post_text.txt,我在windows中的虚拟环境中开发,是没问题的,但是部署到服务器就出问题了,改为post_text.txt即可,相应的文件名称也改成小写),如果用了大写,报错,为了便于大家搜索,我粘贴一下错误内容

“
………
 raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain)
django.template.exceptions.TemplateDoesNotExist: search/indexes/blog/post_text.txt
”

上面这一段引号中的内容是我自己遇到的报错信息,大家忽略即可,下面这一段才是重要的:

templates/search/indexes/blog/post_text.txt文件名必须为要索引的“类名_text.txt”,其内容为

{{object.title}}
{{object.body}}

这个索引的数据模板的作用是对Post.title,Post.body这两个字段建立索引,(一般一篇文章重要信息也就只存在于题目和内容中了),当检索的时候会对这两个字段做全文检索匹配。这样我们就能搜索我们想要的内容了。

这里再补充些内容,是我后来升级博客,为其添加文章标签时候,用的是多对多关系,一篇文章有多个标签,关于“模型类可搜索字段的txt文件”的配置中,怎么让django使用haystack处理多对多和一对多搜索问题呢,使用for循环即可,具体如下:


{{object.title}}
{{object.body}}
{% for tag in object.tags.all %}
{{ tag.name}}
{% endfor %}


5、在URL配置中添加SearchView,并配置模板

在工程目录中的url中,添加SearchView的路径

re_path(r'^search/',include('haystack.urls')),

接下来就要写search.html了,(django by example书中教程里面的搜索页面和view函数,大家要删除了,因为我们自己添加了新的代码)

SearchView()视图函数默认使用的html模板路径为templates/search/search.html(相信你已经有现成的templates文件夹了)

所以需要在templates/search/下添加search.html文件,内容为


{% extends 'blog/base.html' %}
{% block content %}
<h1>站内搜索</h1>
<form action="" method="get">
        <input type="text" name="q" placeholder="请输入关键字">
        <input type="submit" id="search" value="搜索">
 </form>
{% if query %}
    <p>   
        <h4>以下搜索内容包含 "{{ query }}"</h4> 
  </p> 
  {% for result in page.object_list %}
    <h3>  
        <a href="{{ result.object.get_absolute_url }}">{{ result.object.title }}</a>  
    </h3>  
    <p>
      <a href="{{ result.object.get_absolute_url }}">{%  result.object.body  %}</a>  
    </p>
  {% empty %}  
    <p>您搜索的内容不存在,请试试其他内容</p>  
  {% endfor %}  
  
  {% if page.has_previous or page.has_next %}  
    <div>  
       {% if page.has_previous %}<a href="?q={{ query }}&page={{ page.previous_page_number }}">{% endif %}« Previous{% if page.has_previous %}</a>{% endif %}  
       |  
       {% if page.has_next %}<a href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}Next »{% if page.has_next %}</a>{% endif %}  
    </div>  
  {% endif %}  
{% else %}  
   <p>试试搜索您想查看的内容吧</p>  
{% endif %} 
 
{% endblock %}

很明显,它自带了分页。

然后为大家解释一下这个文件。首先可以看到模板里使用了的变量有 form,query,page 。下面一个个的说一下。

form,很明显,它和django里的form类是差不多的,可以渲染出一个搜索的表单,相信用过Django的Form都知道,所以也不多说了,不明白的可以去看Django文档,提供正确的参数如name="seach",method="get"以及你的action地址就OK了。。。,只有这样haystack才能够构造出相应的Form对象来进行检索,其实和django的Form是一样的,Form有一个自我检查数据是否合法的功能,haystack也一样,关于这个此篇文章不做多说,因为我也不太明白(2333)。具体细节去看文档,而且文档上关于View&Form那一节还是比较通俗易懂的,词汇量要求也不是很高,反正就连我都看懂了一些。。。

query嘛,就是我们搜索的字符串。

关于page,可以看到page有object_list属性,它是一个list,里面包含了第一页所要展示的model对象集合,那么list里面到底有多少个呢?haystack为我们提供了一个接口。我们在上面settings.py里设置过

HAYSTACK_SEARCH_RESULTS_PER_PAGE  =  7

然后根据情况可以更加深入的建立分页结构,比如我的菜鸟之志博客www.loonlog.com,就做了简单的分页功能,复杂的我也不会。

6、最后一步,手动重建索引文件

新建索引文件指令

python manage.py rebuild_index

如果是python3,以上指令会报错,

File "manage.py", line 14
    ) from exc
         ^
SyntaxError: invalid syntax

上面指令改为下面的指令即可

python3 manage.py rebuild_index

重建索引指令

update_index


好,下面运行项目,然后进入进入该url搜索一下试试吧:http://127.0.0.1:8000/search/,这个是本地调试url;

每次数据库更新后都需要更新索引,所以haystack为大家提供了一个接口,我们在上面settings.py里已经设置过了:

HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' ,这个会在你更新文章后自动更新索引文件。

试过之后,你会发现,中文不能搜索呀,下面就说一下jieba分词功能,继续看:

7、使用jieba分词,让我们用中文搜索

a、将文件whoosh_backend.py(

whoosh_backend.py文件在你的python安装目录下的libs下的site-packages下去找:比如我的是:D:\Python\install\Lib\site-packages\haystack\backends,如果是用的虚拟环境,在虚拟环境文件夹中的\Lib\site-packages\haystack\backends里面

)拷贝到app下面(我的是blog文件夹下),并重命名为whoosh_cn_backend.py,例如blog/whoosh_cn_backend.py。

之后我们对whoosh_backend.py进行修改,大概在165行附近;


from jieba.analyse import ChineseAnalyzer
#修改前:
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)

b、然后继续修改settings.py中新添加的haystack配置内容(前面注释中说后面还要改,和前面的联系一下,更容易理解)

import os  
HAYSTACK_CONNECTIONS = {  
    'default': {  
        'ENGINE': 'blog.whoosh_cn_backend.WhooshEngine',      #blog.whoosh_cn_backend便是你刚刚添加的文件  
        'PATH': os.path.join(BASE_DIR, 'whoosh_index'  
    },  
}

c、执行重建索引命令(上面第6步里面讲的),再进行搜索中文试试吧。

以上,就可以完成一个django博客项目的站内搜索功能了。


二、简单的扩展一下站内搜索功能

你是否也想让自己的检索结果页面和别人的搜索页面一样,将匹配到的文字也高亮显示呢? {% highlight %} 为我们提供了这个功能(当然不仅是这个标签,貌似还有一个HighLight类,这个自己看文档去吧,我英语差,看不明白)。

具体的语法为

{% highlight <text_block> with <query> [css_class "class_name"] [html_tag "span"] [max_length 200] %}

大概意思是为 text_block 里的 query 部分添加css_class,html_tag,而max_length 为最终返回长度,相当于 cut ,我看了一下此标签实现源码,默认的html_tag 值为 span ,css_class 值为 highlighted,max_length 值为 200,然后就可以通过CSS来添加效果。如默认时:

css中添加如下代码:

span.highlighted {  
 color: red;  
}

让搜索出来的关键字显示红色

比如上面我的search.html中,我把相关代码语句改一下

原来的写法是(前后无关的代码用省略号代替,再列出来方便大家对比看)

………
{% if query %}
    <p>   
        <h4>以下搜索内容包含 "{{ query }}"</h4> 
   </p> 
       
   {% for result in page.object_list %}
     <h3>  
       <a href="{{ result.object.get_absolute_url }}">{{ result.object.title }}</a>  
     </h3>        
     <p>
       <a href="{{ result.object.get_absolute_url }}">{{ result.object.body }}</a>  
     </p>
   {% empty %}  
     <p>No results found.</p>  
   {% endfor %}  
………

现在改为(前后无关的代码用省略号代替)

………
{% if query %}
    <p>   
        <h4>以下搜索内容包含 "{{ query }}"</h4> 
   </p>
    
   {% for result in page.object_list %}   
       <h3>  
        <a href="{{ result.object.get_absolute_url }}">{% highlight result.object.title with query %}</a>
     </h3>
     <p>
        <a href="{{ result.object.get_absolute_url }}">{% highlight result.object.body with query %} </a>  
     </p>
   {% empty %}  
     <p>您搜索的内容不存在,请试试其他内容</p>
   {% endfor %}  
………


三、深度扩展

添加完上面的代码,发现搜索词是高亮了,但是第一个搜索词前面的内容用省略号…代替,在内容显示里面,这样省略非常秒,但是标题的关键词前面也给省略了,这样,本来字数就不多的标题岂不是不完整了,岂不是损失了重要信息,

那么怎么办呢?我没有选择去看文档,可能文档的HighLight类就是用来干这个的吧,但是我选择了读highlight 标签的源码,最终还是让我实现了。

我们需要做的是复制粘贴源码,然后进行修改,而不是选择直接改源码,创建一个自己的标签。为大家奉上。添加myapp/templatetags/my_filters_and_tags.py 文件和 myapp/templatetags/highlighting.py 文件,内容如下(源码分别位于haystack/templatetags/lighlight.py 和 haystack/utils/lighlighting.py 中):

针对我的应用,我把my_filters_and_tags.pyhighlighting.py两个文件放到blog/templatetags/

直接复制粘贴吧

my_filters_and_tags.py内容如下


# encoding: utf-8  
from __future__ import absolute_import, division, print_function, unicode_literals  
  
from django import template  
from django.conf import settings  
from django.core.exceptions import ImproperlyConfigured  
from django.utils import six  
  
from haystack.utils import importlib  
  
register = template.Library()  
  
class HighlightNode(template.Node):  
    def __init__(self, text_block, query, html_tag=None, css_class=None, max_length=None, start_head=None):  
        self.text_block = template.Variable(text_block)  
        self.query = template.Variable(query)  
        self.html_tag = html_tag  
        self.css_class = css_class  
        self.max_length = max_length  
        self.start_head = start_head  
  
        if html_tag is not None:  
            self.html_tag = template.Variable(html_tag)  
  
        if css_class is not None:  
            self.css_class = template.Variable(css_class)  
  
        if max_length is not None:  
            self.max_length = template.Variable(max_length)  
  
        if start_head is not None:  
            self.start_head = template.Variable(start_head)  
  
    def render(self, context):  
        text_block = self.text_block.resolve(context)  
        query = self.query.resolve(context)  
        kwargs = {}  
  
        if self.html_tag is not None:  
            kwargs['html_tag'] = self.html_tag.resolve(context)  
  
        if self.css_class is not None:  
            kwargs['css_class'] = self.css_class.resolve(context)  
  
        if self.max_length is not None:  
            kwargs['max_length'] = self.max_length.resolve(context)  
  
        if self.start_head is not None:  
            kwargs['start_head'] = self.start_head.resolve(context)  
  
        # Handle a user-defined highlighting function.  
        if hasattr(settings, 'HAYSTACK_CUSTOM_HIGHLIGHTER') and settings.HAYSTACK_CUSTOM_HIGHLIGHTER:  
            # Do the import dance.  
            try:  
                path_bits = settings.HAYSTACK_CUSTOM_HIGHLIGHTER.split('.')  
                highlighter_path, highlighter_classname = '.'.join(path_bits[:-1]), path_bits[-1]  
                highlighter_module = importlib.import_module(highlighter_path)  
                highlighter_class = getattr(highlighter_module, highlighter_classname)  
            except (ImportError, AttributeError) as e:  
                raise ImproperlyConfigured("The highlighter '%s' could not be imported: %s" % (settings.HAYSTACK_CUSTOM_HIGHLIGHTER, e))  
        else:  
            from .highlighting import Highlighter  
            highlighter_class = Highlighter  
  
        highlighter = highlighter_class(query, **kwargs)  
        highlighted_text = highlighter.highlight(text_block)  
        return highlighted_text  
 
 
@register.tag  
def myhighlight(parser, token):  
    """ 
    Takes a block of text and highlights words from a provided query within that 
    block of text. Optionally accepts arguments to provide the HTML tag to wrap 
    highlighted word in, a CSS class to use with the tag and a maximum length of 
    the blurb in characters. 
 
    Syntax:: 
 
        {% highlight <text_block> with <query> [css_class "class_name"] [html_tag "span"] [max_length 200] %} 
 
    Example:: 
 
        # Highlight summary with default behavior. 
        {% highlight result.summary with request.query %} 
 
        # Highlight summary but wrap highlighted words with a div and the 
        # following CSS class. 
        {% highlight result.summary with request.query html_tag "div" css_class "highlight_me_please" %} 
 
        # Highlight summary but only show 40 characters. 
        {% highlight result.summary with request.query max_length 40 %} 
    """  
    bits = token.split_contents()  
    tag_name = bits[0]  
  
    if not len(bits) % 2 == 0:  
        raise template.TemplateSyntaxError(u"'%s' tag requires valid pairings arguments." % tag_name)  
  
    text_block = bits[1]  
  
    if len(bits) < 4:  
        raise template.TemplateSyntaxError(u"'%s' tag requires an object and a query provided by 'with'." % tag_name)  
  
    if bits[2] != 'with':  
        raise template.TemplateSyntaxError(u"'%s' tag's second argument should be 'with'." % tag_name)  
  
    query = bits[3]  
  
    arg_bits = iter(bits[4:])  
    kwargs = {}  
  
    for bit in arg_bits:  
        if bit == 'css_class':  
            kwargs['css_class'] = six.next(arg_bits)  
  
        if bit == 'html_tag':  
            kwargs['html_tag'] = six.next(arg_bits)  
  
        if bit == 'max_length':  
            kwargs['max_length'] = six.next(arg_bits)  
  
        if bit == 'start_head':  
            kwargs['start_head'] = six.next(arg_bits)  
  
    return HighlightNode(text_block, query, **kwargs)


highlighting.py文件内容如下

# encoding: utf-8  
  
from __future__ import absolute_import, division, print_function, unicode_literals  
  
from django.utils.html import strip_tags    
  
class Highlighter(object):  
    #默认值  
    css_class = 'highlighted'  
    html_tag = 'span'  
    max_length = 200  
    start_head = True  
    text_block = ''  
  
    def __init__(self, query, **kwargs):  
        self.query = query  
  
        if 'max_length' in kwargs:  
            self.max_length = int(kwargs['max_length'])  
  
        if 'html_tag' in kwargs:  
            self.html_tag = kwargs['html_tag']  
  
        if 'css_class' in kwargs:  
            self.css_class = kwargs['css_class']  
  
        if 'start_head' in kwargs:  
            self.start_head = kwargs['start_head']  
  
        self.query_words = set([word.lower() for word in self.query.split() if not word.startswith('-')])  
  
    def highlight(self, text_block):  
        self.text_block = strip_tags(text_block)  
        highlight_locations = self.find_highlightable_words()  
        start_offset, end_offset = self.find_window(highlight_locations)  
        return self.render_html(highlight_locations, start_offset, end_offset)  
  
    def find_highlightable_words(self):  
        # Use a set so we only do this once per unique word.  
        word_positions = {}  
  
        # Pre-compute the length.  
        end_offset = len(self.text_block)  
        lower_text_block = self.text_block.lower()  
  
        for word in self.query_words:  
            if not word in word_positions:  
                word_positions[word] = []  
  
            start_offset = 0  
  
            while start_offset < end_offset:  
                next_offset = lower_text_block.find(word, start_offset, end_offset)  
  
                # If we get a -1 out of find, it wasn't found. Bomb out and  
                # start the next word.  
                if next_offset == -1:  
                    break  
  
                word_positions[word].append(next_offset)  
                start_offset = next_offset + len(word)  
  
        return word_positions  
  
    def find_window(self, highlight_locations):  
        best_start = 0  
        best_end = self.max_length  
  
        # First, make sure we have words.  
        if not len(highlight_locations):  
            return (best_start, best_end)  
  
        words_found = []  
  
        # Next, make sure we found any words at all.  
        for word, offset_list in highlight_locations.items():  
            if len(offset_list):  
                # Add all of the locations to the list.  
                words_found.extend(offset_list)  
  
        if not len(words_found):  
            return (best_start, best_end)  
  
        if len(words_found) == 1:  
            return (words_found[0], words_found[0] + self.max_length)  
  
        # Sort the list so it's in ascending order.  
        words_found = sorted(words_found)  
  
        # We now have a denormalized list of all positions were a word was  
        # found. We'll iterate through and find the densest window we can by  
        # counting the number of found offsets (-1 to fit in the window).  
        highest_density = 0  
  
        if words_found[:-1][0] > self.max_length:  
            best_start = words_found[:-1][0]  
            best_end = best_start + self.max_length  
  
        for count, start in enumerate(words_found[:-1]):  
            current_density = 1  
  
            for end in words_found[count + 1:]:  
                if end - start < self.max_length:  
                    current_density += 1  
                else:  
                    current_density = 0  
  
                # Only replace if we have a bigger (not equal density) so we  
                # give deference to windows earlier in the document.  
                if current_density > highest_density:  
                    best_start = start  
                    best_end = start + self.max_length  
                    highest_density = current_density  
  
        return (best_start, best_end)  
  
    def render_html(self, highlight_locations=None, start_offset=None, end_offset=None):  
        # Start by chopping the block down to the proper window.  
        #text_block为内容,start_offset,end_offset分别为第一个匹配query开始和按长度截断位置  
        text = self.text_block[start_offset:end_offset]  
  
        # Invert highlight_locations to a location -> term list  
        term_list = []  
  
        for term, locations in highlight_locations.items():  
            term_list += [(loc - start_offset, term) for loc in locations]  
  
        loc_to_term = sorted(term_list)  
  
        # Prepare the highlight template  
        if self.css_class:  
            hl_start = '<%s class="%s">' % (self.html_tag, self.css_class)  
        else:  
            hl_start = '<%s>' % (self.html_tag)  
  
        hl_end = '</%s>' % self.html_tag  
  
        # Copy the part from the start of the string to the first match,  
        # and there replace the match with a highlighted version.  
        #matched_so_far最终求得为text中最后一个匹配query的结尾  
        highlighted_chunk = ""  
        matched_so_far = 0  
        prev = 0  
        prev_str = ""  
  
        for cur, cur_str in loc_to_term:  
            # This can be in a different case than cur_str  
            actual_term = text[cur:cur + len(cur_str)]  
  
            # Handle incorrect highlight_locations by first checking for the term  
            if actual_term.lower() == cur_str:  
                if cur < prev + len(prev_str):  
                    continue  
  
                #分别添上每个query+其后面的一部分(下一个query的前一个位置)  
                highlighted_chunk += text[prev + len(prev_str):cur] + hl_start + actual_term + hl_end  
                prev = cur  
                prev_str = cur_str  
  
                # Keep track of how far we've copied so far, for the last step  
                matched_so_far = cur + len(actual_term)  
  
        # Don't forget the chunk after the last term  
        #加上最后一个匹配的query后面的部分  
        highlighted_chunk += text[matched_so_far:]  
  
        #如果不要开头not start_head才加点  
        if start_offset > 0 and not self.start_head:  
            highlighted_chunk = '...%s' % highlighted_chunk  
  
        if end_offset < len(self.text_block):  
            highlighted_chunk = '%s...' % highlighted_chunk  
  
        #可见到目前为止还不包含start_offset前面的,即第一个匹配的前面的部分(text_block[:start_offset]),如需展示(当start_head为True时)便加上  
        if self.start_head:  
            highlighted_chunk = self.text_block[:start_offset] + highlighted_chunk  
        return highlighted_chunk

添加上这2个文件之后,便可以使用自己的标签 {% myhighlight %}了,使用时记得{% load my_filter_and_tags %}哦!

语法为

{% myhighlight <text_block> with <query> [css_class "class_name"] [html_tag "span"] [max_length 200] [start_head True] %}

可见我只是多添加了一个选项 start_head ,默认为True,如果设置为True 则不会省略,False则省略。

继续更改search.html

原来代码是这样的

………
{% if query %}
   <p>   
     <h4>以下搜索内容包含 "{{ query }}"</h4> 
   </p>
    
   {% for result in page.object_list %}   
     <h3>  
       <a href="{{ result.object.get_absolute_url }}">{% highlight result.object.title with query %}</a>
     </h3>
     <p>
       <a href="{{ result.object.get_absolute_url }}">{% highlight result.object.body with query %} </a>  
     </p>
   {% empty %}  
     <p>您搜索的内容不存在,请试试其他内容</p>
   {% endfor %}  
………

现在改成这样

………
{% if query %}
    <p>   
          <h4>以下搜索内容包含 "{{ query }}"</h4>
      </p> 
    {% for result in page.object_list %}   
        <h3>  
           <a href="{{ result.object.get_absolute_url }}">{% myhighlight result.object.title with query %}</a>
       </h3>
       <p>
           <a href="{{ result.object.get_absolute_url }}">{% myhighlight result.object.body with query  max_length 100 start_head False%}</a>  
       </p>
    {% empty %}  
        <p>您搜索的内容不存在,请试试其他内容</p>
    {% endfor %}  
…………

至此,完美实现搜索页面的搜索词高亮,内容部分第一个搜索词前面添加...省略掉,但题目完整显示,效果参考本站效果,当然本站的css效果有点差。


/******************************************/

网站运行了一年多,有一次我创建服务镜像备份,之后就出问题了,只能访问网站主页,不能访问内页了,开启debug,提示:

ord() expected a character, but string of length 0 found

通过网上搜索,有个说法是“Django全文检索的whoosh_index文件夹是我之前的python版本不一样 可能是造成原因,解决办法是:将whoosh_index文件夹删除重新建立索引即可“,但是我明明只是创建了镜像备份而已,那我也试着删除whoosh_index文件夹,重新创建索引,ok,问题解决了!

/******************************************/


本文参考了https://www.cnblogs.com/xuaijun/p/8027606.html,但原作者不知道是谁

Django , Python , 网站

为django博客添加站内搜索功能
http://loonlog.com/2019/10/24/django-web-search/
    觉得有用?请点击页面顶部广告支持我!

您可能感兴趣的文章

发表评论(关于评论)

评论列表,共 0 条评论

  • 暂无评论