使用高級(jí)特性來(lái)優(yōu)化你的博客
在上一章中,你創(chuàng)建了一個(gè)基礎(chǔ)的博客應(yīng)用。現(xiàn)在你將要改造它成為一個(gè)功能更加齊全的博客即供,利用一些高級(jí)的特性例如通過(guò)email來(lái)分享帖子,添加評(píng)論于微,給帖子打上tag逗嫡,檢索出相似的帖子。在本章中株依,你將會(huì)學(xué)習(xí)以下幾點(diǎn):
- 使用Django發(fā)送email
- 在views中創(chuàng)建并操作表單
- 通過(guò)models創(chuàng)建表單
- 構(gòu)建復(fù)雜的QuerySets
通過(guò)email分享帖子
首先驱证,我們會(huì)允許用戶(hù)通過(guò)發(fā)送郵件來(lái)分享他們的帖子。首先讓我們花費(fèi)一小會(huì)時(shí)間來(lái)想下你該如何使用views恋腕,urls和templates來(lái)創(chuàng)建這個(gè)功能根據(jù)你在上一章中學(xué)到的知識(shí)∧ǔ現(xiàn)在,核對(duì)一下你需要哪幾點(diǎn)才能允許你的用戶(hù)通過(guò)郵箱來(lái)發(fā)送帖子荠藤。你需要做到以下幾點(diǎn):
- 創(chuàng)建一個(gè)表單給用戶(hù)來(lái)填寫(xiě)他們的姓名伙单,email,收件方以及評(píng)論哈肖,評(píng)論不是必選的车份。
- 在views.py文件中創(chuàng)建一個(gè)view來(lái)操作發(fā)布的數(shù)據(jù)和發(fā)送email
- 在博客應(yīng)用的urls.py中為新的view添加一個(gè)URL pattern
- 創(chuàng)建一個(gè)模板來(lái)展示這個(gè)表單
使用Django創(chuàng)建表單
讓我們開(kāi)始創(chuàng)建一個(gè)用來(lái)分享帖子的表單。Django有一個(gè)內(nèi)置的表單框架允許你通過(guò)簡(jiǎn)單的方式來(lái)創(chuàng)建表單牡彻。這個(gè)表單框架允許你定義你的表單字段扫沼,指定他們必須展示的方式,以及指定他們?nèi)绾悟?yàn)證輸入的數(shù)據(jù)庄吼。Django表單框架還提供一個(gè)靈活的方式來(lái)渲染表單以及操作數(shù)據(jù)缎除。
Django應(yīng)用了兩個(gè)基礎(chǔ)類(lèi)來(lái)創(chuàng)建表單:
- Form: 允許你創(chuàng)建一個(gè)標(biāo)準(zhǔn)表單
- ModelForm: 允許你創(chuàng)建一個(gè)表單可用于創(chuàng)建或者更新model的實(shí)例
首先,創(chuàng)建一個(gè)forms.py文件在你博客應(yīng)用的目錄下总寻,輸入以下代碼:
from django import forms
class EmailPostForm(forms.Form):
name = forms.CharField(max_length=25)
to = forms.EmailField()
comments = forms.CharField(required=False, widget=forms.Textarea)
這是你的第一個(gè)Django表單器罐。看下代碼:我們已經(jīng)創(chuàng)建了一個(gè)繼承基礎(chǔ)Form類(lèi)的表單渐行。我們使用不同的字段類(lèi)型以使Django來(lái)驗(yàn)證字段轰坊。
表單可以存在你的Django項(xiàng)目的任何地方铸董,但按照慣例將它們放在每一個(gè)應(yīng)用下面的forms.py文件中
name字段是一個(gè)CharField
。這種類(lèi)型的字段等同于<input type=“text”>
HTML元素肴沫。每個(gè)字段都有默認(rèn)的控件來(lái)確定它在HTML中的展示粟害。通過(guò)改變控件的屬性可以重寫(xiě)默認(rèn)的控件。在comment字段中颤芬,我們使用Textarea控件來(lái)使它展示成一個(gè)<textarea></textarea>
HTML元素來(lái)代替默認(rèn)的<input>
元素悲幅。
字段的驗(yàn)證也依賴(lài)于字段的類(lèi)型。舉個(gè)例子站蝠,email和to字段是EmailField
,它們需要一個(gè)有效的地址汰具,否則字段驗(yàn)證不通過(guò)將會(huì)返回forms.ValidationError
異常導(dǎo)致表單提交失敗。其他的參數(shù)將進(jìn)入表單驗(yàn)證:我們指定name字段最多只能輸入25個(gè)字符菱魔,通過(guò)設(shè)置required=False
表明comments字段不是必填項(xiàng)留荔。目前我們?cè)诒韱沃惺褂玫倪@些字段類(lèi)型只是Django支持的表單字段的一部分。要查看更多可利用的表單字段澜倦,你可以訪問(wèn):https://docs.djangoproject.com/en/1.8/ref/forms/fields/
現(xiàn)在聚蝶,你需要學(xué)習(xí)如何通過(guò)Django來(lái)發(fā)送email。
使用Django發(fā)送email
通過(guò)Django發(fā)送email非常簡(jiǎn)單肥隆。首先既荚,你需要有一個(gè)本地的SMTP服務(wù)或者獲取到一個(gè)外部SMTP服務(wù)的配置稚失,接下來(lái)在你的項(xiàng)目中的setting.py文件中設(shè)置如下內(nèi)容:
EMAIL_HOST: SMTP服務(wù)地址栋艳。默認(rèn)本地。
EMAIL_POSR: SMATP服務(wù)端口句各,默認(rèn)25吸占。
EMAIL_HOST_USER: SMTP服務(wù)的用戶(hù)名。
EMAIL_HOST_PASSWORD: SMTP服務(wù)的密碼凿宾。
EMAIL_USE_TLS: 是否使用TLS加密協(xié)議矾屯。
EMAIL_USE_SSL: 是否使用SSL加密協(xié)議。
如果你沒(méi)有本地SMTP服務(wù)初厚,你可以使用你的email提供的SMTP服務(wù)件蚕。下面提供了一個(gè)簡(jiǎn)單的例子展示如何通過(guò)使用Google賬戶(hù)的Gmail服務(wù)來(lái)發(fā)送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
用django自帶的mail服務(wù)基本都是上面那樣設(shè)置,不過(guò)我的沒(méi)搞好产禾,一直報(bào)錯(cuò)排作,于是自己寫(xiě)了個(gè)腳本 send_mail.py
:
#coding:utf8
import smtplib
from email.mime.text import MIMEText
# 發(fā)送郵件函數(shù)
class SendMail():
def __init__(self):
# 設(shè)置服務(wù)器名稱(chēng)、用戶(hù)名亚情、密碼以及郵件后綴
self.mail_host = "smtp.126.com"
self.mail_user = "iphone3000@126.com"
self.mail_pass = "xxxxxxxxxxx"
self.mail_postfix = "126.com"
def send_mail(self, to_list, sub, context):
me = self.mail_user + "<"+self.mail_user+"@"+self.mail_postfix+">"
msg = MIMEText(context,_charset="utf-8")
msg['Subject'] = sub
msg['From'] = me
msg['To'] = "".join(to_list)
try:
send_smtp = smtplib.SMTP()
send_smtp.connect(self.mail_host)
send_smtp.login(self.mail_user, self.mail_pass)
send_smtp.sendmail(me, to_list, msg.as_string())
send_smtp.close()
return True
except Exception as e:
print(e)
return False
在views中操作表單
你必須創(chuàng)建一個(gè)新的view當(dāng)表單成功提交后進(jìn)行操作和發(fā)送email妄痪。編輯博客應(yīng)用下的views.py文件,添加以下代碼:
...
from .send_mail import SendMail
...
def post_share(request, post_id):
post = get_object_or_404(Post, id=post_id, status='published')
sendmail = SendMail()
sent = False
if request.method == 'POST':
form = EmailPostForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
post_url = request.build_absolute_uri(
post.get_absolute_url())
subject = '{} recommends you reading "{}"'.format(cd['name'], post.title)
message = 'Read "{}" at {}\n\n comments: {}'.format(post.title, post_url, cd['comments'])
sendmail.send_mail([cd['to']],subject,message)
sent = True
else:
form = EmailPostForm()
return render(request, 'blog/post/share.html', {'post': post,'form': form,'sent': sent})
該view完成了以下工作:
- 我們定義了post_share視圖楞件,參數(shù)為request對(duì)象和post_id衫生。
- 我們使用
get_object_or_404
方法通過(guò)ID獲取對(duì)應(yīng)的帖子裳瘪,并且確保取回的帖子一定是已發(fā)布的狀態(tài)。 - 我們使用同一個(gè)view來(lái)展示初始表單以及處理提交后的數(shù)據(jù)罪针。
- 我們會(huì)區(qū)別被提交的表單和不基于這次請(qǐng)求方法的表單彭羹。我們通過(guò)使用
POST
來(lái)提交表單。我們假設(shè)我們會(huì)遇到GET
請(qǐng)求站故,這時(shí)就需要展示一個(gè)空的表單皆怕,而我們遇到POST
請(qǐng)求,我們就需要處理表單提交上來(lái)的數(shù)據(jù)西篓。因此愈腾,我們使用request.method == 'POST'
來(lái)區(qū)分這兩種場(chǎng)景。
下面是展示和操作表單的進(jìn)程:
- 當(dāng)view加載后遇到一個(gè)GET請(qǐng)求岂津,我們會(huì)創(chuàng)建一個(gè)新的表單實(shí)例在模板中展示一個(gè)空的表單:
form = EmailPostForm()
- 當(dāng)用戶(hù)填寫(xiě)了表單并通過(guò)POST方式提交虱黄,我們會(huì) 創(chuàng)建一個(gè)表單實(shí)例來(lái)使用提交的數(shù)據(jù),這些數(shù)據(jù)被包含在request.POST中:
if request.method == 'POST':
# Form was submitted
form = EmailPostForm(request.POST)
- 在以上步驟之后吮成,我們通過(guò)表單的
is_valid()
方法來(lái)驗(yàn)證提交的數(shù)據(jù)橱乱。這個(gè)方法會(huì)驗(yàn)證表單引進(jìn)的數(shù)據(jù),如果所有的字段都是有效數(shù)據(jù)粱甫,將會(huì)返回True泳叠。一旦有任何一個(gè)字段包含無(wú)效的數(shù)據(jù),is_valid()將會(huì)返回False茶宵。你可以通過(guò)訪問(wèn)form.errors
查看所有驗(yàn)證錯(cuò)誤的列表危纫。
- 如果表單數(shù)據(jù)驗(yàn)證沒(méi)有通過(guò),我們會(huì)再次使用提交的數(shù)據(jù)在模板中渲染表單乌庶。我們會(huì)在模板中顯示驗(yàn)證錯(cuò)誤提示种蝶。
- 如果表單數(shù)據(jù)驗(yàn)證通過(guò),我們通過(guò)訪問(wèn)
form.cleaned_data
# 獲取驗(yàn)證過(guò)的數(shù)據(jù)瞒大。這個(gè)屬性是一個(gè)表單字段和值的字典螃征。
如果表單中的數(shù)據(jù)沒(méi)有通過(guò)驗(yàn)證,cleaned_data只會(huì)包含驗(yàn)證過(guò)的數(shù)據(jù)
請(qǐng)注意透敌,我們聲明了一個(gè)sent變量并且賦予它True當(dāng)帖子被成功發(fā)送盯滚。當(dāng)表單成功提交的時(shí)候,我們會(huì)使用這個(gè)變量用來(lái)在template中顯示一條成功提示酗电。因?yàn)槲覀冃枰趀mail中包含帖子的超鏈接魄藕,所以我們通過(guò)使用post.get_absolute_url()
方法來(lái)取到帖子的絕對(duì)路徑。我們將這個(gè)絕對(duì)路徑作為request.build_absolute_uri()
的輸入值來(lái)構(gòu)建一個(gè)完整的HTTP鏈接顾瞻。我們通過(guò)使用驗(yàn)證過(guò)的表單數(shù)據(jù)來(lái)構(gòu)建email的主題和消息內(nèi)容并最終發(fā)送email給表單中的to字段中包含的所有email地址泼疑。
現(xiàn)在你的view已經(jīng)完成了,別忘記為它去添加一個(gè)新的URL pattern。打開(kāi)你的博客應(yīng)用下的urls.py文件添加post_share的URL pattern如下所示:
urlpatterns = [
# ...
url(r'^(?P<post_id>\d+)/share/$', views.post_share, name='post_share'),
]
在templates中渲染表單
在通過(guò)創(chuàng)建表單退渗,編寫(xiě)view以及添加URL patter后移稳,我們就只剩下為這個(gè)view添加tempalte了。在blog/templates/blog/post/
目錄下創(chuàng)建一個(gè)新的文件并命名為share.html会油。在html文件中添加如下代碼:
{% extends "blog/base.html" %}
{% block title %}Share a post{% endblock %}
{% block content %}
{% if sent %}
<h1>E-mail successfully sent</h1>
<p>
"{{ post.title }}" was successfully sent to {{ cd.to }}.
</p>
{% else %}
<h1>Share "{{ post.title }}" by e-mail</h1>
<form action="." method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="Send e-mail">
</form>
{% endif %}
{% endblock %}
這個(gè)tempalte專(zhuān)門(mén)用來(lái)顯示一個(gè)表單或email成功發(fā)送后展示一條成功提示信息个粱。就像你所看見(jiàn)的,我們創(chuàng)建的HTML表單元素里面聲明了提交方式采用的是POST方法翻翩;
{% csrf_token %}
tempalte標(biāo)簽引進(jìn)了一個(gè)隱藏的字段包含一個(gè)自動(dòng)生成的標(biāo)記來(lái)避開(kāi)Cross-Site request forgery(CSRF)攻擊都许。這些攻擊由一個(gè)惡意的站點(diǎn)或程序組成并執(zhí)行一個(gè)不需要的操作給你站點(diǎn)中的用戶(hù)。你可以找到更多的信息通過(guò)訪問(wèn)https://en.wikipedia.org/wiki/Cross-site_request_forgery 嫂冻。上述的標(biāo)簽生成的隱藏字段就像下面一樣:
input type='hidden' name='csrfmiddlewaretoken' value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR' />
默認(rèn)情況下胶征,Django在所有的POST請(qǐng)求中都會(huì)檢查CSRF標(biāo)簽。請(qǐng)記住要在所有使用POST方法提交的表單中包含csrf_token標(biāo)簽桨仿。(譯者注:當(dāng)然你也可以關(guān)閉這個(gè)檢查睛低,注釋掉app_list中的csrf應(yīng)用即可)
現(xiàn)在訪問(wèn)試試:
點(diǎn)擊一篇帖子,點(diǎn)擊Share this post你會(huì)看到一個(gè)包含通過(guò)email分享這個(gè)帖子的表單服傍∏祝看上去如下所示:
為這個(gè)表單提供的CSS樣式被包含在示例代碼中的 static/css/blog.css文件中。當(dāng)你點(diǎn)擊Send e-mail按鈕吹零,這個(gè)表單會(huì)提交并驗(yàn)證罩抗。如果所有的字段都通過(guò)了驗(yàn)證,你會(huì)得到一條成功信息如下所示:
如果你輸入了錯(cuò)誤的數(shù)據(jù)灿椅,你會(huì)看到表單被再次渲染套蒂,并展示出驗(yàn)證錯(cuò)誤信息;
檢查郵箱會(huì)看到剛剛發(fā)送的郵件