《Django By Example》第三章 中文 翻譯 (個人學習,渣翻)


書籍出處:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé

2016年12月20日初稿發布

2017年3月11日精校完成(感謝感謝大牛@kukoo的精校!)

2017年4月6日再次精校(感謝大牛 @媽媽不在家 的精校!)

(譯者注:第三章滾燙出爐,大家請不要吐槽文中圖片比較模糊,畢竟都是從PDF中截圖出來的,有點丟像素,大致能看就行- -,另外還是渣翻,但個人覺的比前兩章翻譯的稍微進步了那么一點點- -,希望后面幾章翻譯的越來越溜,就這樣)

第三章

擴展你的blog應用

在上一章中我們學習了表單的基礎以及如何在項目中集成第三方的應用。本章將會包含以下內容:

  • 創建自定義的模板標簽(template tags)和過濾器(filters)
  • 添加一個站點地圖和帖子反饋(post feed)
  • 使用Solr和Haystack構建一個搜索引擎

創建自定義的模板標簽(template tags)和過濾器(filters)

Django提供了很多內置的模板標簽(tags),例如{% if %}或者{% block %}。你已經在你的模板(template)中使用過一些了。你可以在https://docs.djangoproject.com/en/1.8/ ref/templates/builtins/ 中找到關於內置模板標簽(template tags)以及過濾器(filter)的完整參考。

當然,Django也允許你創建自己的模板標簽(template tags)來執行自定義的動作。當你需要在你的模板中添加功能而Django模板標簽(template tags)的核心設置無法提供此功能的時候,自定義模板標簽會非常方便。

創建自定義的模板標簽(template tags)

Django提供了以下幫助函數(functions)來允許你以一種簡單的方式創建自己的模板標簽(template tags):

  • simple_tag:處理數據並返回一個字符串(string)
  • inclusion_tag:處理數據並返回一個渲染過的模板(template)
  • assignment_tag:處理數據並在上下文(context)中設置一個變量(variable)

模板標簽(template tags)必須存在Django的應用中。

進入你的blog應用目錄,創建一個新的目錄命名為templatetags然后在該目錄下創建一個空的init.py文件。接着在該目錄下繼續創建一個文件並命名為blog_tags.py。到此,我們的blog應用文件結構應該如下所示:

blog/
    __init__.py
    models.py
    ...
    templatetags/
        __init__.py
        blog_tags.py

文件的命名是非常重要的。你將在模板(template)中使用這些模塊的名字加載你的標簽(tags)。

我們將要開始創建一個簡單標簽(simple tag)來獲取blog中所有已發布的帖子。編輯你剛才創建的blog_tags.py文件,加入以下代碼:

from django import template

register = template.Library()

from ..models import Post

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

我們已經創建了一個簡單的模板標簽(template tag)用來取回目前為止所有已發布的帖子。每一個模板標簽(template tags)都需要包含一個叫做register的變量來表明自己是一個有效的標簽(tag)庫。這個變量是template.Library的一個實例,它是用來注冊你自己的模板標簽(template tags)和過濾器(filter)的。我們用一個Python函數定義了一個名為total_posts的標簽,並用@register.simple-tag裝飾器定義此函數為一個簡單標簽(tag)並注冊它。

Django將會使用這個函數名作為標簽(tag)名。如果你想使用別的名字來注冊這個標簽(tag),你可以指定裝飾器的name屬性,比如@register.simple_tag(name='my_tag')

在添加了新的模板標簽(template tags)模塊后,你必須重啟Django開發服務才能使用新的模板標簽(template tags)和過濾器(filters)。

在使用自定義的模板標簽(template tags)之前,你必須使用{% load %}標簽在模板(template)中來加載它們才能有效。就像之前提到的,你需要使用包含了你的模板標簽(template tags)和過濾器(filter)的Python模塊的名字。打開blog/base.html模板(template)在頂部添加{% load blog_tags %}加載你自己的模板標簽(template tags)模塊。之后使用你創建的標簽(tag)來顯示你的帖子總數。只需要在你的模板(template)中添加{% total_posts %}。最新的模板(template)看上去如下所示:

