为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.py和highlighting.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,但原作者不知道是谁
http://loonlog.com/2019/10/24/django-web-search/
评论列表,共 0 条评论
暂无评论