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

全文鏈接

第一章 創(chuàng)建一個blog應用
第二章 使用高級特性來增強你的blog
第三章 擴展你的blog應用
第四章上 創(chuàng)建一個社交網(wǎng)站
第四章下 創(chuàng)建一個社交網(wǎng)站
第五章 在你的網(wǎng)站中分享內(nèi)容
第六章 跟蹤用戶動作
第七章 建立一個在線商店
第八章 管理付款和訂單
第九章上 擴展你的商店
第九章下 擴展你的商店
第十章上 創(chuàng)建一個在線學習平臺
第十章下 創(chuàng)建一個在線學習平臺
第十一章 緩存內(nèi)容
第十二章 構(gòu)建一個API

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

2016年12月20日初稿發(fā)布

2017年3月11日精校完成(感謝感謝大牛@kukoo的精校?。?/strong>

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

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

第三章

擴展你的blog應用

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

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

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

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

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

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

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

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

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

進入你的blog應用目錄,創(chuàng)建一個新的目錄命名為templatetags然后在該目錄下創(chuàng)建一個空的init.py文件。接著在該目錄下繼續(xù)創(chuàng)建一個文件并命名為blog_tags.py。到此,我們的blog應用文件結(jié)構(gòu)應該如下所示:

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

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

我們將要開始創(chuàng)建一個簡單標簽(simple tag)來獲取blog中所有已發(fā)布的帖子。編輯你剛才創(chuàng)建的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)建了一個簡單的模板標簽(template tag)用來取回目前為止所有已發(fā)布的帖子。每一個模板標簽(template tags)都需要包含一個叫做register的變量來表明自己是一個有效的標簽(tag)庫。這個變量是template.Library的一個實例,它是用來注冊你自己的模板標簽(template tags)和過濾器(filter)的。我們用一個Python函數(shù)定義了一個名為total_posts的標簽,并用@register.simple-tag裝飾器定義此函數(shù)為一個簡單標簽(tag)并注冊它。

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

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