{% load blog_tags %}
{% load staticfiles %}
<!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>

我們需要重啟服務來保證新的文件被加載到項目中。使用Ctrl+C停止服務然后通過以下命令再次啟動:

python manage.py runserver

在瀏覽器中打開 http://127.0.0.1:8000/blog/ 。你會在站點的側邊欄(sidebar)看到帖子的總數,如下所示:

django-3-1

自定義模板標簽(template tags)的作用是你可以處理任何的數據並且在任何模板(template)中添加它而不用去關心視圖(view)執行。你可以在你的模板(template)中運行查詢集(QuerySets)或者處理任何數據展示結果。

現在,我們要創建另外一個標簽(tag),可以在我們blog的側邊欄(sidebar)展示最新的幾個帖子。這一次我們要使用一個包含標簽(inclusion tag)。使用一個包含標簽(inclusion tag),你就可以利用模板標簽(template tags)返回的上下文變量(context variables)來渲染模板(template)。編輯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注冊模板標簽(template tag),指定模板(template)必須被blog/post/latest_posts.html返回的值渲染。我們的模板標簽(template tag)將會接受一個可選的count參數(默認是5)允許我們指定我們想要顯示的帖子數量。我們使用這個變量來限制Post.published.order_by('-publish')[:count]查詢的結果。請注意,這個函數返回了一個字典變量而不是一個簡單的值。包含標簽(inclusion tags)必須返回一個字典值,作為上下文(context)來渲染特定的模板(template)。包含標簽(inclusion tags)返回一個字典。這個我們剛創建的模板標簽(template tag)可以通過傳入可選的評論數量值來使用顯示,類似*{% show_latest_posts 3 %}。

現在,在blog/post/下創建一個新的模板(template)文件並且命名為latest_posts.html。在該文件中添加如下代碼:

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

在這里,我們使用模板標簽(template tag)返回的latest_posts變量展示了一個無序的帖子列表。現在,編輯blog/base.hmtl模板(template)添加這個新的模板標簽(template tag)來展示最新的3條帖子。側邊欄(sidebar)區塊(block)看上去應該如下所示:

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

這個模板標簽(template tag)被調用而且傳入了需要展示的帖子數(原文此處 number of comments,應該是寫錯了)。當前模板(template)在給予上下文(context)的位置會被渲染。

現在,回到瀏覽器刷新這個頁面,側邊欄應該如下圖所示:

django-3-2

最后,我們來創建一個分配標簽(assignment tag)。分配標簽(assignment tag)類似簡單標簽(simple tags)但是他們將結果存儲在給予的變量中。我們將會創建一個分配標簽(assignment tag)來展示擁有最多評論的帖子。編輯blog_tags.py文件,在其中添加如下導入和模板標簽:

from django.db.models import Count

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

這個查詢集(QuerySet)使用annotate()函數,為了進行聚合查詢,使用了Count聚合函數。我們構建了一個查詢集(QuerySet),聚合了每一個帖子的評論總數並保存在total_comments字段中,接着我們通過這個字段對查詢集(QuerySet)進行排序。我們還提供了一個可選的count變量,通過給定的值來限制返回的帖子數量。

除了Count以外,Django還提供了不少聚合函數,例如Avg,Max,Min,Sum.你可以在 https://docs.djangoproject.com/en/1.8/topics/db/aggregation/ 頁面讀到更多關於聚合方法的信息。

編輯blog/base.html模板(template),在側邊欄(sidebar)<div>元素中添加如下代碼:

<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>

使用分配模板標簽(assignment template tags)的方法是{% template_tag as variable %}。對於我們的模板標簽(template tag)來說,我們使用{% get_most_commented_posts as most_commented_posts %}。 這樣,我們可以存儲這個模板標簽(template tag)返回的結果到一個新的名為most_commented_posts變量中。之后,我們就可以用一個無序列表(unordered list)顯示返回的帖子。

現在,打開瀏覽器刷新頁面來看下最終的結果,應該如下圖所示:

django-3-3

