django by example 實踐--blog 項目(三)




點我查看本文集的說明及目錄。


本項目相關(guān)內(nèi)容( github傳送 )包括:

實現(xiàn)過程

CH1 創(chuàng)建一個博客應(yīng)用

CH2 使用高級特性增強博客功能

CH3 擴展博客功能

項目總結(jié)及改進

使用類視圖實現(xiàn) blog list 和 detail 視圖


CH3 擴展 blog 應(yīng)用



上一章我們學(xué)習(xí)了表單基礎(chǔ)知識及如何在項目中集成第三方應(yīng)用。本章的要點包含:

  • 創(chuàng)建自定義模板標簽和過濾器

  • 添加 sitemap 和文章 feed

  • 使用 Solr 和 Haystack 創(chuàng)建搜索引擎

    ?

創(chuàng)建自定義模板標簽和過濾器


Django 提供許多內(nèi)置標簽(比如 {% if %} 、{% block %} 等),我們已經(jīng)在前面的開發(fā)過程中使用了一些。下面的鏈接包括 Django 所有的內(nèi)置標簽和過濾器:https://docs.djangoproject.com/en/1.11/ref/templates/builtins/。

Django 允許我們創(chuàng)建自定義標簽來實現(xiàn)自定義動作。如果我們需要實現(xiàn) Django 內(nèi)置模板標簽無法覆蓋的功能時可以創(chuàng)建自定義標簽。

創(chuàng)建自定義模板標簽

Django 提供以下幫助函數(shù)幫助我們快速創(chuàng)建自定義模板標簽:

  • simple_tag: 處理數(shù)據(jù)并返回字符串

  • inclusion_tag: 處理數(shù)據(jù)并返回渲染的模板

  • assignment_tag: 處理數(shù)據(jù)并在 context 中設(shè)置變量

    ?

譯者注:

Django 1.9 版本之后,可以使用 simple_tag 實現(xiàn) assignment_tag 的功能,因此,現(xiàn)在已經(jīng)棄用 assignment_tag 。

模板標簽必須位于 Django 應(yīng)用內(nèi)。

在 blog 應(yīng)用根目錄下創(chuàng)建名為 templatetags 的目錄,并在新建目錄下創(chuàng)建名為__init__.py的空文件,然后在與該空文件相同的目錄下創(chuàng)建名為 blog_tags.py 的文件。目錄及文件創(chuàng)建完成后的 blog 應(yīng)用文件結(jié)構(gòu)是這樣的:

blog_side_constructure.png

文件的名稱很重要,你將在模板中使用這個名稱加載自定義模板標簽。

我們首先創(chuàng)建一個 simple_tag ,該標簽獲取 blog 發(fā)布的文章數(shù)量。編輯 blog_tags.py 文件,添加以下代碼:

from django import template

register = template.Library()

from ..models import Post


@register.simple_tag
def total_posts():
    return Post.published.count()

我們已經(jīng)創(chuàng)建了一個返回發(fā)布文章數(shù)量的簡單模板標簽。每個模板標簽?zāi)K都需要包含一個名為 register 的變量,這個變量是 template.Library 實例,用于注冊自定義模板標簽或過濾器。然后我們使用 python 函數(shù)定義一個名為 total_posts 的模板標簽,并使用@register.simple_tag裝飾器注冊標簽。Django 將使用函數(shù)的名稱作為標簽名稱。如果需要使用其它名稱,可以將 @register.simple_tag 更改為 @register.simple_tag(name='my_tag')

注意:

添加模板標簽?zāi)K后,需要重啟 Django開發(fā)服務(wù)器才能使用新的自定義模板標簽和過濾器。

在使用自定義模板標簽之前,需要使用{% load %}標簽進行加載。打開 blog/base.html 模板,并在開始的位置添加 {% load blog_tags %} 來加載模板標簽?zāi)K,然后通過添加{% total_posts %}來使用創(chuàng)建的標簽表示所有文章,模板最終看起來是這樣的:

{% load blog_tags %}
{% load static %}

<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}{% endblock %}</title>
    <link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
<div id="content">
    {% block content %}
    {% endblock %}
</div>
<div id="sidebar">
    <h2>My blog</h2>
    <p>This is my blog. I've written {% total_posts %} posts so far.</p>
</div>
</body>
</html>

我們需要重啟開發(fā)服務(wù)器來追蹤將添加到項目的新文件,按 Ctrl+C 停止開發(fā)服務(wù)器并運行以下命令啟動開發(fā)服務(wù)器:

python manage.py runserver

在瀏覽器中打開 http://127.0.0.1:8000/blog/ ,我們可以在右側(cè)邊欄看到所有已發(fā)布的文章信息

blog_sp_tag.png

譯者注:

由于模板標簽的 queryset 使用的是 published 管理器,因此需要設(shè)置文章的狀態(tài)(通過 admin網(wǎng)站或者 python manage.py shell)來得到要想的結(jié)果。

自定義標簽的強大之處在于可以處理任何數(shù)據(jù)以及可以添加到任意模板,我們可以在模板中展示 Queryset 或者 任何數(shù)據(jù)的處理結(jié)果。

現(xiàn)在,我們將創(chuàng)建另一個模板標簽(在 blog 邊欄展示最新文章)。這次我們使用 inclusion 標簽。inclusion 標簽可以對內(nèi)容進行渲染。編輯 blog_tags.py 文件并添加以下代碼:

@register.inclusion_tag('blog/post/latest_posts.html')
def show_latest_posts(count=5):
    latest_posts = Post.published.order_by('-publish')[:count]
    return {'latest_posts': latest_posts}