在使用自定義的模板標簽(template tags)之前,你必須使用{% load %}標簽在模板(template)中來加載它們才能有效。就像之前提到的,你需要使用包含了你的模板標簽(template tags)和過濾器(filter)的Python模塊的名字。打開blog/base.html模板(template)在頂部添加{% load blog_tags %}加載你自己的模板標簽(template tags)模塊。之后使用你創(chuàng)建的標簽(tag)來顯示你的帖子總數(shù)。只需要在你的模板(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/ 。你會在站點的側(cè)邊欄(sidebar)看到帖子的總數(shù),如下所示:

django-3-1

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

現(xiàn)在,我們要創(chuàng)建另外一個標簽(tag),可以在我們blog的側(cè)邊欄(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參數(shù)(默認是5)允許我們指定我們想要顯示的帖子數(shù)量。我們使用這個變量來限制Post.published.order_by('-publish')[:count]查詢的結(jié)果。請注意,這個函數(shù)返回了一個字典變量而不是一個簡單的值。包含標簽(inclusion tags)必須返回一個字典值,作為上下文(context)來渲染特定的模板(template)。包含標簽(inclusion tags)返回一個字典。這個我們剛創(chuàng)建的模板標簽(template tag)可以通過傳入可選的評論數(shù)量值來使用顯示,類似*{% show_latest_posts 3 %}。

現(xiàn)在,在blog/post/下創(chuàng)建一個新的模板(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變量展示了一個無序的帖子列表?,F(xiàn)在,編輯blog/base.hmtl模板(template)添加這個新的模板標簽(template tag)來展示最新的3條帖子。側(cè)邊欄(sidebar)區(qū)塊(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)被調(diào)用而且傳入了需要展示的帖子數(shù)(原文此處 number of comments,應該是寫錯了)。當前模板(template)在給予上下文(context)的位置會被渲染。

現(xiàn)在,回到瀏覽器刷新這個頁面,側(cè)邊欄應該如下圖所示:

django-3-2

最后,我們來創(chuàng)建一個分配標簽(assignment tag)。分配標簽(assignment tag)類似簡單標簽(simple tags)但是他們將結(jié)果存儲在給予的變量中。我們將會創(chuàng)建一個分配標簽(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()函數(shù),為了進行聚合查詢,使用了Count聚合函數(shù)。我們構(gòu)建了一個查詢集(QuerySet),聚合了每一個帖子的評論總數(shù)并保存在total_comments字段中,接著我們通過這個字段對查詢集(QuerySet)進行排序。我們還提供了一個可選的count變量,通過給定的值來限制返回的帖子數(shù)量。

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

編輯blog/base.html模板(template),在側(cè)邊欄(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)返回的結(jié)果到一個新的名為most_commented_posts變量中。之后,我們就可以用一個無序列表(unordered list)顯示返回的帖子。

現(xiàn)在,打開瀏覽器刷新頁面來看下最終的結(jié)果,應該如下圖所示:

django-3-3

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

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

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

我們這就創(chuàng)建一個自定義的過濾器(filter),可以在我們的blog帖子中使用markdown語法,然后在模板(template)中將帖子內(nèi)容轉(zhuǎn)變?yōu)镠TML。Markdown是一種非常容易使用的文本格式化語法并且它可以轉(zhuǎn)變?yōu)镠TML。你可以在 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)。為了避免我們的函數(shù)名和markdown模板名起沖突,我們將我們的函數(shù)命名為markdown_format,然后將過濾器(filter)命名為markdown,在模板(template)中的使用方法類似{{ variable|markdown }}。Django會轉(zhuǎn)義過濾器(filter)生成的HTML代碼。我們使用Django提供的mark_safe方法來標記結(jié)果,在模板(template)中作為安全的HTML被渲染。默認的,Django不會信賴任何HTML代碼并且在輸出之前會進行轉(zhuǎn)義。唯一的例外就是被標記為安全轉(zhuǎn)義的變量。這樣的操作可以阻止Django從輸出中執(zhí)行潛在的危險的HTML,并且允許你創(chuàng)建一些例外情況只要你知道你正在運行的是安全的HTML。

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

{% load blog_tags %}

post/detail.thml模板中,替換以下內(nèi)容:

{{ post.body|linebreaks }}

替換成:

{{ post.body|markdown }}

之后,在post/list.html文件中,替換以下內(nèi)容:

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

替換成:

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

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

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

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

django-3-4

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

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

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

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

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

現(xiàn)在,運行以下命令在數(shù)據(jù)庫中為Django的站點應用創(chuàng)建所需的表:

python manage.py migrate

你會看到如下的輸出內(nèi)容:

Applying sites.0001_initial... OK

sites應用現(xiàn)在已經(jīng)在數(shù)據(jù)庫中進行了同步?,F(xiàn)在,在你的blog應用目錄下創(chuà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

通過繼承sitemaps模塊提供的Sitemap類我們創(chuàng)建一個自定義的站點地圖(sitemap)。changefreqpriority屬性表明了帖子頁面修改的頻率和它們在網(wǎng)站中的關(guān)聯(lián)性(最大值是1)。items()方法返回了在這個站點地圖(sitemap)中所包含對象的查詢集(QuerySet)。默認的,Django在每個對象中調(diào)用get_absolute_url()方法來獲取它的URL。請記住,這個方法是我們在第一章(創(chuàng)建一個blog應用)中創(chuàng)建的,用來獲取每個帖子的標準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)中。現(xiàn)在,在瀏覽器中打開 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>

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

django-3-5

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

django-3-6

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

為你的blog帖子創(chuàng)建feeds

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

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

在blog應用的目錄下創(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)

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

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

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

from .feeds import LatestPostsFeed

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

在瀏覽器中轉(zhuǎn)到 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  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的側(cè)邊欄(sitebar)添加一個feed訂閱(subscription)鏈接。打開blog/base.html模板(template),在側(cè)邊欄(sitebar)的div中的帖子總數(shù)下添加如下代碼:

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

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

django-3-7

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

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

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

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

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

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

安裝Solr

你需要1.7或更高的Java運行環(huán)境來安裝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配置。在這個目錄下,通過以下命令,用內(nèi)置的Jetty web服務運行Solr

java -jar start.jar

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

django-3-8

以上是Solr的管理控制臺。這個控制臺向你展示了數(shù)據(jù)統(tǒng)計,允許你管理你的搜索后端,檢測索引數(shù)據(jù),并且執(zhí)行查詢操作。

創(chuàng)建一個Solr core

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

django-3-9

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

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>

這是一個空的架構(gòu)(schema)。這個架構(gòu)(schema)定義了在搜索引擎中將被索引到的數(shù)據(jù)的字段和字段類型。之后我們要使用一個自定義的架構(gòu)(schema)。

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

django-3-10

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

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

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

現(xiàn)在,點擊Add Core按鈕。如果你看到下圖所示,說明你新的core已經(jīng)成功的添加到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設(shè)置中添加haystack,如下所示:

INSTALLED_APPS = (
    # ...
    haystack', 
)

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

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

要注意URL要指向我們的blog core。到此為止,Haystack已經(jīng)安裝好并且已經(jīng)為使用Solr做好了準備。

創(chuàng)建索引(indexex)

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

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

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

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

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

現(xiàn)在,我們已經(jīng)有了一個自定義的搜索索引(index),我們需要創(chuàng)建合適的Solr架構(gòu)(schema)。Solr的配置基于XML,所以我們必須為我們即將索引(index)的數(shù)據(jù)生成一個XML架構(gòu)(schema)。非常幸運,haystack提供了一個基于我們的搜索索引(indexes),動態(tài)生成架構(gòu)(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內(nèi)容直到最后的標簽(tag)</schema>,需要包含所有的標簽(tags)。

這個XML架構(gòu)(schema)是用來將數(shù)據(jù)做索引(index)到Solr中。粘貼這個新的架構(gòu)(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重新載入完畢,新的架構(gòu)(schema)準備好索引(index)新數(shù)據(jù)。

索引數(shù)據(jù)(Indexing data)

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

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ā)布狀態(tài)的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的數(shù)量,如下所示:

django-3-13

現(xiàn)在,在瀏覽器中打開 http://127.0.0.1:8983/solr/#/blog/query 。這是一個Solr提供的查詢接口。點擊Execute query按鈕。默認的查詢會請求你的core中所有被編入索引(indexde)的documents。你會看到一串帶有這個查詢結(jié)果的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)中存儲的數(shù)據(jù)。text字段包含了標題,通過逗號分隔的標簽(tags),還有帖子的內(nèi)容,這個字段是在我們之前定義的模板(template)上構(gòu)建的。

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

創(chuàng)建一個搜索視圖(view)

現(xiàn)在,我們要開始創(chuàng)建一個自定義視圖(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)中,首先我們實例化了我們剛才創(chuàng)建的SearchForm.我們準備使用GET方法來提交這個表單(form),這樣可以使URL結(jié)果中包含查詢的參數(shù)。假設(shè)這個表單(form)已經(jīng)被提交,我們將在request.GET字典中查找query參數(shù)。當表單(form)被提交后,我們通過提交的GET數(shù)據(jù)來實例化它,然后我們要檢查傳入的數(shù)據(jù)是否有效(valid)。如果這個表單是有效(valid)的,我們使用SearchQuerySet為所有被編入索引的并且主要內(nèi)容中包含給予的查詢內(nèi)容的Post對象來執(zhí)行一次搜索。load_all()方法會立刻加載所有在數(shù)據(jù)庫中有關(guān)聯(lián)的Post對象。通過這個方法,我們使用數(shù)據(jù)庫對象填充搜索結(jié)果,避免當遍歷結(jié)果訪問對象數(shù)據(jù)時,每個對象訪問數(shù)據(jù)庫 (譯者注:這話不太好翻譯,看不懂的話可以看下原文)。最后,我們存儲total_results變量中結(jié)果的總數(shù)并傳遞本地的變量作為上下文(context)來渲染一個模板(template)。

搜索視圖(view)已經(jīng)準備好了。我們還需要創(chuàng)建一個模板(template)來展示表單(form)和用戶執(zhí)行搜索后返回的結(jié)果。在templates/blog/post/目錄下創(chuàng)建一個新的文件命名為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)中,我們做了區(qū)分如果這個表單(form)是基于query參數(shù)存在的情況下提交。在這個post提交前,我們展示了這個表單和一個提交按鈕。當這個post被提交,我們就展示查詢的操作結(jié)果,包含返回結(jié)果的總數(shù)和結(jié)果列表。每一個結(jié)果都是Solr返回和Haystack封裝處理后的document。我們需要使用result.object來獲取真實的和這個結(jié)果相關(guān)聯(lián)的Post對象。

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

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

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

django-3-14

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

django-3-15

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

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

總結(jié)

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

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

譯者總結(jié)

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

2016年12月20日初稿發(fā)布

2017年3月11日精校完成(感謝感謝大牛@kukoo的精校?。?/strong>

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

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

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

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