你可以在 https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/ 頁面得到更多的關於自定義模板標簽(template tags)的信息。

創建自定義的模板過濾器(template filters)

Django擁有多種內置的模板過濾器(template filters)允許你對模板(template)中的變量進行修改。這些過濾器其實就是Python函數並提供了一個或兩個參數————一個是需要處理的變量值,一個是可選的參數。它們返回的值可以被展示或者被別的過濾器(filters)處理。一個過濾器(filter)類似{{ variable|my_filter }}或者再帶上一個參數,類似{{ variable|my_filter:"foo" }}。你可以對一個變量調用你想要的多次過濾器(filter),類似{{ variable|filter1|filter2 }}, 每一個過濾器(filter)都會對上一個過濾器(filter)輸出的結果進行過濾。

我們這就創建一個自定義的過濾器(filter),可以在我們的blog帖子中使用markdown語法,然后在模板(template)中將帖子內容轉變為HTML。Markdown是一種非常容易使用的文本格式化語法並且它可以轉變為HTML。你可以在 http://daringfireball.net/projects/markdown/basics 頁面學習這方面的知識。

首先,通過pip渠道安裝Python的markdown模板:

pip install Markdown==2.6.2

之后編輯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))

我們使用和模板標簽(template tags)一樣的方法來注冊我們自己的模板過濾器(template filter)。為了避免我們的函數名和markdown模板名起沖突,我們將我們的函數命名為markdown_format,然后將過濾器(filter)命名為markdown,在模板(template)中的使用方法類似{{ variable|markdown }}。Django會轉義過濾器(filter)生成的HTML代碼。我們使用Django提供的mark_safe方法來標記結果,在模板(template)中作為安全的HTML被渲染。默認的,Django不會信賴任何HTML代碼並且在輸出之前會進行轉義。唯一的例外就是被標記為安全轉義的變量。這樣的操作可以阻止Django從輸出中執行潛在的危險的HTML,並且允許你創建一些例外情況只要你知道你正在運行的是安全的HTML。

現在,在帖子列表和詳情模板(template)中加載你的模板標簽(template tags)模塊。在post/list.htmlpost/detail.html模板(template)的頂部{% extends %}的后方添加如下代碼:

{% load blog_tags %}

post/detail.thml模板中,替換以下內容:

{{ post.body|linebreaks }}

替換成:

{{ post.body|markdown }}

之后,在post/list.html文件中,替換以下內容:

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

替換成:

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

truncateword_html過濾器(filter)會在一定數量的單詞后截斷字符串,避免沒有關閉的HTML標簽(tags)。

現在,打開 http://127.0.0.1:8000/admin/blog/post/add/ ,添加一個帖子,內容如下所示:

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/)

在瀏覽器中查看帖子的渲染情況,你會看到如下圖所示:

django-3-4

就像你所看到的,自定義的模板過濾器(template filters)對於自定義的格式化是非常有用的。你可以在 https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-custom-templatefilters 頁面獲取更多關於自定義過濾器(filter)的信息。

為你的站點添加一個站點地圖(sitemap)

Django自帶一個站點地圖(sitemap)框架,允許你為你的站點動態生成站點地圖(sitemap)。一個站點地圖(sitemap)是一個xml文件,它會告訴搜索引擎你的網站中存在的頁面,它們的關聯和它們更新的頻率。使用站點地圖(sitemap),你可以幫助網絡爬蟲(crawlers)來對你的網站內容進行索引和標記。

Django站點地圖(sitemap)框架依賴django.contrib.sites模塊,這個模塊允許你將對象和正在你項目運行的特殊網址關聯起來。當你想用一個單獨Django項目運行多個網站時,這是非常方便的。為了安裝站點地圖(sitemap)框架,我們需要在項目中激活sitessitemap這兩個應用。編輯項目中的settings.py文件在INSTALLED_APPS中添加django.contrib.sitesdjango.contrib.sitemaps。之后為站點ID定義一個新的設置,如下所示:

SITE_ID = 1
# Application definition
INSTALLED_APPS = (
# ...
'django.contrib.sites',
'django.contrib.sitemaps',
)

