譯者夜夜月,已獲作者授權轉載。

原文連結:

http://www。

jianshu。com/p/53bf20eb4

79c

書籍出處:Django By Example

原作者:Antonio Melé

2016年12月13日釋出(3天完成第二章的翻譯,但沒有進行校對,有很多錯別字以及模糊不清的語句,請大家見諒)

2017年2月17日校對完成(不是精校,希望大家多指出需要修改的地方)

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

2017年3月21日再度精校(感謝大牛 @媽媽不在家 的精校!初版我已經不敢再看!)

(譯者注:翻譯完第一章後,發現翻譯第二章的速度上升了不少,難道這就是傳說中的經驗值提升了?)

第二章

用高階特性來增強你的blog

在上一章中,你建立了一個基礎的部落格應用。現在你將利用一些高階的特性例如透過email來分享帖子,新增評論,給帖子打上tag,檢索出相似的帖子等將它改造成為一個功能更加齊全的部落格。在本章中,你將會學習以下幾點:

透過Django傳送email

在檢視(views)中建立並操作表單

透過模型(models)建立表單

整合第三方應用

構建複雜的查詢集(QuerySets)

透過email分享帖子

首先,我們會允許使用者透過傳送郵件來分享他們的帖子。讓我們花費一小會時間來想下,根據在上一章中學到的知識,你該如何使用views,urls和templates來建立這個功能。現在,核對一下你需要哪些才能允許你的使用者透過郵件來發送帖子。你需要做到以下幾點:

給使用者建立一個表單來填寫他們的姓名,email,收件人以及評論,評論不是必選項。

views。py

檔案中建立一個檢視(view)來操作釋出的資料和傳送email

在blog應用的

urls。py

中為新的檢視(view)新增一個URL模式

建立一個模板(template)來展示這個表單

使用Django建立表單

讓我們開始建立一個表單來分享帖子。Django有一個內建的表單框架允許你透過簡單的方式來建立表單。這個表單框架允許你定義你的表單欄位,指定這些欄位必須展示的方式,以及指定這些欄位如何驗證輸入的資料。Django表單框架還提供了一種靈活的方式來渲染表單以及操作資料。

Django提供了兩個可以建立表單的基本類:

Form: 允許你建立一個標準表單

ModelForm: 允許你建立一個可用於建立或者更新model例項的表單

首先,在你blog應用的目錄下建立一個

forms。py

檔案,輸入以下程式碼:

from django import forms

class EmailPostForm(forms。Form):

name = forms。CharField(max_length=25)

email = forms。EmailField()

to = forms。EmailField()

comments = forms。CharField(required=False,

widget=forms。Textarea)

這是你的第一個Django表單。看下程式碼:我們已經建立了一個繼承了基礎

Form

類的表單。我們使用不同的欄位型別以使Django有依據的來驗證欄位。

表單可以存在你的Django專案的任何地方,但按照慣例將它們放在每一個應用下面的

forms。py

檔案中

name

欄位是一個

CharField

。這種型別的欄位被渲染成HTML元素。每種欄位型別都有預設的控制元件來確定它在HTML中的展示形式。透過改變控制元件的屬性可以重寫預設的控制元件。在comment欄位中,我們使用HTML元素而不是使用預設的元素來顯示它。

欄位驗證取決於欄位型別。例如,

email

to

欄位是

EmailField

,這兩個欄位都需要一個有效的email地址,否則欄位驗證將會丟擲一個

forms。ValidationError

異常導致表單驗證不透過。在表單驗證的時候其他的引數也會被考慮進來:我們將name欄位定義為一個最大長度為25的字串;透過設定required=False讓comments的欄位可選。所有這些也會被考慮到欄位驗證中去。目前我們在表單中使用的這些欄位型別只是Django支援的表單欄位的一部分。要檢視更多可利用的表單欄位,你可以訪問:Form fields | Django documentation | Django

在檢視(views)中操作表單

當表單成功提交後你必須建立一個新的檢視(views)來操作表單和傳送email。編輯blog應用下的

views。py

檔案,新增以下程式碼:

from 。forms import EmailPostForm

def post_share(request, post_id):

# retrieve post by id

post = get_object_or_404(Post, id=post_id, status=‘published’)

if request。method == ‘POST’:

# Form was submitted

form = EmailPostForm(request。POST)

if form。is_valid():

# Form fields passed validation

cd = form。cleaned_data

# 。。。 send email

else:

form = EmailPostform()

return render(request, ‘blog/post/share。html’, {‘post’: post,

‘form: form})

該檢視(view)完成了以下工作:

我們定義了

post_share

檢視,引數為

request

物件和

post_id

我們使用

