07.3 新闻搜索
一、包安装
1、安装djangohaystack
安装
# 安装djangohaystack
# 使用的是当期最新版本 2.8.1
pip install django-haystack
配置文件
# 将Haystack添加到`INSTALLED_APPS`中
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'haystack',
'user',
'news',
'doc',
'course',
'verification'
]
# 配置搜索引擎
# 在settings.py中添加如下设置
# 全文搜索引擎haystack 配置
# 不同的搜索引擎,配置不同,详情见官方文档
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://127.0.0.1:9200/', # 此处为elasticsearch运行的服务器ip地址和端口
'INDEX_NAME': 'tzpython', # 指定elasticserach建立的索引库名称
},
}
# 搜索结果每页显示数量
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
# 实时更新index
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
2、安装elasticsearch-py
haystack操作es还需要python的es驱动。兼容性见官网
根据官网,选择2.4.1版本
pip install elasticsearch==2.4.1
至此,环境搭建完成。相对应的es,es-ik,haystack,es-python的版本请保持一致。
二、新闻搜索
1.业务流程分析
- 判断是否传递查询参数
q
- 如果没有传递
q
,则直接返回热门新闻数据 - 如果有传递,则返回查询结果
- 分页
2. 接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | POST |
url定义 | /news/search/ |
参数格式 | 查询参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
q | 字符串 | 否 | 查询的关键字 |
page | 整数 | 否 | 页码 |
-
返回结果:
搜索页面html
3.后端代码
-
创建haystack数据模型
在apps/news/目录下创建
search_indexes.py
文件,<span style="color:red">注意文件名必须使用search_indexes.py</span>,代码如下:# !/usr/bin/env python # -*- coding:utf-8 -*- from haystack import indexes from .models import News class NewsIndex(indexes.SearchIndex, indexes.Indexable): """ 这个模型的作用类似django的模型,它告诉haystack哪些数据会被 放进查询回的模型对象中,以及通过哪些字段进行索引和查询 """ # 这字段必须这么写,用来告诉haystack和搜索引擎要索引哪些字段 text = indexes.CharField(document=True, use_template=True) id = indexes.CharField(model_attr='id') title = indexes.CharField(model_attr='title') digest = indexes.CharField(model_attr='digest') content = indexes.CharField(model_attr='content') image_url = indexes.CharField(model_attr='image_url') def get_model(self): """ 返回建立索引的模型 :return: """ return News def index_queryset(self, using=None): """ 返回要建立索引的数据查询集 :param using: :return: """ return self.get_model().objects.filter(is_delete=False)
-
创建索引数据模板
根据上面创建的模型中的第一个text字段中的use_template=True参数,还需要创建一个索引数据模板,用来告诉搜索引擎需要索引哪些字段。
在templates中创建文件
search/indexes/yourappname/modelname_text.txt
,所以本项目需要创建search/indexes/news/news_text.txt
,文件内容如下:{{ object.title }} {{ object.digest }} {{ object.content }} {{ object.author.username }}
-
创建索引
按上面的步骤配置好后,就可以运行haystack的命令创建索引了
~$ python manage.py rebuild_index
-
视图代码
在news/views.py中添加如下视图
from haystack.generic_views import SearchView class NewsSearchView(SearchView): """ 新闻搜索视图 """ # 设置搜索模板文件 template_name = 'news/search.html' # 重写get请求,如果请求参数q为空,返回模型News的热门新闻数据 # 否则根据参数q搜索相关数据 def get(self, request, *args, **kwargs): query = request.GET.get('q') if not query: # 显示热门新闻 hot_news = HotNews.objects.select_related('news__tag').only('news__title', 'news__image_url', 'news_id', 'news__tag__name').filter( is_delete=False).order_by('priority', '-news__clicks') paginator = Paginator(hot_news, settings.HAYSTACK_SEARCH_RESULTS_PER_PAGE) try: page = paginator.get_page(int(request.GET.get('page'))) except Exception as e: page = paginator.get_page(1) return render(request, 'news/search.html', context={ 'page': page, 'paginator': paginator, 'query': query }) else: # 搜索 return super().get(request, *args, **kwargs) def get_context_data(self, *args, **kwargs): """ 在context中添加page变量 :param args: :param kwargs: :return: """ context = super().get_context_data(*args, **kwargs) if context['page_obj']: context['page'] = context['page_obj'] return context
-
路由
在news/urls.py中添加如下路由
path('news/search/', views.NewsSearchView.as_view(), name='news_search')
4.前端代码
-
自定义过滤器
在news/templatetags/news_template_filters.py中定义一个处理分页的过滤器
# !/usr/bin/env python # -*- coding:utf-8 -*- # create_time: 2019/7/14 # Author = '心蓝' from django import template register = template.Library() @register.filter def page_bar(page): page_list = [] if page.number != 1: page_list.append(1) if page.number - 3 > 1: page_list.append('...') if page.number - 2 > 1: page_list.append(page.number - 2) if page.number - 1 > 1: page_list.append(page.number - 1) page_list.append(page.number) if page.paginator.num_pages > page.number + 1: page_list.append(page.number + 1) if page.paginator.num_pages > page.number + 2: page_list.append(page.number + 2) if page.paginator.num_pages > page.number + 3: page_list.append('...') if page.paginator.num_pages != page.number: page_list.append(page.paginator.num_pages) return page_list
-
前端html代码
{% extends 'base/base.html' %} {% load static %} {% load news_template_filters %} {% block title %}新闻搜索{% endblock title %} {% block link %} <link rel="stylesheet" href="{% static 'css/news/search.css' %}"> {% endblock link %} {% block main_contain %} <!-- main-contain start --> <div class="main-contain "> <!-- search-box start --> <div class="search-box"> <form action="" style="display: inline-flex;"> {% if query %} <input type="search" placeholder="请输入要搜索的内容" name="q" class="search-control" value="{{ query }}"> {% else %} <input type="search" placeholder="请输入要搜索的内容" name="q" class="search-control"> {% endif %} <input type="submit" value="搜索" class="search-btn"> </form> <!-- 可以用浮动 垂直对齐 以及 flex --> </div> <!-- search-box end --> <!-- content start --> <div class="content"> {% if query %} <!-- search-list start --> <div class="search-result-list"> <h2 class="search-result-title">搜索结果 <span>{{ page.paginator.num_pages|default:0 }}</span> 页</h2> <ul class="news-list"> {% load highlight %} {% for news in page.object_list %} <li class="news-item clearfix"> <a href="{% url 'news:news_detail' news.id %}" class="news-thumbnail" target="_blank"><img src="{{ news.image_url }}" alt=""></a> <div class="news-content"> <h4 class="news-title"> <a href="{% url 'news:news_detail' news.id %}">{% highlight news.title with query %}</a> </h4> <p class="news-details">{{ news.digest }}</p> <div class="news-other"> <span class="news-type">{{ news.object.tag.name }}</span> <span class="news-time">{{ news.object.update_time }}</span> <span class="news-author">{% highlight news.object.author.username with query %}</span> </div> </div> </li> {% empty %} <li class="news-item clearfix"> <p>没有找到你想要的找的内容.</p> </li> {% endfor %} </ul> </div> <!-- search-list end --> {% else %} <!-- news-contain start --> <div class="news-contain"> <div class="hot-recommend-list"> <h2 class="hot-recommend-title">热门推荐</h2> <ul class="news-list"> {% for hotnews in page %} <li class="news-item clearfix"> <a href="#" class="news-thumbnail"> <img src="{{ hotnews.news.image_url }}"> </a> <div class="news-content"> <h4 class="news-title"> <a href="{% url 'news:news_detail' hotnews.news_id %}">{{ hotnews.news.title }}</a> </h4> <p class="news-details">{{ hotnews.news.digest }}</p> <div class="news-other"> <span class="news-type">{{ hotnews.news.tag.name }}</span> <span class="news-time">{{ hotnews.update_time }}</span> <span class="news-author">{{ hotnews.news.author.username }}</span> </div> </div> </li> {% endfor %} </ul> </div> </div> <!-- news-contain end --> {% endif %} <!-- Pagination start--> <div class="page-box" id="pages"> <div class="pagebar" id="pageBar"> <a class="al">{{ page.paginator.count|default:0 }}条</a> <!-- prev page start--> {% if page.has_previous %} {% if query %} <a href="{% url 'news:news_search' %}?q={{ query }}&page={{ page.previous_page_number }}" class="prev">上一页</a> {% else %} <a href="{% url 'news:news_search' %}?page={{ page.previous_page_number }}" class="prev">上一页</a> {% endif %} {% endif %} <!-- prev page end--> <!-- page bar start--> {% if page.has_previous or page.has_next %} {% for n in page|page_bar %} {% if query %} {% if n == '...' %} <span class="point">{{ n }}</span> {% else %} {% if n == page.number %} <span class="sel">{{ n }}</span> {% else %} <a href="{% url 'news:news_search' %}?page={{ n }}&q={{ query }}">{{ n }}</a> {% endif %} {% endif %} {% else %} {% if n == '...' %} <span class="point">{{ n }}</span> {% else %} {% if n == page.number %} <span class="sel">{{ n }}</span> {% else %} <a href="{% url 'news:news_search' %}?page={{ n }}">{{ n }}</a> {% endif %} {% endif %} {% endif %} {% endfor %} {% endif %} <!-- page bar end--> <!-- next page start--> {% if page.has_next %} {% if query %} <a href="{% url 'news:news_search' %}?q={{ query }}&page={{ page.next_page_number }}" class="prev">下一页</a> {% else %} <a href="{% url 'news:news_search' %}?page={{ page.next_page_number }}" class="prev">下一页</a> {% endif %} {% endif %} <!-- next page end--> </div> </div> <!-- Pagination end--> </div> <!-- content end --> </div> <!-- main-contain end --> {% endblock main_contain %} {#{% block otherjs %}#} {# <script src="{% static 'js/news/index.js' %}"></script>#} {#{% endblock otherjs %}#}
-
css代码
修改static/css/news/search.css如下:
/* ================= main start ================= */ #main { margin-top: 25px; min-height: 700px; } /* ========= main-contain start ============ */ #main .main-contain { width: 800px; float: left; background: #fff; } /* === search-box start === */ .main-contain .search-box { padding: 40px 50px; width: 700px; box-shadow: 1px 2px rgba(0,0,0,.1); display: inline-flex; } .main-contain .search-box .search-control { width: 600px; height: 40px; border-radius: 20px 0 0 20px; border: 1px solid #ddd; border-right: none; padding-left: 0.88em; font-size: 20px; } .main-contain .search-box .search-btn { width: 100px; height: 40px; border: 1px solid red; background: red; color: #fff; font-size: 20px; border-radius: 0 20px 20px 0; cursor: pointer; } /* === search-box end === */ /* === content start === */ /* == search-list start == */ .content .search-result-list { padding-top: 20px; } .content .search-result-list .search-result-title { padding-left: 20px; font-size: 20px; line-height: 26px; } .content .search-result-list .search-result-title span { font-weight: 700; color: #ff6620; } /* == search-list end == */ /* == news-contain start == */ .content .news-contain .hot-recommend-list { padding-top: 20px; } .hot-recommend-list .hot-recommend-title { padding-left: 20px; font-size: 20px; line-height: 26px; } .content .news-contain li { border-bottom: 1px solid #ededed; } .news-list .news-item { padding: 20px; } .news-list .news-item .news-thumbnail { float: left; width: 224px; height: 160px; margin-right: 30px; overflow: hidden; } .news-item .news-thumbnail img { width: 100%; height: 100%; transition: all 0.3s ease-out; } .news-item .news-thumbnail:hover img { transform: scale(1.1); transition: all 0.3s ease-in; } .news-list .news-item .news-content { width: 500px; height: 170px; float: right; color: #878787; font-size: 14px; } .news-item .news-content .news-title{ color: #212121; font-size: 22px; height: 52px; line-height: 26px; transition:all 0.3s ease-out; } .news-item .news-content .news-title:hover { color: #5b86db; transition:all 0.3s ease-in; } .news-item .news-content .news-details { height: 44px; line-height: 22px; margin-top: 19px; text-align: justify; } .news-item .news-content .news-other { margin-top: 30px; } .news-content .news-other .news-type { color: #5b86db; } .news-content .news-other .news-author { float: right; margin-right: 15px; } .news-content .news-other .news-time { float: right; } /* === current index start === */ #pages { padding: 32px 0 10px; } .page-box { text-align: center; /*font-size: 14px;*/ } #pages a.prev, a.next { width: 56px; padding: 0 } #pages a { display: inline-block; height: 26px; line-height: 26px; background: #fff; border: 1px solid #e3e3e3; text-align: center; color: #333; padding: 0 10px } #pages .sel { display: inline-block; height: 26px; line-height: 26px; background: #0093E9; border: 1px solid #0093E9; color: #fff; text-align: center; padding: 0 10px } #pages .point { display: inline-block; height: 26px; line-height: 26px; background: #fff; border: 1px solid #e3e3e3; text-align: center; color: #333; padding: 0 10px } .highlighted { font-weight: 700; color: #ff6620; } /* === current index end === */ /* === content end === */ /* ================= main end ================= */