現在,運行以下命令在數據庫中為Django的站點應用創建所需的表:

python manage.py migrate

你會看到如下的輸出內容:

Applying sites.0001_initial... OK

sites應用現在已經在數據庫中進行了同步。現在,在你的blog應用目錄下創建一個新的文件命名為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

通過繼承sitemaps模塊提供的Sitemap類我們創建一個自定義的站點地圖(sitemap)。changefreqpriority屬性表明了帖子頁面修改的頻率和它們在網站中的關聯性(最大值是1)。items()方法返回了在這個站點地圖(sitemap)中所包含對象的查詢集(QuerySet)。默認的,Django在每個對象中調用get_absolute_url()方法來獲取它的URL。請記住,這個方法是我們在第一章(創建一個blog應用)中創建的,用來獲取每個帖子的標准URL。如果你想為每個對象指定URL,你可以在你的站點地圖(sitemap)類中添加一個location方法。lastmode方法接收items()返回的每一個對象並且返回對象的最后修改時間。changefreqpriority兩個方法既可以是方法也可以是屬性。你可以在Django的官方文檔 https://docs.djangoproject.com/en/1.8/ref/contrib/sitemaps/ 頁面中獲取更多的站點地圖(sitemap)參考。

最后,我們只需要添加我們的站點地圖(sitemap)URL。編輯項目中的主*urls.py文件,如下所示添加站點地圖(sitemap):

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

sitemaps = {
    'posts': PostSitemap,
}

urlpatterns = [
    url(r'^admin/', include(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'),
]

在這里,我們加入了必要的導入並定義了一個sitemaps的字典。我們定義了一個URL模式來匹配sitemap.xml並使用sitemap視圖(view)。sitemaps字典會被傳入到sitemap視圖(view)中。現在,在瀏覽器中打開 http://127.0.0.1:8000/sitemap.xml 。你會看到類似下方的XML代碼:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>http://example.com/blog/2015/09/20/another-post/</loc>
        <lastmod>2015-09-29</lastmod>
        <changefreq>weekly</changefreq>
        <priority>0.9</priority>
    </url>
    <url>
        <loc>http://example.com/blog/2015/09/20/who-was-djangoreinhardt/</loc>
        <lastmod>2015-09-20</lastmod>
        <changefreq>weekly</changefreq>
        <priority>0.9</priority>
    </url>
</urlset>

調用get_absolute_url()方法,每個帖子的URL已經被構建。如同我們之前在站點地圖(sitemap)中所指定的,lastmode屬性對應該帖子的publish日期字段,changefreqpriority屬性也從我們的PostSitemap類中帶入。你能看到被用來構建URL的域(domain)是example.com。這個域(domain)來自存儲在數據庫中的一個Site對象。這個默認的對象是在我們之前同步sites框架數據庫時創建的。在你的瀏覽器中打開http://127.0.0.1:8000/admin/sites/site/ ,你會看到如下圖所示:

django-3-5

這是sites框架管理視圖(admin view)的列表顯示。在這里,你可以設置sites框架使用的域(domain)或者主機(host),而且應用也依賴它們。為了生成能在我們本地環境可用的URL,更改域(domain)名為127.0.0.1:8000,如下圖所示並保存:

django-3-6

為了開發需要我們指向了我們本地主機。在生產環境中,你必須使用你自己的sites框架域(domain)名。

為你的blog帖子創建feeds

譯者注:這節中有不少英文單詞,例如feed,syndication,Atom等,沒有比較好的翻譯,很多地方也都是直接不翻譯保留原文,所以我也保留原文)

Django有一個內置的syndication feed框架,就像用sites框架創建站點地圖(sitemap)一樣,使用類似的方式(manner),你可以動態(dynamically)生成RSS或者Atom feeds。

在blog應用的目錄下創建一個新文件命名為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)

首先,我們繼承了syndication框架的Feed類創建了一個子類。其中的title,link,description屬性各自對應RSS中的<title>,<link>,<description>元素。

