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建立的索引庫名稱
},
}
# 搜索結(jié)果每頁顯示數(shù)量
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
# 實時更新index
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
2、安裝elasticsearch-py
haystack操作es還需要python的es驅(qū)動。兼容性見官網(wǎng)
根據(jù)官網(wǎng),選擇2.4.1版本
pip install elasticsearch==2.4.1
至此,環(huán)境搭建完成。相對應的es,es-ik,haystack,es-python的版本請保持一致。
二、新聞搜索
1.業(yè)務流程分析
- 判斷是否傳遞查詢參數(shù)
q
- 如果沒有傳遞
q
,則直接返回熱門新聞數(shù)據(jù) - 如果有傳遞,則返回查詢結(jié)果
- 分頁
2. 接口設計
- 接口說明:
類目 | 說明 |
---|---|
請求方法 | POST |
url定義 | /news/search/ |
參數(shù)格式 | 查詢參數(shù) |
- 參數(shù)說明:
參數(shù)名 | 類型 | 是否必須 | 描述 |
---|---|---|---|
q | 字符串 | 否 | 查詢的關鍵字 |
page | 整數(shù) | 否 | 頁碼 |
-
返回結(jié)果:
搜索頁面html
3.后端代碼
-
創(chuàng)建haystack數(shù)據(jù)模型
在apps/news/目錄下創(chuàng)建
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哪些數(shù)據(jù)會被 放進查詢回的模型對象中,以及通過哪些字段進行索引和查詢 """ # 這字段必須這么寫,用來告訴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): """ 返回要建立索引的數(shù)據(jù)查詢集 :param using: :return: """ return self.get_model().objects.filter(is_delete=False)
-
創(chuàng)建索引數(shù)據(jù)模板
根據(jù)上面創(chuàng)建的模型中的第一個text字段中的use_template=True參數(shù),還需要創(chuàng)建一個索引數(shù)據(jù)模板,用來告訴搜索引擎需要索引哪些字段。
在templates中創(chuàng)建文件
search/indexes/yourappname/modelname_text.txt
,所以本項目需要創(chuàng)建search/indexes/news/news_text.txt
,文件內(nèi)容如下:{{ object.title }} {{ object.digest }} {{ object.content }} {{ object.author.username }}
-
創(chuàng)建索引
按上面的步驟配置好后,就可以運行haystack的命令創(chuàng)建索引了
~$ python manage.py rebuild_index
-
視圖代碼
在news/views.py中添加如下視圖
from haystack.generic_views import SearchView class NewsSearchView(SearchView): """ 新聞搜索視圖 """ # 設置搜索模板文件 template_name = 'news/search.html' # 重寫get請求,如果請求參數(shù)q為空,返回模型News的熱門新聞數(shù)據(jù) # 否則根據(jù)參數(shù)q搜索相關數(shù)據(jù) 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="請輸入要搜索的內(nèi)容" name="q" class="search-control" value="{{ query }}"> {% else %} <input type="search" placeholder="請輸入要搜索的內(nèi)容" 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">搜索結(jié)果 <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>沒有找到你想要的找的內(nèi)容.</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 ================= */