譯者夜夜月,已獲作者授權轉載。
原文連結:
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元素而不是使用預設的元素來顯示它。
欄位驗證取決於欄位型別。例如,
和
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
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
{% endif %}
{% endblock %}
這個模板(tempalte)專門用來顯示一個表單或一條成功提示資訊。如你所見,我們建立的HTML表單元素裡面表明了它必須透過POST方法提交:
{% 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程式碼:
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
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 %}
{% 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日再度精校(感謝大牛 @媽媽不在家 的精校!初版我已經不敢再看!)