items()方法返回包含在feed中的對象。我們只給這個feed取回最新五個已發布的帖子。item_title()item_description()方法接受items()返回的每個對象然后返回每個item各自的標題和描述信息。我們使用內置的truncatewords模板過濾器(template filter)構建帖子的描述信息並只保留最前面的30個單詞。

現在,編輯blog應用下的urls.py文件,導入你剛創建的LatestPostsFeed,在新的URL模式(pattern)中實例化feed:

from .feeds import LatestPostsFeed

urlpatterns = [
	# ...
	url(r'^feed/$', LatestPostsFeed(), name='post_feed'),
]

在瀏覽器中轉到 http://127.0.0.1:8000/blog/feed/ 。你會看到最新的5個blog帖子的RSS feedincluding:

<?xml version="1.0" encoding="utf-8"?>

<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
	<channel>
		<title>My blog</title>
		<link>http://127.0.0.1:8000/blog/</link>
		<description>New posts of my blog.</description>
		<atom:link href="http://127.0.0.1:8000/blog/feed/" rel="self"/>
		<language>en-us</language>
		<lastBuildDate>Sun, 20 Sep 2015 20:40:55 -0000</lastBuildDate>
		<item>
			<title>Who was Django Reinhardt?</title>
			<link>http://127.0.0.1:8000/blog/2015/09/20/who-was-django-reinhardt/</link>
			<description>The Django web framework was named after the amazing jazz guitarist Django Reinhardt.</description>
			<guid>http://127.0.0.1:8000/blog/2015/09/20/who-was-django-reinhardt/</guid>
		</item> 
		...
	</channel>
</rss>

如果你在一個RSS客戶端中打開相同的URL,通過用戶友好的接口你能看到你的feed。

最后一步是在blog的側邊欄(sitebar)添加一個feed訂閱(subscription)鏈接。打開blog/base.html模板(template),在側邊欄(sitebar)的div中的帖子總數下添加如下代碼:

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

現在,在瀏覽器中打開 http://127.0.0.1:8000/blog/ 看下側邊欄(sitebar)。這個新鏈接將會帶你去blog的feed:

django-3-7

使用Solr和Haystack添加一個搜索引擎

譯者注:終於到了這一節,之前自己按照本節一步步操作的走下來添加了一個搜索引擎但是並沒有達到像本節中所說的效果,期間還出現了很多莫名其妙的錯誤,可以說添加的搜索引擎功能失敗了,而且本節中的提到的工具版本過低,官網最新版本的操作步驟已經和本節中描述的不一樣,本節的翻譯就怕會帶來更多的錯誤,大家有需要盡量去閱讀下英文原版。另外,一些單詞沒有好的翻譯我還是保留原文。

現在,我們要為我們的blog添加搜索功能。Django ORM允許你使用icontains過濾器(filter)執行對大小寫不敏感的查詢操作。舉個例子,你可以使用以下的查詢方式來找到內容中包含單詞framework的帖子:

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

但是,如果你想要更加強大的搜索功能,你需要使用合適的搜索引擎。我們准備使用Solr結合Django的方式為我們的blog構建一個搜索引擎。Solr是一個非常流行的開源搜索平台,它提供了全文檢索(full text search),term boosting,hit highlighting,分類搜索(faceted search)以及動態聚集(clustering),還有其他更多的高級搜索特性。

為了在我們的項目中集成Solr,我們需要使用HaystackHaystack是一個為多個搜索引擎提供抽象層工作的Django應用。它提供了一個非常類似於Django查詢集(QuerySets)的簡單的API。讓我們開始安裝和配置SolrHaystack

安裝Solr

你需要1.7或更高的Java運行環境來安裝Solr。你可以在終端中輸入java -version來檢查你的java版本。下方的輸出和你的輸出可能有所出入,但是你必須保證安裝的版本至少也要是1.7的:

java version "1.7.0_25"
Java(TM) SE Runtime Environment (build 1.7.0_25-b15)
Java HotSpot(TM) 64-Bit Server VM (build 23.25-b01, mixed mode)