get_object_or_404

快捷方法透過ID獲取對應的帖子,並且確保獲取的帖子有一個

published

狀態。

我們使用同一個檢視(view)來展示初始表單和處理提交後的資料。我們會區別被提交的表單和不基於這次請求方法的表單。我們將使用

POST

來提交表單。如果我們得到一個

GET

請求,一個空的表單必須顯示,而如果我們得到一個

POST

請求,則表單需要提交和處理。因此,我們使用request。method == ’POST‘來區分這兩種場景。

下面是展示和操作表單的過程:

1。透過GET請求檢視(view)被初始載入後,我們建立一個新的表單例項,用來在模板(template)中顯示一個空的表單:

form = EmailPostForm()

2。當用戶填寫好了表單並透過

POST

提交表單。之後,我們會用儲存在request。POST中提交的資料建立一個表單例項。

if request。method == ’POST‘:

# Form was submitted

form = EmailPostForm(request。POST)

3。在以上步驟之後,我們使用表單的

is_valid()

方法來驗證提交的資料。這個方法會驗證表單引進的資料,如果所有的欄位都是有效資料,將會返回

True

。一旦有任何一個欄位是無效的資料,

is_valid()

就會返回

False

。你可以透過訪問 form。errors來檢視所有驗證錯誤的列表。

4如果表單資料驗證沒有透過,我們會再次使用提交的資料在模板(template)中渲染表單。我們會在模板(template)中顯示驗證錯誤的提示。

5。如果表單資料驗證透過,我們透過訪問form。cleaned_data獲取驗證過的資料。這個屬性是一個表單欄位和值的字典。

如果你的表單資料沒有透過驗證,

cleaned_data

只會包含驗證透過的欄位

現在,你需要學習如何使用Django來發送email,把所有的事情結合起來。

使用Django傳送email

使用Django傳送email非常簡單。首先,你需要有一個本地的SMTP服務或者透過在你專案的settings。py檔案中新增以下設定去定義一個外部SMTP伺服器的配置:

EMAIL_HOST: SMTP服務地址。預設本地。

EMAIL_POSR: SMATP服務埠,預設25。

EMAIL_HOST_USER: SMTP服務的使用者名稱。

EMAIL_HOST_PASSWORD: SMTP服務的密碼。

EMAIL_USE_TLS: 是否使用TLS加密連線。

EMAIL_USE_SSL: 是否使用隱式的SSL加密連線。

如果你沒有本地SMTP服務,你可以使用你的email服務供應商提供的SMTP服務。下面提供了一個簡單的例子展示如何透過使用Google賬戶的Gmail服務來發送email:

EMAIL_HOST = ’smtp。gmail。com‘

EMAIL_HOST_USER = ’your_account@gmail。com‘

EMAIL_HOST_PASSWORD = ’your_password‘

EMAIL_PORT = 587

EMAIL_USE_TLS = True

執行命令python manage。py shell來開啟Python shell,傳送一封email如下所示:

>>> from django。core。mail import send_mail

>>> send_mail(’Django mail‘, ’This e-mail was sent with Django。‘,’your_account@gmail。com‘, [’your_account@gmail。com‘], fail_silently=False)

send_mail()

方法需要這些引數:郵件主題,內容,傳送人以及一個收件人的列表。透過設定可選引數fail_silently=False,我們告訴這個方法如果email沒有傳送成功那麼需要丟擲一個異常。如果你看到輸出是

1

,證明你的email傳送成功了。如果你使用之前的配置用Gmail來發送郵件,你可能需要去

https://www。

google。com/settings/sec

urity/lesssecureapps

去開通一下低安全級別應用的許可權。(譯者注:練習時老老實實用QQ郵箱吧)

現在,我們要將以上程式碼新增到我們的檢視(view)中。在blog應用下的

views。py

檔案中編輯

post_share

檢視(view)如下所示:

from django。core。mail import send_mail

def post_share(request, post_id):

# Retrieve post by id

post = get_object_or_404(Post, id=post_id, status=’published‘)

sent = False

if request。method == ’POST‘:

# Form was submitted

form = EmailPostForm(request。POST)

if form。is_valid():

# Form fields passed validation

cd = form。cleaned_data

post_url = request。build_absolute_url(

post。get_absolute_url())

subject = ’{} ({}) recommends you reading “{}”‘。format(cd[’name‘], cd[’email‘], post。title)

message = ’Read “{}” at {}\n\n{}\‘s comments: {}’。format(post。title, post_url, cd[‘name’], cd[‘comments’])

send_mail(subject, message, ‘admin@myblog。com’,[cd[‘to’]])

sent = True

else:

