一、样式:
django-haystack在utils模块中封装了HighHighlighter用于配置搜索结果的样式展示。想要更改结果的样式,可以写个子类重写相应的方法达到效果
1.关键字高亮:
HighHighlighter为模版文件提供了搜索关键字相关的配置信息,只需在模版文件search.html中使用即可
1 # 关键字高亮分为三步: 2 # 1.模版引用 3 {% load highlight %} 4 5 # 2.改变模版变量的填充方式 6 # 改变写法后,higtlight会为原结果中的关键字部分添一个span标签,并设置class为highlighted 7 # 改变前 8 {{ item.object.title }} 9 # 改变后 10 {% highlight item.object.title with query %} 11 12 # 3.给关键字设置样式 13 <style> 14 span.highlighted { 15 color: red; 16 } 17 </style>
效果如下:
注:我这里改了关键字显示位置,原生的django-haystack的搜索结果是会以 ... 开头,然后拼接关键字以及后面部分的。
2.关键字显示位置:
通过改变HighHighlighter类的find_window方法可改变关键字显示位置,具体实现如下:
1 # 1.定义子类继承,并重写find_window方法 2 class MyHighlighter(Highlighter): 3 """自定义Highlighter,改变高亮部分前面文本长度""" 4 def find_window(self, highlight_locations): 5 best_start = 0 6 best_end = self.max_length 7 8 # First, make sure we have words. 9 if not len(highlight_locations): 10 return (best_start, best_end) 11 12 words_found = [] 13 14 # Next, make sure we found any words at all. 15 for word, offset_list in highlight_locations.items(): 16 if len(offset_list): 17 # Add all of the locations to the list. 18 words_found.extend(offset_list) 19 20 if not len(words_found): 21 return (best_start, best_end) 22 23 if len(words_found) == 1: 24 # 查找内容中只找到一个query 25 best_start = words_found[0] 26 best_end = words_found[0] + self.max_length 27 28 if best_end > len(self.text_block): 29 move_forward_steps = best_end - len(self.text_block) 30 best_start -= move_forward_steps 31 32 if best_start < 0: 33 best_start = 0 34 35 return (best_start, best_end) 36 37 # 2.django的settings文件中自定义Highlighter类 38 HAYSTACK_CUSTOM_HIGHLIGHTER = 'utils.haystack_custom.MyHighlighter'
效果如上图
二、搜索频率限制:
跟踪haystack.urls可以知道,Django-haystack的视图定义在SearchView这个类中
1 # haystack.urls.py 2 3 urlpatterns = [ 4 url(r'^$', SearchView(), name='haystack_search'), 5 ]
urls.py中将SearchView的类对象直接作为视图函数,当搜索请求来的时候,将会执行SearchView类的__call__方法,__call__方法源码如下:
1 def __call__(self, request): 2 """ 3 Generates the actual response to the search. 4 5 Relies on internal, overridable methods to construct the response. 6 """ 7 # 获取request对象 8 self.request = request 9 10 # 获取form中的参数 11 self.form = self.build_form() 12 # 校验form参数 13 self.query = self.get_query() 14 # 生成搜索结果 15 self.results = self.get_results() 16 17 # 返回视图 18 return self.create_response()
那么,可以写个子类继承SearchView类,在SearchView类的__call__方法执行之前,从request对象中获取到用户ip,然后对ip做频率限制即可实现我们的需求,实现如下:
1 # 1.写个子类继承SearchView类,重写__call__方法 2 3 # 导入这里就不说了 4 class MySearchView(SearchView): 5 """自定义search视图函数,添加IP搜索频率限制""" 6 def __call__(self, request): 7 # 获取用户IP 8 ip = request.META.get("REMOTE_ADDR", "") 9 if ip == "": 10 return super(MySearchView, self).__call__(request) 11 12 try: 13 has_search_flag = cache.get("has_search_%s" % ip) 14 except Exception as e: 15 settings.LOGGER.error(e) 16 return super(MySearchView, self).__call__(request) 17 else: 18 if has_search_flag is None: 19 cache.set("has_search_%s" % ip, 1, settings.SEARCH_FREQUENCY_FORBID_TIME) 20 return super(MySearchView, self).__call__(request) 21 22 return render(request, 'search/search.html', {"errmsg":"您的操作过于频繁,请稍后再试"}) 23 24 # 2.修改路由匹配 25 26 # 导入这里就不说了 27 urlpatterns = [ 28 # path('search', include('haystack.urls')), # 全文检索框架 29 path('search', MySearchView(), name="haystack_search"), # 全文检索框架 30 ]
------------------------------
更新:当搜索结果分页后,用户点击下一页或上一页,是会改变page参数重新发起请求的,这里也会视为频繁操作,那就有问题了。
于是逻辑修改如下:将缓存中的从固定的1改成存关键字,下次搜索关键字变更了的情况下才加以限制,实现如下:
1 class MySearchView(SearchView): 2 """自定义search视图函数,添加IP搜索频率限制""" 3 def __call__(self, request): 4 # 获取用户IP 5 search_q = request.GET.get("q", None) 6 if search_q is None: 7 return render(request, 'search/search.html', {"errmsg":"参数有误"}) 8 9 ip = request.META.get("REMOTE_ADDR", "") 10 if ip == "": 11 return super(MySearchView, self).__call__(request) 12 13 try: 14 has_search_flag = cache.get("has_search_%s" % ip) 15 except Exception as e: 16 settings.LOGGER.error(e) 17 return super(MySearchView, self).__call__(request) 18 else: 19 if has_search_flag: 20 if has_search_flag != search_q: 21 return render(request, 'search/search.html', {"errmsg":"您的操作过于频繁,请稍后再试"}) 22 else: 23 return super(MySearchView, self).__call__(request) 24 else: 25 cache.set("has_search_%s" % ip, search_q, settings.SEARCH_FREQUENCY_FORBID_TIME) 26 return super(MySearchView, self).__call__(request)