在這段代碼中,我們使用register.inclusion_tag注冊模板標簽并設(shè)置返回數(shù)據(jù)渲染所用的模板 blog/post/latest_posts.html 。模板標簽可以輸入可選的 count 參數(shù)(默認值為 5 )。我們使用這個變量來限制 Post.published.order_by('-publish') 結(jié)果的數(shù)量。注意,這個函數(shù)返回變量字典而不是單個值。Inclusion標簽需要返回值的字典作為特定模板的內(nèi)容。我們剛剛創(chuàng)建的模板可以傳入可選的文章數(shù)量并這樣進行顯示 {% show_latest_posts 3 %}

現(xiàn)在,在 templates/blog/post 目錄下新建一個名為 latest_posts.html 模板文件并添加以下代碼:

<ul>
    {% for post in latest_posts %}
        <li>
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
        </li>
    {% endfor %}
</ul>

這里,我們使用 latest_posts 展示無序的文章列表并通過標簽返回?,F(xiàn)在,編輯 blog/base.html 模板并通過添加新的模板標簽展示最新的 3 篇文章。sidebar 應(yīng)該是這樣的:

<div id="sidebar">
    <h2>My blog</h2>
    <p>This is my blog. I've written {% total_posts %} posts so far.</p>
    <h3>Latests posts</h3>
    {% show_latest_posts 3 %}
</div>

通過傳入要展示的文章數(shù)量調(diào)用模板標簽,模板已經(jīng)對給定內(nèi)容進行了渲染。

現(xiàn)在回到瀏覽器刷新一下頁面,邊欄現(xiàn)在是這樣的了:

blog_sp_in_tag.png

最后,我們再次使用 simple_tag 創(chuàng)建一個 assignment_tag 功能的標簽,該功能是將返回的值保存在一個變量中。我們將創(chuàng)建一個標簽來展示評論最多的文章。編輯 blog_tags.py 文件,并添加以下代碼:

from django.db.models import Count

@register.simple_tag
def get_most_commented_posts(count=5):
    return Post.published.annotate(total_comments=Count('comments')).order_by(
        '-total_comments')[:count]

譯者注:

由于 assignment_tags 已經(jīng)廢棄,這里使用 simple_tag 代替原文中的 assignment_tags。

Queryset 使用 annotate() 函數(shù)進行聚合查詢(使用 Count 聚合函數(shù))。創(chuàng)建 total_comments 字段保存每篇文章評論數(shù)量的 queryset 并通過該字段對查詢結(jié)果進行排序。我們還提供可選的 count 參數(shù)限制返回對象的數(shù)量。

除了 Count ,Django 還提供 Avg 、Max 、Min 、 Sum 等聚合函數(shù)。我們可以通過閱讀官網(wǎng)資料對聚合函數(shù)進行更多了解:https://docs.djangoproject.com/en/1.11/topics/db/aggregation/。

編輯 blog/base.html 模板并在 sidebar 中添加以下代碼:

    <h3>Most commented posts</h3>
    {% get_most_commented_posts as most_commented_posts %}
    <ul>
        {% for post in most_commented_posts %}
        <li>
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
        </li>
        {% endfor %}
    </ul>

這里最大的區(qū)別在于使用 {% template_tag as variable %} 。在代碼中,我們使用了 {% get_most_commented_posts as most_commented_posts %} 。這樣可以將標簽結(jié)果保存到一個名為most_commented_posts 的變量中,然后使用無序列表對返回的文章進行展示。

現(xiàn)在,刷新瀏覽器,我們將看到這樣的頁面:

blog_3_tag.png

我們可以閱讀官方文檔了解更多關(guān)于模板標簽的內(nèi)容:https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/。

創(chuàng)建自定義模板過濾器

Django 內(nèi)置許多模板過濾器來幫助我們修改模板中的變量。它們是包含一個或兩個輸入(第一個是要操作的對象,第二個是可選參數(shù))的 python 函數(shù)。它們返回一個可以被其它過濾器處理的值。一個過濾器看起來是這樣的 {{ variable|my_filter}} 或者可以傳遞參數(shù)的 {{ variable|my_filter: 'foo' }} 。我們可以為一個變量使用任意數(shù)量的過濾器 {{ variable|filter1|filter2 }} ,它們中的每一個將使用前一個過濾器的輸出作為輸入。

我們將創(chuàng)建一個自定義過濾器在 blog 文章中使用 markdown 語法然后在模板中將文章內(nèi)容轉(zhuǎn)換為 HTML 。markdown 是一個簡單易用的文本格式語法并且可以轉(zhuǎn)換為 HTML 。我們可以了解它們的基本格式:https://daringfireball.net/projects/markdown/basics

首先通過 pip 安裝 Python markdown 模塊:

pip install Markdown

然后編輯 blog_tags.py 文件,添加以下代碼:

from django.utils.safestring import mark_safe
import markdown


@register.filter(name='markdown')
def markdown_format(text):
    return mark_safe(markdown.markdown(text))

模板過濾器的注冊方法與模板標簽的注冊方法相同。為避免函數(shù)名與 markdown 模塊混淆,我們將函數(shù)命名為 markdown_format 并將過濾器命名為 markdown 以便在模板中使用 {{ variable|markdown }} 。Django 不對過濾器生成的 HTML 代碼進行轉(zhuǎn)義。因此,我們使用 Django 提供的 mark_safe 函數(shù)對結(jié)果進行轉(zhuǎn)義來保證可以在模板中進行渲染。默認情況下,Django 不信任任何 HTML 代碼并在輸出之前進行轉(zhuǎn)義。變量被標記為 safe 是唯一的例外。這個特性可以防止 Django 生成存在潛在危險的 HTML ,并允許用戶在知道代碼可以返回安全的 HTML 時不進行轉(zhuǎn)義。

現(xiàn)在,在 post/list.html 和 post/detail.html 中的 {% extends %} 后面加載模板標簽:

{% load blog_tags %}

在 post/detail.html 中使用:

{{ post.body|markdown }}