form = EmailPostForm()

return render(request, ‘blog/post/share。html’, {‘post’: post,

‘form’: form,

‘sent’: sent})

請注意,我們聲明瞭一個

sent

變數並且當帖子被成功傳送時賦予它

True

。當表單成功提交的時候,我們之後將在模板(template)中使用這個變數顯示一條成功提示。由於我們需要在email中包含帖子的超連結,所以我們透過使用post。get_absolute_url()方法來獲取到帖子的絕對路徑。我們將這個絕對路徑作為request。build_absolute_uri()的輸入值來構建一個完整的包含了HTTP schema和主機名的url。我們透過使用驗證過的表單資料來構建email的主題和訊息內容並最終給表單to欄位中包含的所有email地址傳送email。

現在你的檢視(view)已經完成了,別忘記為它去新增一個新的URL模式。開啟你的blog應用下的

urls。py

檔案新增

post_share

的URL模式如下所示:

urlpatterns = [

# 。。。

url(r‘^(?P\d+)/share/$’, views。post_share,

name=‘post_share’),

在模板(templates)中渲染表單

在透過建立表單,編寫檢視(view)以及新增URL模式後,我們就只剩下為這個檢視(view)新增模板(tempalte)了。在blog/templates/blog/post/目錄下建立一個新的檔案並命名為

share。html

。在該檔案中新增如下程式碼:

{% extends “blog/base。html” %}

{% block title %}Share a post{% endblock %}

{% block content %}

{% if sent %}

E-mail successfully sent

“{{ post。title }}” was successfully sent to {{ cd。to }}。

{% else %}

Share “{{ post。title }}” by e-mail

{{ form。as_p }}

{% csrf_token %}

{% endif %}

{% endblock %}

這個模板(tempalte)專門用來顯示一個表單或一條成功提示資訊。如你所見,我們建立的HTML表單元素裡面表明了它必須透過POST方法提交:

接下來我們要包含真實的表單例項。我們告訴Django用as_p方法利用HTML的

元素來渲染它的欄位。我們也可以使用as_ul利用無序列表來渲染表單或者使用as_table利用HTML表格來渲染。如果我們想要逐一渲染每一個欄位,我們可以迭代欄位。例如下方的例子:

{% for field in form %}

{{ field。errors }}

{{ field。label_tag }} {{ field }}

{% endfor %}

{% csrf_token %}模板(tempalte)標籤(tag)引進了可以避開

Cross-Site request forgery(CSRF)

攻擊的自動生成的令牌,這是一個隱藏的欄位。這些攻擊由惡意的站點或者可以在你的站點中為使用者執行惡意行為的程式組成。透過訪問 https://en。wikipedia。org/wiki/Cross-site_request_forgery你可以找到更多的資訊 。

上述的標籤(tag)生成的隱藏欄位就像下面一樣:

預設情況下,Django在所有的POST請求中都會檢查CSRF標記(token)。請記住要在所有使用POST方法提交的表單中包含

csrf_token

標籤。(譯者注:當然你也可以關閉這個檢查,註釋掉app_list中的csrf應用即可,我就是這麼做的,因為我懶)

編輯你的

blog/post/detail。html

模板(template),在{{ post。body|linebreaks }}變數後面新增如下的連結來分享帖子的URL:

Share this post

請記住,我們透過使用Django提供的{% url %}模板(template)標籤(tag)來動態的生成URL。我們以

blog

為名稱空間,以

post_share

為URL,同時傳遞帖子ID作為引數來構建絕對的URL。

現在,透過python manage。py runserver命令來啟動開發伺服器,在瀏覽器中開啟

http://

127。0。0。1:8000/blog/

。點選任意一個帖子標題檢視詳情頁面。在帖子內容的下方,你會看到我們剛剛新增的連結,如下所示:

django-2-1

點選Share this post,你會看到包含透過email分享帖子的表單的頁面。看上去如下所示:

django-2-2

這個表單的CSS樣式被包含在示例程式碼中的 static/css/blog。css檔案中。當你點選Send e-mail按鈕,這個表單會提交併驗證。如果所有的欄位都通過了驗證,你會得到一條成功資訊如下所示:

django-2-3

如果你輸入了無效資料,你會看到表單被再次渲染,並展示出驗證錯誤資訊,如下所示:

django-2-4

建立一個評論系統

現在我們準備為blog建立一個評論系統,這樣使用者可以在帖子上進行評論。需要做到以下幾點來建立一個評論系統:

建立一個模型(model)用來儲存評論

建立一個表單用來提交評論並且驗證輸入的資料

新增一個檢視(view)來處理表單和儲存新的評論到資料庫中

編輯帖子詳情模板(template)來展示評論列表以及用來新增新評論的表單

首先,讓我們建立一個模型(model)來儲存評論。開啟你的blog應用下的

models。py

檔案新增如下程式碼:

class Comment(models。Model):

post = models。ForeignKey(Post, related_name=‘comments’)

name = models。CharField(max_length=80)

email = models。EmailField()

body = models。TextField()

created = models。DateTimeField(auto_now_add=True)

updated = models。DateTimeField(auto_now=True)

active = models。BooleanField(default=True)

class Meta:

ordering = (‘created’,)

def __str__(self):

return ‘Comment by {} on {}’。format(self。name, self。post)

以上就是我們的

Comment

模型(model)。它包含了一個外來鍵將一個單獨的帖子和評論關聯起來。在

Comment

模型(model)中定義多對一(many-to-one)的關係是因為每一條評論只能在一個帖子下生成,而每一個帖子又可能包含多個評論。

related_name

屬性允許我們給這個屬性命名,這樣我們就可以利用這個關係從相關聯的物件反向定位到這個物件。定義好這個之後,我們透過使用 comment。post就可以從一條評論來取到對應的帖子,以及透過使用post。comments。all()來取回一個帖子所有的評論。如果你沒有定義

related_name

屬性,Django會使用這個模型(model)的名稱加上

_set

(在這裡是:comment_set)來命名從相關聯的物件反向定位到這個物件的manager。

訪問Many-to-one relationships, 你可以學習更多關於多對一的關係。

我們用一個

active

布林欄位用來手動禁用那些不合適的評論。預設情況下,我們根據

created

欄位,對評論按時間順序進行排序。

你剛建立的這個新的

Comment

模型(model)並沒有同步到資料庫中。執行以下命令生成一個新的反映了新模型(model)建立的資料遷移:

python manage。py makemigrations blog

你會看到如下輸出:

Migrations for ‘blog’:

0002_comment。py:

- Create model Comment

Django在blog應用下的

migrations/

目錄中生成了一個

0002_comment。py

檔案。現在你需要建立一個關聯資料庫模式並且將這些改變應用到資料庫中。執行以下命令來執行已經存在的資料遷移:

python manage。py migrate

你會獲取以下輸出:

Applying blog。0002_comment。。。 OK

我們剛剛建立的資料遷移已經被執行,現在一張

blog_comment

表已經存在資料庫中。

現在,我們可以新增我們新的模型(model)到管理站點中並透過簡單的介面來管理評論。開啟blog應用下的

admin。py

檔案,新增comment model的匯入,新增如下內容:

from 。models import Post, Comment

class CommentAdmin(admin。ModelAdmin):

list_display = (‘name’, ‘email’, ‘post’, ‘created’, ‘active’)

list_filter = (‘active’, ‘created’, ‘updated’)

search_fields = (‘name’, ‘email’, ‘body’)

admin。site。register(Comment, CommentAdmin)

執行命令python manage。py runserver來啟動開發伺服器然後在瀏覽器中開啟

http://

127。0。0。1:8000/admin/

。你會看到新的模型(model)在Blog區域中出現,如下所示:

django-2-5

我們的模型(model)現在已經被註冊到了管理站點,這樣我們就可以使用簡單的介面來管理評論例項。

透過模型(models)建立表單

我們仍然需要構建一個表單讓我們的使用者在blog帖子下進行評論。請記住,Django有兩個用來建立表單的基礎類:

Form

ModelForm

。你先前已經使用過第一個讓使用者透過email來分享帖子。在當前的例子中,你將需要使用

ModelForm

,因為你必須透過你的

Comment

模型(model)動態的建立表單。編輯blog應用下的

forms。py

,新增如下程式碼:

from 。models import Comment

class CommentForm(forms。ModelForm):

class Meta:

model = Comment

fields = (‘name’, ‘email’, ‘body’)

根據模型(model)建立表單,我們只需要在這個表單的

Meta

類裡表明使用哪個模型(model)來構建表單。Django將會解析model併為我們動態的建立表單。每一種模型(model)欄位型別都有對應的預設表單欄位型別。表單驗證時會考慮到我們定義模型(model)欄位的方式。Django為模型(model)中包含的每個欄位都建立了表單欄位。然而,使用

fields

列表你可以明確的告訴框架你想在你的表單中包含哪些欄位,或者使用

exclude

列表定義你想排除在外的那些欄位。對於我們的

CommentForm

來說,我們在表單中只需要

name

email

,和

body

欄位,因為我們只需要用到這3個欄位讓我們的使用者來填寫。

在檢視(views)中操作

ModelForms

為了能更簡單的處理它,我們會使用帖子的詳情檢視(view)來例項化表單。編輯

views。py

檔案(注: 原文此處有錯,應為 views。py),匯入

Comment

模型(model)和

CommentForm

表單,並且修改

post_detail

檢視(view)如下所示:

from 。models import Post, Comment

from 。forms import EmailPostForm, CommentForm

def post_detail(request, year, month, day, post):

post = get_object_or_404(Post, slug=post,

status=‘published’,

publish__year=year,

publish__month=month,

publish__day=day)

# List of active comments for this post

comments = post。comments。filter(active=True)

new_comment = None

if request。method == ‘POST’:

# A comment was posted

comment_form = CommentForm(data=request。POST)

if comment_form。is_valid():

# Create Comment object but don‘t save to database yet

new_comment = comment_form。save(commit=False)

# Assign the current post to the comment

new_comment。post = post

# Save the comment to the database

new_comment。save()

else:

comment_form = CommentForm()

return render(request,

’blog/post/detail。html‘,

{’post‘: post,

’comments‘: comments,

’new_comment‘: new_comment,

’comment_form‘: comment_form})

讓我們來回顧一下我們剛才對檢視(view)添加了哪些操作。我們使用

post_detail

檢視(view)來顯示帖子和該帖子的評論。我們添加了一個查詢集(QuerySet)來獲取這個帖子所有有效的評論:

comments = post。comments。filter(active=True)

我們從

post

物件開始構建這個查詢集(QuerySet)。我們使用關聯物件的manager,這個manager是我們在

Comment

模型(model)中使用

related_name

關係屬性為

comments

定義的。

我們還在這個檢視(view)中讓我們的使用者新增一條新的評論。因此,如果這個檢視(view)是透過GET請求被載入的,那麼我們用comment_fomr = commentForm()來建立一個表單例項。如果是透過POST請求,我們使用提交的資料並且用

is_valid()

方法驗證這些資料去例項化表單。如果這個表單是無效的,我們會用驗證錯誤資訊渲染模板(template)。如果表單透過驗證,我們會做以下的操作:

1。我們透過呼叫這個表單的

save()

方法建立一個新的

Comment

物件,如下所示:

new_comment = comment_form。save(commit=False)

Save()

方法建立了一個表單連結的model的例項,並將它儲存到資料庫中。如果你呼叫這個方法時設定comment=False,你建立的模型(model)例項不會即時儲存到資料庫中。當你想在最終儲存之前修改這個model物件會非常方便,我們接下來將做這一步驟。

save()

方法是給

ModelForm

用的,而不是給

Form

例項用的,因為

Form

例項沒有關聯上任何模型(model)。

2。我們為我們剛建立的評論分配一個帖子:

new_comment。post = post

透過以上動作,我們指定新的評論是屬於這篇給定的帖子。

3。最後,我們用下面的程式碼將新的評論儲存到資料庫中:

new_comment。save()

我們的檢視(view)已經準備好顯示和處理新的評論了。

在帖子詳情模板(template)中新增評論

我們為帖子建立了一個管理評論的功能。現在我們需要修改我們的

post_detail。html

模板(template)來適應這個功能,透過做到以下步驟:

顯示這篇帖子的評論總數

顯示評論的列表

顯示一個表單給使用者來新增新的評論

首先,我們來新增評論的總數。開啟

views_detail。html

(譯者注:根據官網最新更正修改,原文是blog_detail。html)模板(template)在

content

區塊中新增如下程式碼:

{% with comments。count as total_comments %}

{{ total_comments }} comment{{ total_comments|pluralize }}

{% endwith %}

在模板(template)中我們使用Django ORM執行comments。count() 查詢集(QuerySet)。注意,Django模板(template)語言中不使用圓括號來呼叫方法。{% with %} 標籤(tag)允許我們分配一個值給新的變數,這個變數可以一直使用直到遇到{% endwith %}標籤(tag)。

{% with %}模板(template)標籤(tag)是非常有用的,可以避免直接操作資料庫或避免多次呼叫花費較多的方法。

根據

total_comments

的值,我們使用

pluralize

模板(template)過濾器(filter)為單詞

comment

顯示覆數字尾。模板(Template)過濾器(filters)獲取到他們輸入的變數值,返回計算後的值。我們將會在

第三章 擴充套件你的部落格應用

中討論更多的模板過濾器(tempalte filters)。

pluralize

模板(template)過濾器(filter)在值不為1時,會在值的末尾顯示一個“s”。之前的文字將會被渲染成類似:

0 comments

1 comment

或者

N comments

。Django內建大量的模板(template)標籤(tags)和過濾器(filters)來幫助你以你想要的方式來顯示資訊。

現在,讓我們加入評論列表。在模板(template)中之前的程式碼後面加入以下內容:

{% for comment in comments %}

Comment {{ forloop。counter }} by {{ comment。name }}

{{ comment。created }}

{{ comment。body|linebreaks }}

{% empty %}

There are no comments yet。

{% endfor %}

我們使用{% for %}模板(template)標籤(tag)來迴圈所有的評論。如果

comments

列為空我們會顯示一個預設的資訊,告訴我們的使用者這篇帖子還沒有任何評論。我們使用 {{ forloop。counter }}變數來列舉所有的評論,在每次迭代中該變數都包含迴圈計數。之後我們顯示傳送評論的使用者名稱,日期,和評論的內容。

最後,當表單提交成功後,你需要渲染表單或者顯示一條成功的資訊來代替之前的內容。在之前的程式碼後面新增如下內容:

{% if new_comment %}

Your comment has been added。

{% else %}

Add a new comment

{{ comment_form。as_p }}

{% csrf_token %}

{% endif %}

這段程式碼非常簡潔明瞭:如果

new_comment

物件存在,我們會展示一條成功資訊因為成功建立了一條新評論。否則,我們用段落

元素渲染表單中每一個欄位,並且包含

POST

請求需要的

CSRF

令牌。在瀏覽器中開啟

http://

127。0。0。1:8000/blog/

然後點選任意一篇帖子的標題檢視它的詳情頁面。你會看到如下頁面展示:

django-2-6

使用該表單新增數條評論。這些評論會在你的帖子下面根據時間排序來展示,類似下圖:

django-2-7

在你的瀏覽器中開啟

http://

127。0。0。1:8000/admin/bl

og/comment/

。你會在管理頁面中看到你建立的評論列表。點選其中一個進行編輯,取消選擇Active複選框,然後點選Save按鈕。你會再次被重定向到評論列表頁面,剛才編輯的評論Save列將會顯示成一個沒有啟用的圖示。類似下圖的第一個評論:

django-2-8

增加標籤(tagging)功能

在實現了我們的評論系統之後,我們準備創建立一個方法來給我們的帖子新增標籤。我們將透過在我們的專案中整合第三方的Django標籤應用來完成這個功能。

django-taggit

是一個可複用的應用,它會提供給你一個

Tag

模型(model)和一個管理器(manager)來方便的給任何模型(model)新增標籤。你可以在 alex/django-taggit 看到它的原始碼。

首先,你需要透過pip安裝django-taggit,執行以下命令:

pip install django-taggit==0。17。1**(譯者注:根據@孤獨狂飲 驗證,直接 `pip install django-taggit` 安裝最新版即可,原作者提供的版本過舊會有問題,感謝@孤獨狂飲)**

之後開啟

mysite

專案下的

settings。py

檔案,在

INSTALLED_APPS

設定中設定如下:

INSTALLED_APPS = (

# 。。。

’blog‘,

’taggit‘,

開啟你的blog應用下的

model。py

檔案,給

Post

模型(model)新增

django-taggit

提供的

TaggableManager

管理器(manager),使用如下程式碼:

from taggit。managers import TaggableManager

class Post(models。Model):

# 。。。

tags = TaggableManager()

這個

tags

管理器(manager)允許你給

Post

物件新增,獲取以及移除標籤。

執行以下命令為你的模型(model)改變建立一個數據庫遷移:

python manage。py makemigrations blog

你會看到如下輸出:

Migrations for ’blog‘:

0003_post_tags。py:

- Add field tags to post

現在,執行以下程式碼在資料庫中生成

django-taggit

模型(model)對應的表以及同步你的模型(model)的改變:

python manage。py migrate

你會看到以下輸出,:

Applying taggit。0001_initial。。。 OK

Applying taggit。0002_auto_20150616_2121。。。 OK

Applying blog。0003_post_tags。。。 OK

你的資料庫現在已經可以使用

django-taggit

模型(model)。開啟終端執行命令python manage。py shell來學習如何使用

tags

管理器(manager)。首先,我們取回我們的其中一篇帖子(該帖子的ID為1):

>>> from blog。models import Post

>>> post = Post。objects。get(id=1)

之後為它新增一些標籤並且取回它的標籤來檢查標籤是否新增成功:

>>> post。tags。add(’music‘, ’jazz‘, ’django‘)

>>> post。tags。all()

最後,移除一個標籤並且再次檢查標籤列表:

>>> post。tags。remove(’django‘)

>>> post。tags。all()

非常簡單,對吧?執行命令python manage。py runserver啟動開發伺服器,在瀏覽器中開啟

http://

127。0。0。1:8000/admin/ta

ggit/tag/

。你會看到管理頁面包含了

taggit

應用的

Tag

物件列表:

django-2-9

轉到

http://

127。0。0。1:8000/admin/bl

og/post/

並點選一篇帖子進行編輯。你會看到帖子中包含了一個新的Tags欄位如下所示,你可以非常容易的編輯它:

django-2-10

現在,我們準備編輯我們的blog帖子來顯示這些標籤。開啟

blog/post/list。html

模板(template)在帖子標題下方新增如下HTML程式碼:

Tags: {{ post。tags。all|join:“, ” }}

join

模板(template)過濾器(filter)的功能類似python字串的join()方法,將給定的字串連線起來。在瀏覽器中開啟

http://

127。0。0。1:8000/blog/

。 你會看到每一個帖子的標題下面的標籤列表:

django-2-11

現在,讓我們來編輯我們的

post_list

檢視(view)讓使用者可以列出打上了特定標籤的所有帖子。開啟blog應用下的

views。py

檔案,從

django-taggit

中匯入

Tag

模型(model),然後修改

post_list

檢視(view)讓它可以透過標籤選擇性的過濾,如下所示:

from taggit。models import Tag

def post_list(request, tag_slug=None):

object_list = Post。published。all()

tag = None

if tag_slug:

tag = get_object_or_404(Tag, slug=tag_slug)

object_list = object_list。filter(tags__in=[tag])

# 。。。

這個檢視(view)做了以下工作:

1。檢視(view)帶有一個可選的

tag_slug

引數,預設是一個

None

值。這個引數會帶進URL中。

2。檢視(view)的內部,我們構建了初始的查詢集(QuerySet),取回所有釋出狀態的帖子,假如給予一個標籤 slug,我們透過get_object_or_404()用給定的slug來獲取標籤物件。

3。之後我們過濾所有帖子只留下包含給定標籤的帖子。因為有一個多對多(many-to-many)的關係,我們必須透過給定的標籤列表來過濾,在我們的例子中標籤列表只包含一個元素。

要記住查詢集(QuerySets)是惰性的。這個查詢集(QuerySets)只有當我們在模板(template)中迴圈渲染帖子列表時才會被執行。

最後,修改檢視(view)最底部的

render()

函式來,傳遞

tag

變數給模板(template)。這個檢視(view)完成後如下所示:

def post_list(request, tag_slug=None):

object_list = Post。published。all()

tag = None

if tag_slug:

tag = get_object_or_404(Tag, slug=tag_slug)

object_list = object_list。filter(tags__in=[tag])

paginator = Paginator(object_list, 3) # 3 posts in each page

page = request。GET。get(’page‘)

try:

posts = paginator。page(page)

except PageNotAnInteger:

# If page is not an integer deliver the first page

posts = paginator。page(1)

except EmptyPage:

# If page is out of range deliver last page of results

posts = paginator。page(paginator。num_pages)

return render(request, ’blog/post/list。html‘, {’page‘: page,

’posts‘: posts,

’tag‘: tag})

開啟blog應用下的

url。py

檔案,註釋基於類的

PostListView

URL模式,然後取消

post_list

檢視(view)的註釋,如下所示:

url(r’^$‘, views。post_list, name=’post_list‘),

# url(r’^$‘, views。PostListView。as_view(), name=’post_list‘),

新增下面額外的URL pattern到透過標籤過濾過的帖子列表中:

url(r’^tag/(?P[-\w]+)/$‘,views。post_list,

name=’post_list_by_tag‘),

如你所見,兩個模式都指向了相同的檢視(view),但是我們可以給它們不同的命名。第一個模式會呼叫

post_list

檢視(view)並且不帶上任何可選引數。然而第二個模式會呼叫這個檢視(view)帶上

tag_slug

引數。

因為我們要使用

post_list

檢視(view),編輯

blog/post/list。html

模板(template),使用

posts

物件修改pagination,如下所示:

{% include “pagination。html” with page=posts %}

在{% for %}迴圈上方新增如下程式碼:

{% if tag %}

Posts tagged with “{{ tag。name }}”

{% endif %}

如果使用者正在訪問blog,他會看到所有帖子列表。如果他指定一個標籤來過濾所有的帖子,他就會看到以上的資訊。現在,修改標籤的顯示方式,如下所示:

Tags:

{% for tag in post。tags。all %}

{{ tag。name }}

{% if not forloop。last %}, {% endif %}

{% endfor %}

現在,我們迴圈一個帖子的所有標籤,透過某一標籤來顯示一個自定義的連結URL。我們透過{% url “blog:post_list_by_tag” tag。slug %},用URL的名稱以及標籤 slug作為引數來構建URL。我們使用逗號分隔這些標籤。

在瀏覽器中開啟

http://

127。0。0。1:8000/blog/

然後點選任意的標籤連結,你會看到透過該標籤過濾過的帖子列表,如下所示:

django-2-12

檢索類似的帖子

如今,我們已經可以給我們的blog帖子加上標籤,我們可以透過它們做更多有意思的事情。透過使用標籤,我們能夠很好的分類我們的blog帖子。擁有類似主題的帖子一般會有幾個共同的標籤。我們準備建立一個功能:透過帖子共享的標籤數量來顯示類似的帖子。這樣的話,當一個使用者閱讀一個帖子,我們可以建議他們去讀其他有關聯的帖子。

為了透過一個特定的帖子檢索到類似的帖子,我們需要做到以下幾點:

返回當前帖子的所有標籤。

返回所有帶有這些標籤的帖子。

在返回的帖子列表中排除當前的帖子,避免推薦相同的帖子。

透過和當前帖子共享的標籤數量來排序所有的返回結果。

假設有兩個或多個帖子擁有相同數量的標籤,推薦最近的帖子。

限制我們想要推薦的帖子數量。

這些步驟可以轉換成一個複雜的查詢集(QuerySet),該查詢集(QuerySet)我們需要包含在我們的

post_detail

檢視(view)中。開啟blog應用中的

view。py

檔案,在頂部新增如下匯入:

from django。db。models import Count

這是Django ORM的

Count

聚合函式。這個函式允許我們處理聚合計算。然後在

post_detail

檢視(view)的

render()

函式之前新增如下程式碼:

# List of similar posts

post_tags_ids = post。tags。values_list(’id‘, flat=True)

similar_posts = Post。published。filter(tags__in=post_tags_ids)\

。exclude(id=post。id)

similar_posts = similar_posts。annotate(same_tags=Count(’tags‘))\

。order_by(’-same_tags‘,’-publish‘)[:4]

以上程式碼的解釋如下:

1。我們取回了一個包含當前帖子所有標籤的ID的Python列表。

values_list()

查詢集(QuerySet)返回包含給定的欄位值的元祖。我們傳給元祖flat=True來獲取一個簡單的列表類似[1,2,3,。。。]。

2。我們獲取所有包含這些標籤的帖子排除了當前的帖子。

3。我們使用

Count

聚合函式來生成一個計算欄位

same_tags

,該欄位包含與查詢到的所有 標籤共享的標籤數量。

4。我們透過共享的標籤數量來排序(降序)結果並且透過

publish

欄位來挑選擁有相同共享標籤數量的帖子中的最近的一篇帖子。我們對返回的結果進行切片只保留最前面的4篇帖子。

render()

函式中給上下文字典增加

similar_posts

物件,如下所示:

return render(request,

’blog/post/detail。html‘,

{’post‘: post,

’comments‘: comments,

’comment_form‘: comment_form,

’similar_posts‘: similar_posts})

現在,編輯

blog/post/detail。html

模板(template)在帖子評論列表前新增如下程式碼:

Similar posts

{% for post in similar_posts %}

{{ post。title }}

{% empty %}

There are no similar posts yet。

{% endfor %}

推薦你在你的帖子詳情模板中新增標籤列表,就像我們在帖子列表模板所做的一樣。現在,你的帖子詳情頁面看上去如下所示:

django-2-13

你已經成功的為你的使用者推薦了類似的帖子。

django-taggit

還內建了一個similar_objects() 管理器(manager)使你可以透過共享的標籤返回所有物件。你可以透過訪問 The API - django-taggit 0。12 documentation 看到所有django-taggit管理器。

總結

在本章中,你學習瞭如何使用Django的表單和模型(model)表單。你建立了一個透過email分享你的站點內容的系統,還為你的部落格建立了一個評論系統。透過整合一個可複用的應用,你為你的帖子增加了打標籤的功能。同時,你還構建了一個複雜的查詢集(QuerySets)用來返回類似的物件。

在下一章中,你會學習到如何建立自定義的模板(temaplate)標籤(tags)和過濾器(filters)。你還會為你的部落格應用構建一個自定義的站點地圖,整合一個高階的搜尋引擎。

書籍出處:Django By Example

原作者:Antonio Melé

2016年12月13日釋出(3天完成第二章的翻譯,但沒有進行校對,有很多錯別字以及模糊不清的語句,請大家見諒)

2017年2月17日校對完成(不是精校,希望大家多指出需要修改的地方)

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

2017年3月21日再度精校(感謝大牛 @媽媽不在家 的精校!初版我已經不敢再看!)