如果你沒有安裝過Java或者版本低於要求版本,你可以在 http://www.oracle.com/technetwork/java/javase/downloads/index.html 下載Java。

檢查Java版本后,從 http://archive.apache.org/dist/lucene/solr/ 下載4.10.4版本的Solr(譯者注:請一定要下載這個版本,不然下面的操作無法進行!!)。解壓下載的文件進入Solr安裝路徑下的example目錄(也就是,cd solr-4.10.4/example/)。這個目錄下包含了一個准備使用的Solr配置。在這個目錄下,通過以下命令,用內置的Jetty web服務運行Solr

java -jar start.jar

打開你的瀏覽器,進入URL:http://127.0.0.1:8983/solr/ 。你會看到如下圖所示:

django-3-8

以上是Solr的管理控制台。這個控制台向你展示了數據統計,允許你管理你的搜索后端,檢測索引數據,並且執行查詢操作。

創建一個Solr core

Solr允許你隔離每一個core實例。每個Solr core是一個Lucene全文搜索引擎實例,連同一個Solr配置,一個數據架構(schema),以及其他要求的配置才能使用。Slor允許你動態地創建和管理cores。參考例子配置中包含了一個叫collection1的core。如果你點擊Core Admin菜單欄, 你可以看到這個core的信息,如下圖所示:

django-3-9

我們要為我們的blog應用創建一個core。首先,我們需要為我們的core創建文件結構。進入solr-4.10.4/example/目錄下,創建一個新的目錄命名為blog。然后在blog目錄下創建空文件和目錄,如下所示:

blog/ 
    data/
	conf/
	protwords.txt
	schema.xml
	solrconfig.xml
	stopwords.txt
	synonyms.txt
	lang/
	stopwords_en.txt

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)定義了在搜索引擎中將被索引到的數據的字段和字段類型。之后我們要使用一個自定義的架構(schema)。

現在,點擊Core Admin菜單欄再點擊Add core按鈕。你會看到如下圖所示的一張表單,允許你為你的core指定信息:

django-3-10

用以下數據填寫表單:

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

name字段是你想給這個core起的名字。instanceDir字段是你的core的目錄。dataDir是索引數據將要存放的目錄,它位於instanceDir目錄下面。config字段是你的Solr XML配置文件名。schema字段是你的Solr XML 數據架構(schema)文件名。

現在,點擊Add Core按鈕。如果你看到下圖所示,說明你新的core已經成功的添加到Solr中:

django-3-11

安裝Haystack

為了在Django中使用Solr,我們還需要Haystack。使用下面的命令,通過pip渠道安裝Haystack:

pip install django-haystack==2.4.0

Haystack能和一些搜索引擎后台交互。要使用Solr后端,你還需要安裝pysolr模塊。運行如下命令安裝它:

pip install pysolr==3.3.2

django-haystackpysolr完成安裝后,你還需要在你的項目中激活Haystack。打開settings.py文件,在INSTALLED_APPS設置中添加haystack,如下所示:

INSTALLED_APPS = (
	# ...
	haystack', 
)

你還需要為haystack定義搜索引擎后端。為此你要添加一個HAYSTACK_CONNECTIONS設置。在settings.py文件中添加如下內容:

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

要注意URL要指向我們的blog core。到此為止,Haystack已經安裝好並且已經為使用Solr做好了准備。

創建索引(indexex)

現在,我們必須將我們想要存儲在搜索引擎中的模型進行注冊。Haystack的慣例是在你的應用中創建一個search_indexes.py文件,然后在該文件中注冊你的模型(models)。在你的blog應用目錄下創建一個新的文件命名為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模型(model)的自定義SearchIndex。通過這個索引(index),我們告訴Haystack這個模型(model)中的哪些數據必須被搜索引擎編入索引。這個索引(index)是通過繼承indexes.SearchIndexindexes.Indexable構建的。每一個SearchIndex都需要它的其中一個字段擁有document=True。按照慣例,這個字段命名為text。這個字段是一個主要的搜索字段。通過使用use_template=True,我們告訴Haystack這個字段將會被渲染成一個數據模板(template)來構建document,它會被搜索引擎編入索引(index)。publish字段是一個日期字段也會被編入索引。我們通過model_attr參數來表明這個字段對應Post模型(model)的publish字段。這個字段將用 被索引的Post對象的publish字段的內容 索引。