代替

{{ post.body|linebreaks }}

然后,在post/list.html中,使用:

{{ post.body|markdown|truncatechars_html:30 }}

代替

{{ post.body|truncatewords:30|linebreaks }}

truncatechars_html 過濾器將截取一定字符的字符串,避免未關(guān)閉的 HTML 標簽。

現(xiàn)在,在瀏覽器中打開 http://127.0.0.1:8000/admin/blog/post/add/ 頁面并添加以下內(nèi)容的文章:

This is a post formatted with markdown

*This is emphasized* and **this is more emphasized**.

Here is a list:

- One
- Two
- Three

And a [link to the Django website](https://www.djangoproject.com/)
add_markdown.png

打開瀏覽器,我們來看下這篇文章如何渲染,我們應(yīng)該看到這樣的效果:

markdown_page.png

我們可以看到,自定義模板過濾器可以有效的自定義格式,通過以下鏈接可以了解更多內(nèi)容:https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/。

添加 sitemap


Django 內(nèi)置 sitemap 框架(動態(tài)生成網(wǎng)站的 sitemaps )。sitemap 是一個 XML文件,用于向搜索引擎提供網(wǎng)站上的頁面、相關(guān)性及更新頻率。使用 sitemap ,可以幫助網(wǎng)絡(luò)搜索器檢索網(wǎng)站內(nèi)容。

Django sitemap 框架依賴 django.contrib.sites(允許項目的特定網(wǎng)站與對象建立聯(lián)系)。用于處理使用一個Django 項目運行多個網(wǎng)站的情況。安裝 sitemap 框架,需要在項目中同時激活 sites 和 sitemap 兩個應(yīng)用。編輯項目的 settings.py 文件,在 INSTALLED_APPS 中添加 django.contrib.site 和 django.contrib.sitemaps ,然后設(shè)置 site ID :

SITE_ID = 1

# Application definition

INSTALLED_APPS = ['django.contrib.admin', 
                  'django.contrib.auth',
                  'django.contrib.contenttypes',
                  'django.contrib.sessions',
                  'django.contrib.messages',
                  'django.contrib.staticfiles',
                  'blog', 
                  'taggit',
                  'django.contrib.sites',
                  'django.contrib.sitemaps']

現(xiàn)在,運行以下命令將 sites 應(yīng)用的模型同步到數(shù)據(jù)庫:

python manage.py migrate

我們將看到這樣的輸出:

Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions, sites, taggit
Running migrations:
  Applying sites.0001_initial... OK
  Applying sites.0002_alter_domain_unique... OK

sites 應(yīng)用的模型現(xiàn)在已經(jīng)同步到數(shù)據(jù)庫了。現(xiàn)在,我們在 blog 應(yīng)用目錄下新建一個名為 sitemaps.py 的文件,打開文件并添加以下代碼:

from django.contrib.sitemaps import Sitemap

from .models import Post


class PostSitemap(Sitemap):
    changefreq = 'weekly'
    priority = 0.9

    def items(self):
        return Post.published.all()

    def lastmod(self, obj):
        return obj.publish

我們繼承 sitemap 模塊的 Sitemap 類創(chuàng)建自定義 sitemap 。changefreq 和 priority 屬性表示文章頁面更新頻率和與網(wǎng)站的相關(guān)性(最大值為 1 )。items 方法為 sitemap 返回對象的queryset。默認情況下,Django 調(diào)用模型的 get_absolute_url() 方法來獲取模型對象的 URL ,我們在第一章的 Post 模型中定義了 get_absolute_url() 方法。如果希望指定每篇文章的 URL ,那么可以為 sitemap 類添加 location 方法。lastmod 方法獲得 item() 返回的每個對象并返回文章的最新修改時間。changefreq 和 priority 可以是方法或?qū)傩?。sitemap 更多的內(nèi)容詳見:https://docs.djangoproject.com/en/1.11/ref/contrib/sitemaps/。

最后,我們只需要添加 sitemap URL,編輯項目的 urls.py 文件,并修改成這樣:


from django.conf.urls import url, include
from django.contrib import admin
from django.contrib.sitemaps.views import sitemap

from blog.sitemaps import PostSitemap

sitemaps = {'post': PostSitemap, }

urlpatterns = [url(r'^admin/', admin.site.urls),  
               url(r'^blog/',include('blog.urls',namespace='blog',
                                     app_name='blog')),
               url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
                   name='django.contrib.sitemaps.views.sitemap')]

現(xiàn)在,我們導(dǎo)入了需要的模塊并定義了一個 sitemaps 字典,定義了一個匹配 sitemap.xml 并使用 sitemap 視圖的 URL 模式,sitemaps 字典傳入 sitamap 視圖,現(xiàn)在在瀏覽器中打開 http://127.0.0.1:8000/sitemap.xml ,我們將看到這樣的 XML :

sitemaps.png

每篇文章的 URL 通過調(diào)用它的 get_absolute_url() 獲得。lastmod 屬性對應(yīng)我們在 sitemap 中指定的文章的 publish 字段,changefreq 和 priority 屬性也是從 PostSitemap 中獲得的。我們可以看到,創(chuàng)建 URL 時使用的域名為 example.com 。這個域名來自于數(shù)據(jù)庫中存儲的一個 Site 對象,我們同步 sites 框架時會創(chuàng)建默認對象 example.com?,F(xiàn)在在瀏覽器中打開 http://127.0.0.1:8000/admin/sites/site/ ,你將看到:

site_domain.png

這是 sites 框架的域名列表視圖。我們可以設(shè)置 sites 框架使用的域名或主機。為了生成本地環(huán)境的 URL ,將域名更改為 127.0.0.0:8000 :

domain_to_local.png

為了便于開發(fā),我們將域名指定為本地主機。在生產(chǎn)環(huán)境中,則需要使用自己的域名。

為blog文章創(chuàng)建feeds

Django 內(nèi)置 syndication feed 框架(動態(tài)生成 RSS 或 Atom feeds ),這個框架的使用方法與 sitemap 框架類似。

在 blog 應(yīng)用目錄下創(chuàng)建一個名為 feeds.py 的文件,并添加以下代碼:

from django.contrib.syndication.views import Feed
from django.template.defaultfilters import truncatewords

from .models import Post


class LatestPostsFeed(Feed):
    title = 'My blog'
    link = '/blog'
    description = 'New posts of my blog'

    def items(self):
        return Post.published.all()[:5]

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return truncatewords(item.body, 30)

首先,我們創(chuàng)建 LatestPostFeed 類,它是 syndication 框架 Feed 類的子類。其中 title 、 link 和 description 屬性分別與 RSS 的 <title> 、 <link> 和 <discription> 對應(yīng)。

items() 方法獲取 feed 包含的對象。這個 feed 只包含最新的 5 篇文章。item_title() 和 item_description() 方法獲得 items() 返回的每個對象并為每個對象返回名稱和描述。item_description() 使用內(nèi)置的模板過濾器 truncateword 截取文章的前 30 個詞作為描述。

現(xiàn)在,編輯 blog 應(yīng)用的 urls.py 文件,導(dǎo)入剛剛創(chuàng)建的 LatestPostFeed ,并在新的 URL 模式中進行實例化:

from django.conf.urls import url

from . import views
from .feeds import LatestPostsFeed

urlpatterns = [url(r'^$', views.post_list, name='post_list'),  # post views
    # url(r'^$', views.PostListView.as_view(), name='post_list'),
    url(r'^tag/(?P<tag_slug>[-\w]+)/$', views.post_list,
        name='post_list_by_tag'),
    url(r'(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)/$',
        views.post_detail, name='post_detail'),
    url(r'(?P<post_id>\d+)/share/$', views.post_share, name='post_share'),
    url(r'^feed/$', LatestPostsFeed(), name='post_feed'), ]

在瀏覽器中打開 http://127.0.0.1:8000/blog/feed/ ,我們應(yīng)該可以看到最新五篇文章的 RSS feed :

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>My blog</title><link>http://127.0.0.1:8000/blog</link><description>New posts of my blog</description><atom:link rel="self" ></atom:link><language>en-us</language><lastBuildDate>Tue, 20 Mar 2018 06:53:39 +0000</lastBuildDate><item><title>MarkDown post</title><link>http://127.0.0.1:8000/blog/2018/03/20/markdown-post/</link><description>This is a post formatted with markdown *This is emphasized* and **this is more emphasized**. Here is a list: - One - Two - Three And a [link to the ...</description><guid>http://127.0.0.1:8000/blog/2018/03/20/markdown-post/</guid></item><item><title>Another post</title><link>http://127.0.0.1:8000/blog/2018/03/19/another-post/</link><description>post boday</description><guid>http://127.0.0.1:8000/blog/2018/03/19/another-post/</guid></item><item><title>Meet Django</title><link>http://127.0.0.1:8000/blog/2018/03/19/meet-django/</link><description>Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, ...</description><guid>http://127.0.0.1:8000/blog/2018/03/19/meet-django/</guid></item><item><title>New title</title><link>http://127.0.0.1:8000/blog/2018/03/19/one-more-post/</link><description>Post body.</description><guid>http://127.0.0.1:8000/blog/2018/03/19/one-more-post/</guid></item><item><title>Why Django?</title><link>http://127.0.0.1:8000/blog/2018/03/19/why-django/</link><description>With Django, you can take Web applications from concept to launch in a matter of hours. Django takes care of much of the hassle of Web development, so you can ...</description><guid>http://127.0.0.1:8000/blog/2018/03/19/why-django/</guid></item></channel></rss>

如果在一個 RSS 客戶端中打開同一個 URL ,你將看到一個附帶用戶友好接口的 feed 。

最后一步是為 blog 邊欄添加 feed 訂閱鏈接。打開 blog/base.html 模板并在 sidebar 所有文章數(shù)量下面一行添加以下代碼:

<p><a href="{% url 'blog:post_feed' %}">Subscribe to my RSS feed</a></p>

在瀏覽器中打開http://127.0.0.1:8000/blog/ ,我們在邊欄中可以看到跳轉(zhuǎn)到 blog 的 feed 的新鏈接:

feed_link.png

使用 Solr 和 Haystack 添加搜索引擎


現(xiàn)在,我們?yōu)?blog 添加搜索功能。Django ORM 提供 icontains 過濾器實現(xiàn)不區(qū)分大小寫的查詢。例如,可以使用下面的查詢找到正本包含 framework 的文章:

Post.objects.filter(body__icontains='framework')

然而,如果需要更加強大的搜索功能,則需要使用搜索引擎。我們將使用 Solr 結(jié)合 Django 創(chuàng)建 blog 的搜索引擎。Solr 是一個非常流行的開源搜索框架,它可以提供全文搜索、動態(tài)聚類及其它高級搜索功能。

為了在項目中集成 Solr ,我們將使用 Haystack 。Haystack 是一個 Django 應(yīng)用,為多個搜索引擎提供抽象層。它提供一個與 Django Queryset 類似的搜索 API 。我們從安裝及配置 Solr 和 Haystack 開始。

安裝 Solr

我們需要 Java 運行環(huán)境 1.7 及以上版本來安裝 Solr ??梢允褂?java -version 命令來查看電腦上的 java 版本。輸出可能有所變化,但是至少需要安裝 1.7 版本。

java version "9.0.1"
Java(TM) SE Runtime Environment (build 9.0.1+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

如果沒有安裝 Java 或者版本低于要求,可以從以下網(wǎng)址下載 JDK :http://www.oracle.com/technetwork/java/javase/downloads/index.html。

檢查完 Java 版本后,從http://archive.apache.org/dist/lucene/solr/ 下載 Solr 4.10.4 。解壓下載的文件并轉(zhuǎn)到 Slor 安裝目錄的 example 目錄( cd solr-4.10.4/example/ )。這個目錄包含一個可用的 Solr 配置。在這個目錄中使用以下命令來使用內(nèi)置的 Jetty web 服務(wù)器運行 Solr :

java -jar start.jar

打開瀏覽器并輸入以下網(wǎng)址 http://127.0.0.1:8983/solr/ ,將看到下面的內(nèi)容:

solr_init.png

這是 Solr 的管理平臺。這個平臺靜態(tài)顯示使用情況并允許我們管理搜索 backend、檢查索引數(shù)據(jù)及進行查詢。

創(chuàng)建 Solr 內(nèi)核

Solr 允許在內(nèi)核中隔離實例。每個 Solr 內(nèi)核是一個 Lucene 實例,Lucene 實例包括一個 Solr 配置、一個數(shù)據(jù)事務(wù)和其它需要用到的配置。Solr 允許我們直接創(chuàng)建并管理內(nèi)核。示例配置包含一個名為 collection1 的內(nèi)核,點擊Core Admin 目錄按鈕可以看到這個內(nèi)核的信息,如下圖所示:

solr_core.png

我們將為 blog 應(yīng)用創(chuàng)建一個內(nèi)核。首先需要為內(nèi)核創(chuàng)建一個文件結(jié)構(gòu),在 color-4.10.4/example/solr 目錄中創(chuàng)建一個名為 blog 的目錄,然后在該文件夾中創(chuàng)建下面的空文件及目錄:

solr_structure.png

在 solrconfig.xml 中添加下面的 XML 代碼:

<?xml version="1.0" encoding="utf-8" ?>
<config>
  <luceneMatchVersion>LUCENE_36</luceneMatchVersion>
  <requestHandler name="/select" class="solr.StandardRequestHandler" default="true" />
  <requestHandler name="/update" class="solr.UpdateRequestHandler" />
  <requestHandler name="/admin" class="solr.admin.AdminHandlers" />
  <requestHandler name="/admin/ping" class="solr.PingRequestHandler">
    <lst name="invariants">
      <str name="qt">search</str>
      <str name="q">*:*</str>
    </lst>
  </requestHandler>
</config>

可以從這一章附帶的代碼中拷貝這個文件。這是一個 Solr 最小配置,編輯 schema.xml 文件并添加以下 XML 代碼:

<?xml version="1.0" ?>
<schema name="default" version="1.5">
</schema>

這是一個空的 schema 。這個 schema 定義了在搜索引擎中進行索引的數(shù)據(jù)的字段及類型。稍后我們將使用自定義 schema 。

現(xiàn)在,點擊 Core Amdin 目錄按鈕然后點擊 Core Core 按鈕。你將看到一個為內(nèi)核設(shè)置信息的表單:

solr_add_core.png

使用以下數(shù)據(jù)填充表單:

  • name: blog
  • instanceDir: blog
  • dataDir :data
  • config :solrconfig.xml
  • schema :schema.xml

name 字段為這個內(nèi)核的名稱,instanceDir 字段為內(nèi)核目錄,dataDir 為存儲索引數(shù)據(jù)的目錄,config 字段為Solr XML 配置文件的名稱,schema 字段為 Solr XML 數(shù)據(jù)事務(wù)文件的名稱。

現(xiàn)在,點擊 Add Core 按鈕,如果可以看到以下信息,則表示內(nèi)核成功添加到 Solr 中了:

solr_added_core.png

安裝 Haystack

在 Django 中使用 Solr 需要安裝 Haystack 。通過 pip 安裝 Haystack :

pip install django-haystack

譯者注:

python 3.6 + Django 1.11

第一次運行 pip install django-haystack 安裝失敗,錯誤信息為:Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/6z/46z01qw53pzg721yzh80k64h0000gn/T/pip-build-trx_9mq7/django-haystack/。

運行 pip install django-haystack==2.4.0 可以安裝成功。但是后面運行 python manage.py build_solr_schema 會報錯。

運行 pip uninstall django-haystack 卸載 haystack,

然后運行 pip install django-haystack 安裝成功。

Haystack 可以與多個搜索引擎進行交互。為了使用 Solr 引擎,我們還需要安裝 pysolr 模塊。運行下面的命令進行安裝:

pip install pysolr

安裝完 django-haystack 和 pysolr 之后,我們需要在項目中激活 Haystack ,打開項目的 settings.py 文件并將haystack 添加到 INSTALLED_APPS 中:


INSTALLED_APPS = ['django.contrib.admin',
                  'django.contrib.auth',
                  'django.contrib.contenttypes',
                  'django.contrib.sessions',
                  'django.contrib.messages',
                  'django.contrib.staticfiles',
                  'blog',
                  'taggit',
                  'django.contrib.sites',
                  'django.contrib.sitemaps',
                  'haystack']

我們需要為 haystack 定義搜索引擎 backends ??梢酝ㄟ^在 settings.py 中設(shè)置 HAYSTACK_CONNECTIONS 進行配置實現(xiàn),在 settings.py 文件中添加以下代碼:

# search engine setting
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
        'URL': 'http://127.0.0.1:8983/solr/blog'
    },
}

注意,URL 指向 blog 內(nèi)核,Haystack 現(xiàn)在已經(jīng)安裝好了,我們可以使用 Solr 了。

創(chuàng)建索引

現(xiàn)在,我們需要注冊保存到搜索引擎中的模型。Haystack 的方便之處在于可以通過在應(yīng)用下創(chuàng)建一個 search_indexes.py 文件并在文件注冊模型。在 blog 應(yīng)用目錄下新建名為 search_indexes.py 的文件,并添加以下代碼:

from haystack import indexes

from .models import Post


class PostIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    publish = indexes.DateTimeField(model_attr='publish')

    def get_model(self):
        return Post

    def index_queryset(self, using=None):
        return self.get_model().published.all()

這是 Post 模型的自定義 SearchIndex 。我們使用這個 Index 告訴 Haystack 模型中的哪些數(shù)據(jù)需要在搜索引擎中創(chuàng)建索引。這個索引通過繼承 indexes.SearchIndex 和 indexes.Indexable 實現(xiàn)。每個 SearchIndex 都需要其中的一個字段設(shè)置 document=True ,比較方便的方法是將具備該屬性的字段命名為 text ,這個字段為第一搜索字段。我們通過設(shè)置 use_template=True 屬性告訴 Haystack 這個字段將渲染為數(shù)據(jù)模板(用于創(chuàng)建搜索引擎索引的文檔)。我們通過 model_attr 設(shè)置將 publish 字段映射到 Post 模型的 publish 字段。這個字段將通過創(chuàng)建索引的 Post 對象的 publish 字段的內(nèi)容進行索引。

這樣一個額外字段將有助于為索引提供額外的過濾器。get_model() 方法返回保存到索引中的文檔模型。index_queryset() 方法返回索引對象的 queryset 。注意,這里只包含狀態(tài)為 published 的文章。

現(xiàn)在,在 blog/templates 目錄下創(chuàng)建 search/indexes/blog/post_text.txt 文件,并添加以下代碼:

{{ object.title }}
{{ object.tags.all|join:',' }}
{{ object.body }}

這是索引 text 字段文檔模板的默認路徑 。Haystack 使用應(yīng)用名稱和模型名稱動態(tài)創(chuàng)建路徑。檢索一個對象時,Haystack 將基于該模板創(chuàng)建一個文檔,然后在 Solr 搜索引擎為文檔創(chuàng)建索引。

現(xiàn)在,我們已經(jīng)有了一個自定義搜索索引,接下來需要創(chuàng)建一個合適的 Solr schema 。Solr 配置基于 XML ,因此我們需要為索引數(shù)據(jù)生成一個 XML schema 。Haystack 提供一種動態(tài)生成 schema 的方法(基于我們的搜索索引)。打開 terminal 并運行以下命令:

python manage.py build_solr_schema  

將看到一個 XML 輸出??匆幌?XML 文件的底部,我們將看到 Haystack 為 PostIndex 生成的字段:

<field name="text" type="text_en" indexed="true" stored="true" multiValued="false" />
<field name="publish" type="date" indexed="true" stored="true" multiValued="false" />

這個 XML 是為 Solr 創(chuàng)建索引數(shù)據(jù)的 schema 。將整個 XML 輸出(從最初的 <?xml version="1.0" ?> 到最終的</schema> )拷貝到 Solr 應(yīng)用 example/solr/blog/conf/schema.xml 文件中。本章附帶的代碼中已經(jīng)包含該 schema.xml 文件,所以也可以直接從代碼中拷貝文件。

譯者注:

schema.xml 文件內(nèi)容為:

<?xml version="1.0" ?>
<!--
 Licensed to the Apache Software Foundation (ASF) under one or more
 contributor license agreements.  See the NOTICE file distributed with
 this work for additional information regarding copyright ownership.
 The ASF licenses this file to You under the Apache License, Version 2.0
 (the "License"); you may not use this file except in compliance with
 the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-->

<schema name="default" version="1.5">
  <types>
    <fieldtype name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
    <fieldtype name="binary" class="solr.BinaryField"/>

    <!-- Numeric field types that manipulate the value into
         a string value that isn't human-readable in its internal form,
         but with a lexicographic ordering the same as the numeric ordering,
         so that range queries work correctly. -->
    <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
    <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
    <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
    <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
    <fieldType name="sint" class="solr.SortableIntField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="slong" class="solr.SortableLongField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="sfloat" class="solr.SortableFloatField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="sdouble" class="solr.SortableDoubleField" sortMissingLast="true" omitNorms="true"/>

    <fieldType name="tint" class="solr.TrieIntField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
    <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
    <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
    <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>

    <fieldType name="date" class="solr.TrieDateField" omitNorms="true" precisionStep="0" positionIncrementGap="0"/>
    <!-- A Trie based date field for faster date range queries and date faceting. -->
    <fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" precisionStep="6" positionIncrementGap="0"/>

    <fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
    <fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
    <fieldtype name="geohash" class="solr.GeoHashField"/>

    <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
        <!-- in this example, we will only use synonyms at query time
        <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
        -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory"
                ignoreCase="true"
                words="lang/stopwords_en.txt"
                enablePositionIncrements="true"
                />
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.EnglishPossessiveFilterFactory"/>
        <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
        <!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
          <filter class="solr.EnglishMinimalStemFilterFactory"/>
        -->
        <filter class="solr.PorterStemFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.StopFilterFactory"
                ignoreCase="true"
                words="lang/stopwords_en.txt"
                enablePositionIncrements="true"
                />
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.EnglishPossessiveFilterFactory"/>
        <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
        <!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
          <filter class="solr.EnglishMinimalStemFilterFactory"/>
        -->
        <filter class="solr.PorterStemFilterFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
      <analyzer>
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="ngram" class="solr.TextField" >
      <analyzer type="index">
        <tokenizer class="solr.KeywordTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.KeywordTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
      <analyzer type="index">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
        <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" />
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
      </analyzer>
    </fieldType>
  </types>

  <fields>
    <!-- general -->
    <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
    <field name="django_ct" type="string" indexed="true" stored="true" multiValued="false"/>
    <field name="django_id" type="string" indexed="true" stored="true" multiValued="false"/>
    <field name="_version_" type="long" indexed="true" stored ="true"/>

    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
    <dynamicField name="*_t"  type="text_en"    indexed="true"  stored="true"/>
    <dynamicField name="*_b"  type="boolean" indexed="true"  stored="true"/>
    <dynamicField name="*_f"  type="float"  indexed="true"  stored="true"/>
    <dynamicField name="*_d"  type="double" indexed="true"  stored="true"/>
    <dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
    <dynamicField name="*_p" type="location" indexed="true" stored="true"/>
    <dynamicField name="*_coordinate"  type="tdouble" indexed="true"  stored="false"/>


    <field name="publish" type="date" indexed="true" stored="true" multiValued="false" />

    <field name="text" type="text_en" indexed="true" stored="true" multiValued="false" />

  </fields>

  <!-- field to use to determine and enforce document uniqueness. -->
  <uniqueKey>id</uniqueKey>

  <!-- field for the QueryParser to use when an explicit fieldname is absent -->
  <defaultSearchField>text</defaultSearchField>

  <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
  <solrQueryParser defaultOperator="AND"/>
