點我查看本文集的說明及目錄给赞。
本項目相關(guān)內(nèi)容( github傳送 )包括:
實現(xiàn)過程:
項目總結(jié)及改進(jìn):
使用類視圖實現(xiàn) blog list 和 detail 視圖
CH3 擴(kuò)展 blog 應(yīng)用
上一章我們學(xué)習(xí)了表單基礎(chǔ)知識及如何在項目中集成第三方應(yīng)用。本章的要點包含:
創(chuàng)建自定義模板標(biāo)簽和過濾器
添加 sitemap 和文章 feed
-
使用 Solr 和 Haystack 創(chuàng)建搜索引擎
?
創(chuàng)建自定義模板標(biāo)簽和過濾器
Django 提供許多內(nèi)置標(biāo)簽(比如 {% if %} 昵慌、{% block %} 等)沸枯,我們已經(jīng)在前面的開發(fā)過程中使用了一些肤视。下面的鏈接包括 Django 所有的內(nèi)置標(biāo)簽和過濾器:https://docs.djangoproject.com/en/1.11/ref/templates/builtins/。
Django 允許我們創(chuàng)建自定義標(biāo)簽來實現(xiàn)自定義動作字柠。如果我們需要實現(xiàn) Django 內(nèi)置模板標(biāo)簽無法覆蓋的功能時可以創(chuàng)建自定義標(biāo)簽探越。
創(chuàng)建自定義模板標(biāo)簽
Django 提供以下幫助函數(shù)幫助我們快速創(chuàng)建自定義模板標(biāo)簽:
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 。
模板標(biāo)簽必須位于 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)是這樣的:
文件的名稱很重要卷玉,你將在模板中使用這個名稱加載自定義模板標(biāo)簽哨颂。
我們首先創(chuàng)建一個 simple_tag ,該標(biāo)簽獲取 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ù)量的簡單模板標(biāo)簽。每個模板標(biāo)簽?zāi)K都需要包含一個名為 register 的變量寝并,這個變量是 template.Library 實例箫措,用于注冊自定義模板標(biāo)簽或過濾器。然后我們使用 python 函數(shù)定義一個名為 total_posts 的模板標(biāo)簽衬潦,并使用@register.simple_tag
裝飾器注冊標(biāo)簽斤蔓。Django 將使用函數(shù)的名稱作為標(biāo)簽名稱。如果需要使用其它名稱别渔,可以將 @register.simple_tag
更改為 @register.simple_tag(name='my_tag')
附迷。
注意:
添加模板標(biāo)簽?zāi)K后惧互,需要重啟 Django開發(fā)服務(wù)器才能使用新的自定義模板標(biāo)簽和過濾器哎媚。
在使用自定義模板標(biāo)簽之前,需要使用{% load %}
標(biāo)簽進(jìn)行加載喊儡。打開 blog/base.html 模板拨与,并在開始的位置添加 {% load blog_tags %}
來加載模板標(biāo)簽?zāi)K,然后通過添加{% total_posts %}
來使用創(chuàng)建的標(biāo)簽表示所有文章艾猜,模板最終看起來是這樣的:
{% 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ā)布的文章信息
譯者注:
由于模板標(biāo)簽的 queryset 使用的是 published 管理器匆赃,因此需要設(shè)置文章的狀態(tài)(通過 admin網(wǎng)站或者 python manage.py shell)來得到要想的結(jié)果淤毛。
自定義標(biāo)簽的強大之處在于可以處理任何數(shù)據(jù)以及可以添加到任意模板,我們可以在模板中展示 Queryset 或者 任何數(shù)據(jù)的處理結(jié)果算柳。
現(xiàn)在低淡,我們將創(chuàng)建另一個模板標(biāo)簽(在 blog 邊欄展示最新文章)。這次我們使用 inclusion 標(biāo)簽瞬项。inclusion 標(biāo)簽可以對內(nèi)容進(jìn)行渲染蔗蹋。編輯 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
注冊模板標(biāo)簽并設(shè)置返回數(shù)據(jù)渲染所用的模板 blog/post/latest_posts.html
囱淋。模板標(biāo)簽可以輸入可選的 count 參數(shù)(默認(rèn)值為 5 )猪杭。我們使用這個變量來限制 Post.published.order_by('-publish')
結(jié)果的數(shù)量。注意妥衣,這個函數(shù)返回變量字典而不是單個值皂吮。Inclusion標(biāo)簽需要返回值的字典作為特定模板的內(nèi)容戒傻。我們剛剛創(chuàng)建的模板可以傳入可選的文章數(shù)量并這樣進(jìn)行顯示 {% 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 展示無序的文章列表并通過標(biāo)簽返回。現(xiàn)在狂票,編輯 blog/base.html 模板并通過添加新的模板標(biāo)簽展示最新的 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)用模板標(biāo)簽,模板已經(jīng)對給定內(nèi)容進(jìn)行了渲染闺属。
現(xiàn)在回到瀏覽器刷新一下頁面慌盯,邊欄現(xiàn)在是這樣的了:
最后,我們再次使用 simple_tag 創(chuàng)建一個 assignment_tag 功能的標(biāo)簽掂器,該功能是將返回的值保存在一個變量中亚皂。我們將創(chuàng)建一個標(biāo)簽來展示評論最多的文章。編輯 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ù)進(jìn)行聚合查詢(使用 Count 聚合函數(shù))乃摹。創(chuàng)建 total_comments 字段保存每篇文章評論數(shù)量的 queryset 并通過該字段對查詢結(jié)果進(jìn)行排序禁漓。我們還提供可選的 count 參數(shù)限制返回對象的數(shù)量。
除了 Count 孵睬,Django 還提供 Avg 播歼、Max 、Min 掰读、 Sum 等聚合函數(shù)秘狞。我們可以通過閱讀官網(wǎng)資料對聚合函數(shù)進(jìn)行更多了解: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 %}
。這樣可以將標(biāo)簽結(jié)果保存到一個名為most_commented_posts 的變量中拢肆,然后使用無序列表對返回的文章進(jìn)行展示减响。
現(xiàn)在,刷新瀏覽器善榛,我們將看到這樣的頁面:
我們可以閱讀官方文檔了解更多關(guān)于模板標(biāo)簽的內(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))
模板過濾器的注冊方法與模板標(biāo)簽的注冊方法相同熔萧。為避免函數(shù)名與 markdown 模塊混淆糖驴,我們將函數(shù)命名為 markdown_format 并將過濾器命名為 markdown 以便在模板中使用 {{ variable|markdown }} 。Django 不對過濾器生成的 HTML 代碼進(jìn)行轉(zhuǎn)義佛致。因此贮缕,我們使用 Django 提供的 mark_safe 函數(shù)對結(jié)果進(jìn)行轉(zhuǎn)義來保證可以在模板中進(jìn)行渲染。默認(rèn)情況下俺榆,Django 不信任任何 HTML 代碼并在輸出之前進(jìn)行轉(zhuǎn)義感昼。變量被標(biāo)記為 safe 是唯一的例外。這個特性可以防止 Django 生成存在潛在危險的 HTML 罐脊,并允許用戶在知道代碼可以返回安全的 HTML 時不進(jìn)行轉(zhuǎn)義定嗓。
現(xiàn)在,在 post/list.html 和 post/detail.html 中的 {% extends %} 后面加載模板標(biāo)簽:
{% 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 標(biāo)簽梗夸。
現(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/)
打開瀏覽器号醉,我們來看下這篇文章如何渲染反症,我們應(yīng)該看到這樣的效果:
我們可以看到,自定義模板過濾器可以有效的自定義格式畔派,通過以下鏈接可以了解更多內(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暮顺。默認(rèn)情況下,Django 調(diào)用模型的 get_absolute_url() 方法來獲取模型對象的 URL 秀存,我們在第一章的 Post 模型中定義了 get_absolute_url() 方法拖云。如果希望指定每篇文章的 URL ,那么可以為 sitemap 類添加 location 方法应又。lastmod 方法獲得 item() 返回的每個對象并返回文章的最新修改時間宙项。changefreq 和 priority 可以是方法或?qū)傩浴itemap 更多的內(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 :
每篇文章的 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)建默認(rèn)對象 example.com∈啄幔現(xiàn)在在瀏覽器中打開 http://127.0.0.1:8000/admin/sites/site/ 挑庶,你將看到:
這是 sites 框架的域名列表視圖。我們可以設(shè)置 sites 框架使用的域名或主機软能。為了生成本地環(huán)境的 URL 迎捺,將域名更改為 127.0.0.0:8000 :
為了便于開發(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 模式中進(jìn)行實例化:
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 的新鏈接:
使用 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 的管理平臺永脓。這個平臺靜態(tài)顯示使用情況并允許我們管理搜索 backend、檢查索引數(shù)據(jù)及進(jìn)行查詢鞋仍。
創(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)核的信息板甘,如下圖所示:
我們將為 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)建下面的空文件及目錄:
在 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 定義了在搜索引擎中進(jìn)行索引的數(shù)據(jù)的字段及類型。稍后我們將使用自定義 schema 隐岛。
現(xiàn)在猫妙,點擊 Core Amdin 目錄按鈕然后點擊 Core Core 按鈕。你將看到一個為內(nèi)核設(shè)置信息的表單:
使用以下數(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 中了:
安裝 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 可以與多個搜索引擎進(jìn)行交互。為了使用 Solr 引擎杖剪,我們還需要安裝 pysolr 模塊冻押。運行下面的命令進(jìn)行安裝:
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 進(jìn)行配置實現(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)容進(jìn)行索引。
這樣一個額外字段將有助于為索引提供額外的過濾器豆茫。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 字段文檔模板的默認(rèn)路徑 。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 :
我們 reload 內(nèi)核來更新 schema.xml 哥桥。當(dāng)內(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ù)量:
現(xiàn)在佛玄,在瀏覽器中打開 http://127.0.0.1:8983/solr/#/blog/query ,這是 Solr 提供的一個 query 接口累澡,點擊Excute query 按鈕梦抢。默認(rèn)搜索返回內(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 字段包含用標(biāo)題、標(biāo)簽和正本(用\分隔)蕊梧,使用的是我們在 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ù)對表單進(jìn)行初始化并檢查輸入的數(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é)果:
現(xiàn)在商叹,在搜索框中輸入 搜索內(nèi)容 (這里輸入了 django )并點擊 Search 按鈕,我們將看這樣的結(jié)果:
現(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é)合分析器酪夷、標(biāo)記器榴啸、應(yīng)用到索引的 token 過濾器及搜索時間等提供更精確的搜索孽惰。以下鏈接包含更多 Solr 的相關(guān)內(nèi)容:https://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters晚岭。
筆者注:
搜索引擎正常工作需要滿足下面兩個條件:
在 Solr-4.10.4/example/ 下運行
java -jar start.jar
使用內(nèi)置的 Jetty web 服務(wù)器運行 Solr;當(dāng)然也可以使用其它 Solr 配置 勋功,一定要保證 Solr 處于運行狀態(tài)坦报;定期更新 Solr 索引,可以通過 python manage.py update_index 命令實現(xiàn)狂鞋,也可以創(chuàng)建定時任務(wù)實現(xiàn)片择。
關(guān)于 search 視圖及模板的思考:
顯示搜索結(jié)果時,應(yīng)該在結(jié)果上方顯示搜索框骚揍,以備用戶再次搜索字管,而不是在搜索結(jié)果底部添加搜索鏈接。
搜索框可以做成 inclusion_tag 模板標(biāo)簽信不,可以在任意需要搜索的地方加載即可嘲叔,當(dāng)然 inclusion_tag 應(yīng)該可以傳入顯示搜索結(jié)果的 url ;
搜索使用的 GET 請求抽活,在視圖中使用 form.is_valid() 進(jìn)行驗證的作用有限硫戈,可以考慮將以下代碼:
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()
替換為:
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 模板標(biāo)簽和過濾器來為模板提供自定義功能丁逝,我們還創(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)社交認(rèn)證罪既。