額外的字段,像這個為搜索提供額外的過濾器(filters),是非常有用的。get_model()方法必須返回將儲存在這個索引中的documents的模型(model)。index_queryset()方法返回將會被編入索引的對象的查詢集(QuerySet)。請注意,我們只包含了發布狀態的帖子。

現在,在blog應用的模板(templates)目錄下創建目錄和文件search/indexes/blog/post_text.txt,然后添加如下代碼:

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

這是document模板(template)的默認路徑,是給索引中的text字段使用的。Haystack使用應用名和模型(model)名來動態構建這個路徑。每一次我們要索引一個對象,Haystack都會基於這個模板(template)構建一個document,之后在Solr的搜索引擎中索引這個document。

現在,我們已經有了一個自定義的搜索索引(index),我們需要創建合適的Solr架構(schema)。Solr的配置基於XML,所以我們必須為我們即將索引(index)的數據生成一個XML架構(schema)。非常幸運,haystack提供了一個基於我們的搜索索引(indexes),動態生成架構(schema)的方法。打開終端,運行以下命令:

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 version="1.0"?>開始拷貝所有輸出的XML內容直到最后的標簽(tag)</schema>,需要包含所有的標簽(tags)。

這個XML架構(schema)是用來將數據做索引(index)到Solr中。粘貼這個新的架構(schema)到你的Solr安裝路徑下的example目錄下的blog/conf/schema.xml文件中。schema.xml文件也被包含在本章的示例代碼中,所以你可以直接從示例代碼中復制出來使用。

在你的瀏覽器中打開 http://127.0.0.1:8983/solr/ 然后點擊Core Admin菜單欄,再點擊blog core,然后再點擊Reload按鈕:

django-3-12

我們重新載入這個core確保schema.xml的改變生效。當core重新載入完畢,新的架構(schema)准備好索引(index)新數據。

索引數據(Indexing data)

讓我們blog中的帖子編輯索引(index)到Solr中。打開終端,執行以下命令:

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將會清理搜索索引並且插入所有的發布狀態的blog帖子。你會看到如下輸出:

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

在瀏覽器中打開 http://127.0.0.1:8983/solr/#/blog 。在**Statistics*下方,你會看到被編入索引(indexed)documents的數量,如下所示:

django-3-13

現在,在瀏覽器中打開 http://127.0.0.1:8983/solr/#/blog/query 。這是一個Solr提供的查詢接口。點擊Execute query按鈕。默認的查詢會請求你的core中所有被編入索引(indexde)的documents。你會看到一串帶有這個查詢結果的JSON輸出。輸出的documents如下所示:

{
	"id": "blog.post.1",
	"text": "Who was Django Reinhardt?\njazz, music\nThe Django web framework was named after the amazing jazz guitarist Django Reinhardt.",
	"django_id": "1",
	"publish": "2015-09-20T12:49:52Z",
	"django_ct": "blog.post"
},

這是每個帖子在搜索索引(index)中存儲的數據。text字段包含了標題,通過逗號分隔的標簽(tags),還有帖子的內容,這個字段是在我們之前定義的模板(template)上構建的。

你已經使用過python manage.py rebuild_index來刪除索引(index)中的所有信息然后再次對documents進行索引(index)。為了不刪除所有對象而更新你的索引(index),你可以使用python manage.py update_index。另外,你可以使用參數--age=<num_hours>來更新少量的對象。為了保證你的Solr索引更新,你可以為這個操作設置一個定時任務(Cron job)。

創建一個搜索視圖(view)

現在,我們要開始創建一個自定義視圖(view)來允許我們的用戶搜索帖子。首先,我們需要一個搜索表單(form)。編輯blog應用下的forms.py文件,加入以下表單:

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

我們會使用query字段來讓用戶引入搜索條件(terms)。編輯blog應用下的views.py文件,加入以下代碼:

from .forms import EmailPostForm, CommentForm, 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})

在這個視圖(view)中,首先我們實例化了我們剛才創建的SearchForm.我們准備使用GET方法來提交這個表單(form),這樣可以使URL結果中包含查詢的參數。假設這個表單(form)已經被提交,我們將在request.GET字典中查找query參數。當表單(form)被提交后,我們通過提交的GET數據來實例化它,然后我們要檢查傳入的數據是否有效(valid)。如果這個表單是有效(valid)的,我們使用SearchQuerySet為所有被編入索引的並且主要內容中包含給予的查詢內容的Post對象來執行一次搜索。load_all()方法會立刻加載所有在數據庫中有關聯的Post對象。通過這個方法,我們使用數據庫對象填充搜索結果,避免當遍歷結果訪問對象數據時,每個對象訪問數據庫 (譯者注:這話不太好翻譯,看不懂的話可以看下原文)。最后,我們存儲total_results變量中結果的總數並傳遞本地的變量作為上下文(context)來渲染一個模板(template)。

搜索視圖(view)已經准備好了。我們還需要創建一個模板(template)來展示表單(form)和用戶執行搜索后返回的結果。在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 %}

就像在搜索視圖(view)中,我們做了區分如果這個表單(form)是基於query參數存在的情況下提交。在這個post提交前,我們展示了這個表單和一個提交按鈕。當這個post被提交,我們就展示查詢的操作結果,包含返回結果的總數和結果列表。每一個結果都是Solr返回和Haystack封裝處理后的document。我們需要使用result.object來獲取真實的和這個結果相關聯的Post對象。

最后,編輯blog應用下的urls.py文件,添加以下URL模式:

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

現在,在瀏覽器中打開 http://127.0.0.1:8000/blog/search/。你會看到如下圖所示的搜索表單(form):

django-3-14

現在,輸入一個查詢條件然后點擊Search按鈕。你會看到查詢搜索的結果,如下圖所示:

django-3-15

如今,在你的項目中你已經構建了一個強大的搜索引擎,但這僅僅只是開始,還有更多豐富的功能可以通過Solr和Haystack做到。Haystack包含視圖(views),表單(forms)以及搜索引擎的高級功能。你可以在 http://django-haystack.readthedocs.org/en/latest/ 頁面上閱讀Haystack文檔。

通過自定義架構(schema),Solr搜索引擎可以適配各種需求。你可以結合分析儀,斷詞,和令牌過濾器,這些是在索引或搜索的時間執行,為你的網站內容提供更准確的搜索。 你可以在 https://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters 看到所有的可能性。

總結

在這一章中,你學習了如何創建自定義的Django模板標簽(template tags)和過濾器(filters),提供給模板(template)實現一些自定義的功能。你還為搜索引擎創建了一個站點地圖(sitemap),爬取你的站點以及一個RSS feed 給用戶來訂閱。你還通過在項目中集成Slor和Haystack為blog應用構建了一個搜索引擎。

在下一章中,你將會學習到通過使用Django認證框架,如何構建一個社交網站,創建自定義的用戶畫像,以及創建社交認證。

譯者總結

終於將第三章也翻譯完成了,隔了一個星期沒翻譯,相對於前兩章,發現這章的翻譯速度又加快了那么一點點,兩天內完成翻譯。本章中創建自己的模板標簽和過濾器個人認為非常實用,我已經打算這段時間將手頭上上線的幾個項目都使用本章中提供的方法進行部分重構。本章最后部分的搜索引擎我倒是用不到,看官們可以也可以選擇不看,畢竟書中提供的版本太老了。。。。。。前三章都是圍繞博客應用展開(為什么大家都喜歡用博客應用做初始教程- -|||),下一章開始將開啟新的項目應用,我們下周或下下周或下個月繼續- -|||

2016年12月20日初稿發布

2017年3月11日精校完成(感謝感謝大牛@kukoo的精校!)

2017年4月6日再次精校(感謝大牛 @媽媽不在家 的精校!)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM