7.3 django项目-新闻博客系统之新闻搜索3

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驱动。兼容性见官网

1563194487143.png

根据官网,选择2.4.1版本

pip install elasticsearch==2.4.1

至此,环境搭建完成。相对应的es,es-ik,haystack,es-python的版本请保持一致。

二、新闻搜索

1.业务流程分析

  • 判断是否传递查询参数q
  • 如果没有传递q,则直接返回热门新闻数据
  • 如果有传递,则返回查询结果
  • 分页

2. 接口设计

  1. 接口说明:
类目 说明
请求方法 POST
url定义 /news/search/
参数格式 查询参数
  1. 参数说明:
参数名 类型 是否必须 描述
q 字符串 查询的关键字
page 整数 页码
  1. 返回结果:

    搜索页面html

3.后端代码

  1. 创建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)
    
  2. 创建索引数据模板

    根据上面创建的模型中的第一个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 }}
    
  3. 创建索引

    按上面的步骤配置好后,就可以运行haystack的命令创建索引了

    ~$ python manage.py rebuild_index
    
  4. 视图代码

    在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
    
  5. 路由

    在news/urls.py中添加如下路由

        path('news/search/', views.NewsSearchView.as_view(), name='news_search')
    

4.前端代码

  1. 自定义过滤器

    在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
    
  2. 前端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 %}#}
    
  3. 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 ================= */
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容