</schema>

在瀏覽器中打開 http://127.0.0.1:8983/solr/ 點擊 core Admin 目錄按鈕,然后點擊 blog ,然后點擊 reload :

solr_reload.png

我們 reload 內(nèi)核來更新 schema.xml 。當內(nèi)核reload 結(jié)束時,新的 schema 索引新數(shù)據(jù)。

檢索數(shù)據(jù)

我們?yōu)?Solr 創(chuàng)建 blog 文章的索引。打開 teminal 運行以下命令:

python manage.py rebuild_index

你將看到以下警告信息:

WARNING: This will irreparably remove EVERYTHING from your search index in connection 'default'.
Your choices after this are to restore from backups or rebuild via the `rebuild_index` command.
Are you sure you wish to continue? [y/N] 

輸入 y,Haystack 將清空搜索索引并寫入所有發(fā)布的文章。你應(yīng)該看到這樣的輸出:

Removing all documents from your index because you said so.
All documents removed.
Indexing 5 posts

在瀏覽器中打開 http://127.0.0.1:8983/solr/#/blog ,在 Statistics下你將看到文件索引數(shù)量:

solr_index.png

現(xiàn)在,在瀏覽器中打開 http://127.0.0.1:8983/solr/#/blog/query ,這是 Solr 提供的一個 query 接口,點擊Excute query 按鈕。默認搜索返回內(nèi)核中所有文件的索引。你將看到一個 JSON 格式的輸出,輸出內(nèi)容是這種樣子的:

{
  "responseHeader": {
    "status": 0,
    "QTime": 8
  },
  "response": {
    "numFound": 5,
    "start": 0,
    "docs": [
      {
        "id": "blog.post.1",
        "django_ct": "blog.post",
        "django_id": "1",
        "text": "Why Django?\njazz,music\nWith Django, you can take Web applications from concept to launch in a matter of hours. Django takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.",
        "publish": "2018-03-19T04:12:52Z"
      },
      {
        "id": "blog.post.2",
        "django_ct": "blog.post",
        "django_id": "2",
        "text": "New title\n\nPost body.",
        "publish": "2018-03-19T04:34:48Z"
      },
      {
        "id": "blog.post.4",
        "django_ct": "blog.post",
        "django_id": "4",
        "text": "Meet Django\n\nDjango is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.",
        "publish": "2018-03-19T08:37:52Z"
      },
      {
        "id": "blog.post.5",
        "django_ct": "blog.post",
        "django_id": "5",
        "text": "Another post\n\npost boday",
        "publish": "2018-03-19T08:40:12Z"
      },
      {
        "id": "blog.post.6",
        "django_ct": "blog.post",
        "django_id": "6",
        "text": "MarkDown post\nmarkdown\nThis is a post formatted with markdown\n\n*This is emphasized* and **this is more emphasized**.\n\nHere is a list:\n\n- One\n- Two\n- Three\n\nAnd a [link to the Django website](https://www.djangoproject.com/)",
        "publish": "2018-03-20T06:26:25Z"
      }
    ]
  }
}

這是搜索索引為每篇文章保存的數(shù)據(jù)。text 字段包含用標題、標簽和正本(用\分隔),使用的是我們在 search/indexes/blog/post_text.txt 模板中定義的格式。

我們已經(jīng)使用 python manage.py rebuild_index 移除了 index 中的所有內(nèi)容并為文章重新建立索引。我們可以使用 python manage.py update_index 命令在不移除所有對象的情況下更新索引。而且可以通過 --age=<num_hours>更新更少的對象。我們可以為此創(chuàng)建一個定時任務(wù)( cron jobs )來更新 Solr 中的索引。

創(chuàng)建搜索視圖

現(xiàn)在,我們新建一個自定義視圖來幫助用戶搜索文章。首先,我們需要一個搜索表單。編輯 blog 應(yīng)用的 forms.py并添加以下表單:

class SearchForm(forms.Form):
    query = forms.CharField()

用戶通過 query 字段輸入搜索內(nèi)容。編輯 blog 應(yīng)用的 views.py 文件并添加以下代碼:

from .forms import SearchForm
from haystack.query import SearchQuerySet


def post_search(request):
    form = SearchForm()
    if 'query' in request.GET:
        form = SearchForm(request.GET)
        if form.is_valid():
            cd = form.cleaned_data
            results = SearchQuerySet().models(Post).filter(
                content=cd['query']).load_all()
            # count total results
            total_results = results.count()
            return render(request, 'blog/post/search.html',
                          {'form': form, 'cd': cd, 'results': results,
                           'total_results': total_results})
    else:
        return render(request,'blog/post/search.html',{'form':form})
      

在這個視圖中,首先需要初始化剛剛創(chuàng)建的 SearchForm 。我們使用 GET 方法提交表單,這樣 URL 可以包含 query 參數(shù)。我們通過查找 request.GET 中是否包含 ‘query’ 來判斷表單是否已經(jīng)提交。如果表單已經(jīng)提交,我們使用 GET 中的數(shù)據(jù)對表單進行初始化并檢查輸入的數(shù)據(jù)是否有效,如果表單有效,則使用 SearchQuerySet(已包含給定的查詢)在索引的文章對象中查詢符合查詢條件的文章。load_all() 方法從數(shù)據(jù)庫中加載所有相關(guān)文章對象。我們使用這個方法從數(shù)據(jù)庫中獲取數(shù)據(jù)以避免遍歷結(jié)果時每個對象都要訪問數(shù)據(jù)庫。最后,我們在 total_results 變量中保存檢索結(jié)果的數(shù)量并傳入 context 中來渲染模板。

search 視圖已經(jīng)實現(xiàn)了,現(xiàn)在我們需要創(chuàng)建一個模板來展示表單及用戶搜索結(jié)果,在 templates/blog/post/ 目錄下新建一個名為 search.html 的文件并添加以下代碼:

{% extends "blog/base.html" %}

{% block title %}Search{% endblock %}

{% block content %}
    {% if "query" in request.GET %}
        <h1>Posts containing "{{ cd.query }}"</h1>
        <h3>Found {{ total_results }} result{{ total_results|pluralize }}</h3>
        {% for result in results %}
            {% with post=result.object %}
                <h4><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h4>
                {{ post.body|truncatewords:5 }}
            {% endwith %}
        {% empty %}
            <p>There are no results for your query.</p>
        {% endfor %}
        <p><a href="{% url 'blog:post_search' %}">Search again</a></p>
    {% else %}
        <h1>Search for posts</h1>
        <form action="." method="get">
            {{ form.as_p }}
            <input type="submit" value="Search">
        </form>
    {% endif %}
{% endblock %}

我們在 Search視圖中通過 query 參數(shù)判斷表單是否提交,在表單提交之前,我們展示表單及一個確定按鈕,在表單提交之后,我們展示查詢結(jié)果,查詢總數(shù)及查詢結(jié)果列表。每個 result 都是一個文檔,文檔通過 Solr 返回并通過 Haystack 打包。我們需要使用 result.object 來訪問結(jié)果對應(yīng)的文章對象。

最后,編輯 blog 應(yīng)用的 urls.py 并添加以下 URL 模式:

url(r'^search/$', views.post_search, name='post_search'),

現(xiàn)在,在瀏覽器中打開 http://127.0.0.1:8000/blog/search/ ,我們將看到如下結(jié)果:

solr_search.png

現(xiàn)在,在搜索框中輸入 搜索內(nèi)容 (這里輸入了 django )并點擊 Search 按鈕,我們將看這樣的結(jié)果:

solr_search_result.png

現(xiàn)在,項目已經(jīng)集成了一個強大的搜索引擎, Solr 和 Haystack 在此基礎(chǔ)之上可以實現(xiàn)更多的功能。Haystack 包含搜索引擎使用的搜索視圖、表單及高級功能。以下鏈接包含更多 HayStack 的相關(guān)內(nèi)容:http://django-haystack.readthedocs.io/en/latest/。

Solr 搜索引擎可以通過自定義 schema 來實現(xiàn)各種需求。我們可以在創(chuàng)建索引或者搜索時結(jié)合分析器、標記器、應(yīng)用到索引的 token 過濾器及搜索時間等提供更精確的搜索。以下鏈接包含更多 Solr 的相關(guān)內(nèi)容:https://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters。

筆者注:

搜索引擎正常工作需要滿足下面兩個條件

  1. 在 Solr-4.10.4/example/ 下運行 java -jar start.jar 使用內(nèi)置的 Jetty web 服務(wù)器運行 Solr;當然也可以使用其它 Solr 配置 ,一定要保證 Solr 處于運行狀態(tài);

  2. 定期更新 Solr 索引,可以通過 python manage.py update_index 命令實現(xiàn),也可以創(chuàng)建定時任務(wù)實現(xiàn)。

關(guān)于 search 視圖及模板的思考

  1. 顯示搜索結(jié)果時,應(yīng)該在結(jié)果上方顯示搜索框,以備用戶再次搜索,而不是在搜索結(jié)果底部添加搜索鏈接。

  2. 搜索框可以做成 inclusion_tag 模板標簽,可以在任意需要搜索的地方加載即可,當然 inclusion_tag 應(yīng)該可以傳入顯示搜索結(jié)果的 url ;

  3. 搜索使用的 GET 請求,在視圖中使用 form.is_valid() 進行驗證的作用有限,可以考慮將以下代碼:

    if 'query' in request.GET:
            form = SearchForm(request.GET)
            if form.is_valid():
                cd = form.cleaned_data
                results = SearchQuerySet().models(Post).filter(
                    content=cd[&#39;query&#39;]).load_all()
    

    替換為:

    if 'query' in request.GET:
            query = request.GET.get('query')
            results = SearchQuerySet().models(Post).filter(content=query).load_all()
    

至于安全和性能問題,后續(xù)統(tǒng)一考慮。

總結(jié)

本章我們學(xué)習(xí)了如何自定義 Django 模板標簽和過濾器來為模板提供自定義功能,我們還創(chuàng)建了搜索引擎爬取網(wǎng)站使用的 sitemap 以及訂閱使用的 RSS feed。此外,還通過集成 Solr 和 Haystack 為 blog 創(chuàng)建了搜索引擎。

下一章中,我們將學(xué)習(xí)通過使用 Django 權(quán)限框架創(chuàng)建社交網(wǎng)站,創(chuàng)建自定義用戶文件以及實現(xiàn)社交認證。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容