在上一個章節(jié),我們已經(jīng)創(chuàng)建了一個基礎(chǔ)的Blog程序。現(xiàn)在我們將使用一些Dajngo高級功能,去實現(xiàn)一個完整的blog網(wǎng)站,比如通過email進行分享,允許用戶進行評論,可以添加標簽和對帖子進行搜索檢索,在這一章,我們將學(xué)到如下的內(nèi)容:
? 1、使用Django去發(fā)送一封郵件
?2、創(chuàng)建表單并在視圖中處理他們
3、從models中創(chuàng)建表單
4、整合集成一些第三方的應(yīng)用
5、創(chuàng)建復(fù)雜的查詢
第一:使用Djngo去發(fā)送一封郵件
首先,我們將實現(xiàn)讓用戶通過郵件去分享自己的帖子的功能,在未看指南之前,先自己花點時間想想,你將如何使用上一章節(jié)我們學(xué)過的URLS\VIEWS\TEMPLATE等知識,來自己設(shè)計實現(xiàn)一個發(fā)送郵件的功能?現(xiàn)在檢查你需要什么去允許你的用戶發(fā)送通過郵件發(fā)送帖子。你將要:
1、創(chuàng)建一個表單,讓用戶可以去輸入 名字、email、接收人和可選的評論
2、在views.py里面創(chuàng)建一個視圖,處理帖子的內(nèi)容并發(fā)出郵件
3、在blog程序的url 地址映射表中,將新增加的視圖,添加到地址映射表
4、創(chuàng)建一個展示表單的網(wǎng)頁模板(Template)
在Django中創(chuàng)建/使用 表單
讓我們從創(chuàng)建分享帖子的表單開始,Django有一個內(nèi)置的表單框架,讓你可以用一個簡單的方式去創(chuàng)建一個表單。這個表單的框架允許你去定義你表單的字段,標記他們是否必須顯示,驗證輸入的數(shù)據(jù)是否準確。Django的表單架構(gòu)還是提供處理數(shù)據(jù)和渲染表單的一種靈活的方式。
Django有兩個基礎(chǔ)類去創(chuàng)建表單:
Form ?允許你去創(chuàng)建標準的表單
ModelForm ?允許你創(chuàng)建表單來創(chuàng)建和更新model實例
首先,我們在blog程序的目錄下面,創(chuàng)建一個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 = froms.Textarea)
這是你的第一個Django表單,讓我們再看看code:我們通過基礎(chǔ)的Form類,來創(chuàng)建一個表單,我們使用了Django的不同類型的字段,并根據(jù)每個字段類型的不同去驗證數(shù)據(jù)的有效性。
注意: 表單可以寫在Django項目的任何地方,如寫到views里面或者寫在文件里面或者寫在models里面,但一般來說,大家約定是在每個應(yīng)用程序中創(chuàng)建一個forms.py,將表單寫在這個文件中。
在上面的表單中,name字段是一個CharField類型,這個類型的字段在網(wǎng)頁上呈現(xiàn)的是一個<input type='text'>的 Html ?元素。每一個字段類似都有一個默認的網(wǎng)頁元素widget與之對應(yīng)。字段的默認網(wǎng)頁元素widget屬性可以被重寫,后面將會介紹到如何重寫一個表單字段的默認widget屬性。在comments字段中,我們使用widget=forms.Textarea來使用html的Textarea 元素替代默認的字段顯示為 <input>元素。意思說,如果我們不在字段屬性后面使用 widget=froms.Textarea,那么 comments字段的默認widget是input。
字段校驗同樣依賴于字段類型。例如,email和to這兩個字段的類型是EmailField。這些字段就需要用戶輸入一個正確的e-mail地址,否則校驗的時候會拋出一個forms.ValidationError異常,并且表單不會通過驗證。其他的參數(shù)同樣需要考慮到表單的驗證:我們定義了一個最大長度為25個字符的name字段,我們讓comments字段含有一個required=False參數(shù)來指明comments是一個可選的,不用必須輸入。所有這些都需要考慮到字段的校驗。上面表單的字段操作知識只是Django 表單字段的一部分,對于所有表單的可用字段列表說明,你可以訪問:https://docs.djangoproject.com/en/1.8/ref/forms/fields/.
在視圖中處理表單
當(dāng)表單成功提交后,為了能夠處理表單的數(shù)據(jù)并發(fā)送一個e-mail你需要去創(chuàng)建一個新的view。編輯你的blog程序下的views.py文件,將下面的代碼添加進去:
from .forms import EmailPostForm
def post_share(request,post_id):
?#Retrive post by id ,通過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})
這個視圖的工作過程如下:
我們定義了一個post_share視圖,使用request對象和post_id做為參數(shù)
我們使用get_object_or_404()快捷操作,通過id檢索post并確實檢索到的post是published狀態(tài)
我們?nèi)匀挥眠@個view去顯示初始化的表單并處理提交的數(shù)據(jù).我們對表單提交的方法進行了區(qū)分。我們用POST方法去提交表單數(shù)據(jù),用get方法去獲取表單數(shù)據(jù)。同時我們需要判斷以下,如果我們得到的是一個GET請求,那么我們就返回一個空的表格去進行顯示,如果我們得到的是一個POST請求,那么我們會將表單提交并進行處理。因此,我們使用request.method =='POST'在這兩個場景間去進行區(qū)分。
下面是去處理顯示和處理表單:
1、當(dāng)這個視圖通過GET請求進行初始化請求時,我們創(chuàng)建一個新的表單實體,并將被用來在模板中顯示一個空表單
form =EmailPostForm()
2、用戶填充進入表單并通過POST渠道提交,這個時候,我們創(chuàng)建一個表單實體使用提交數(shù)據(jù)包含在request.POST:
if request.method =="POST":
#Form was submitted
form = EmailPostForm(request.POST)
3、做完這些,我們通過使用表單的is_valid()方法去進行數(shù)據(jù)的校驗。這個方法在表單中校驗數(shù)據(jù)的信息并且返回True加入所有的字段都檢驗數(shù)據(jù)正確。加入有任意的一個字段校驗是錯誤數(shù)據(jù) ,這個時候 is_valid() 將返回False.你可以通過訪問form.errors看到一個校驗錯誤信息的列表.
4、假如表格有非法的信息,我們使用提交的數(shù)據(jù)在模板里面渲染表單。我們將在模板里面顯示錯誤信息。
5、假如表單沒有非法的信息,我們通過訪問form.cleaned_date來檢索驗證后的數(shù)據(jù)。表單的字段和表單值將以字段的形式組織起來。
注意:假如你的表單數(shù)據(jù)沒有校驗 cleaned_date將只包含有效的字段。
現(xiàn)在,你需要去學(xué)習(xí),怎么使用Django發(fā)送一個email,去把他們整合在一起。
通過Django發(fā)送e-mails
通過django發(fā)送e-mails是很簡單。首先,你需要有個本地的SMTP服務(wù)器或者通過在項目的settings.py里面添加下面的步驟定義一個外部SMTP服務(wù)器。
EMAIL_HOST:SMTP服務(wù)器的host。默認是本地的
EMAIL_POST:SMTP服務(wù)器的端口,默認是25
EMAIL_HOST_USER:SMTP服務(wù)器的用戶名
EMAIL_HOST_PASSWORD:SMTP服務(wù)器的密碼
EMAIL_USE_TLS:是否去使用TLS安全連接
EMAIL_USE_SSL:是否去使用隱藏TLS安全連接
假如你沒有本地服務(wù)器,你可以使用你的email提供商的SMTP服務(wù)器。下面是一個通過使用GMAIL服務(wù)器發(fā)送郵件的例子。
EMAL_HOST ='smtp.gmail.com'
EMAIL_HOST_USER='your_account@gmail'
EMAIL_HOST_PASSWORD='your password'
EMAIL_post=587
EMAIL_USE_TLS=True
在命令行運行python manage.py命令打開python的shell,像下面這樣發(fā)送一個e-mail試試:
>>>from django.core.mail import send_mail
>>>send_mail('Django mail','this e-mail was send with django.','your_account@gmail',['your_account@gmail.com'],fail_silently=False)
send_mail() 使用 主題、消息、發(fā)送者、收件人列表做為參數(shù).通過設(shè)置可選參數(shù)fail_silently=False,我們告訴服務(wù)器,當(dāng)e-mail不能被正確投送時,會拋出一個異常,如果你看到值為1,這個時候你的email被發(fā)送成功。如果你通過前面配置的gmail服務(wù)器,你需要去允許訪問低安全度在https://www.google.com/settings/security/lesssecureapps.
現(xiàn)在,我們開始把這些添加到我們的視圖中,在blog程序的views.py中編輯post_share視圖,讓他像下面這樣:
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
from = EmailPostForm(request.POST)
if form.is_valid():
cd = form.cleaned_date
post_url = request.build_absolute_url(post.get_absolute_url())
subject='{}({})recomends you reading "{}"'.format(cd['name'],cd['email'],post.title)
message='Read "{}" at {}\b\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})
注意,我們在發(fā)送post的時候聲明了一個sent變量并將其設(shè)置為True。當(dāng)表單成功提交后,我們將在模板中使用這個變量,去顯示一個成功的信息。因為我們必須在電子郵件中加入一個post連接,我們得用get_absolute_url()來檢索post的絕對路徑。
我們使用這個路徑做為request.build_absolute_uri()的輸入,去構(gòu)建一個完整的URL鏈接,在這個URL鏈接中包含HTTP模式和域名,我們通過使用已驗證過的、被清洗后的表單數(shù)據(jù)構(gòu)建郵件的標題和郵件正文,并最終發(fā)送郵件到表單里面e-mail字段的 地址中。
現(xiàn)在你的視圖已經(jīng)完成,記住需要為它添加一個新的url表達式。打開blog程序下的urls.py文件,添加post_share url表達式,完成后如下:
urlpatterns =[
#.....
url(r'^(?P<post_id>\d+)/share/$',views.post_share,name='post_share'),
]
在網(wǎng)頁模板中渲染表單
在創(chuàng)建了模板,編寫了view的代碼,并在urls 地址路由表中添加了url表達式,我們就只缺少構(gòu)建一個該視圖的網(wǎng)頁模板了。在blog/templates/blog/post/目錄下添加一個名字叫做share.html的新文件,并加入如下的代碼:
{% entends "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}}
{% crst_token %}
<input type="submit" value = "send e-mail">
</form>
{% endif %}
{% endblock %}
這個模板用來去顯示表單或者當(dāng)他被發(fā)送時顯示發(fā)送成功的信息。正如你所看到的,我們創(chuàng)建這個HTML表單元素去標明 ,表單必須通過POST方法去提交。
<form action ="." method = "post">
然后我們包含了這個實際的表單實例,我們告訴Django在HTML段落 <p>元素中,用表單的as_p方法去渲染表單的字段,我們還可以通過調(diào)用as_ul渲染表單為一個無序列表或者調(diào)用as_table渲染成為一個html 表。如果我們想渲染每一個字段,我們還可以通過遍歷字段。向下面的例子:
{% for field in form %}
<div>
{{field.errors}}
{{field.label_tag}}{{field}}
</div>
{% endfor %}
{% csrf_token %}模板標簽 引入一個隱藏字段,帶有一個自動生成的token,去規(guī)避 CROSS-Site Request Forgery(CSRF)攻擊。這些攻擊由一些惡意的網(wǎng)站或者程序組成,去執(zhí)行一些你的網(wǎng)站用戶不想發(fā)生的有害的行為。
在https://en.wikipedia.org/wiki/Cross-site_request_forgery你可以發(fā)現(xiàn)更多的關(guān)于CSRF信息。
前面說的這個標簽生成一個隱藏的字段,像下面這里:
<input type='hidden' name ='csrfmiddlewatetoken' value ='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR'/>
注意:默認的,Django在所有的POST請求中檢查CSRF token。記住在所有的表單中包含csrf_token標簽并通過POST渠道去提交。
編輯你的 blog/post/detail.html模板,添加下述鏈接到分享帖子的url中,在{{post.body|linebreaks}}變量后。
<p>
<a href ="{% url "blog:post_share" post.id %}">share this post</a>
<p>
記住,我們正通過Django提供的 {% url %}模板標簽去動態(tài)構(gòu)建url鏈接。我們通過名字回調(diào)blog并把url命名為 post_share,我們通過傳遞post id參數(shù)去創(chuàng)建絕對路徑。
現(xiàn)在,使用python manage.py runserver命令啟動開發(fā)服務(wù)器,在瀏覽器打開http://127.0.0.1:8000/blog/,點擊任意一個帖子標題進入詳情頁。在正文的下面,你將看到我們剛剛添加的鏈接,如下圖所示:
XXXXX圖片。。
點擊 Share this post ,你將看到一個包含通過email去分享這個post表單的頁面,如下圖所示:
表單的CSS樣式表包含在 例子代碼的 static/css/blog.css文件中,當(dāng)你點擊Send e-mail按鈕,這個表單將提交并進行驗證,如果所有的字段都是有效的數(shù)據(jù),你將得到一個如下圖所示的發(fā)送成功的消息。
如果你輸入了錯誤的數(shù)據(jù),你將看到,表單被在再次渲染,并包含所有的校驗錯誤信息。
創(chuàng)建一個評論系統(tǒng)
現(xiàn)在,我們開始為這個blog程序開發(fā)一個評論系統(tǒng),在里面用戶可以對某個帖子發(fā)表評論。去創(chuàng)建這樣一個評論系統(tǒng),你將要做:
-創(chuàng)建保存評論的數(shù)據(jù)模型;
-創(chuàng)建一個表單去提交評論并驗證輸入的數(shù)據(jù)正確性;
-添加一個處理表單并保存評論記錄到數(shù)據(jù)庫中的視圖;
-編輯帖子的detail模板,增加評論列表在詳情頁的顯示,并含有讓用戶進行評論的表單單元;
首先,讓我們創(chuàng)建一個保存評論的數(shù)據(jù)庫模型。打開你的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數(shù)據(jù)模型.它包含一個外鍵,用來讓評論關(guān)聯(lián)到唯一一個帖子上。這是在Commnet數(shù)據(jù)模型中定義的一個多對一的數(shù)據(jù)關(guān)系,因為每個評論都是在帖子上,并且每個帖子都可能有多個評論。post的 related_name屬性,允許我們通過定義這個屬性來讓關(guān)聯(lián)的對象反向引用。在定義這個后,我們就可以通過使用comment.post獲取到當(dāng)前帖子的一條評論對象或者通過post.comments.all()獲取到當(dāng)前post的所有評論。如果你不定義related_name這個屬性,Django將使用 undercase name of 數(shù)據(jù)模型后面跟隨的_set連接器(在本例中是 comment_set)去命名關(guān)聯(lián)對象反向引用的管理器。
[翻譯不好,引用解釋:如果模型有一個ForeignKey,那么該ForeignKey 所指的模型實例可以通過一個管理器返回前一個模型的所有實例。默認情況下,這個管理器的名字為foo_set,其中foo是源模型的小寫名稱。該管理器返回的查詢集可以用上一節(jié)提到的方式進行過濾和操作。]
你可以在https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/學(xué)習(xí)到更多關(guān)于多對一關(guān)系的資料。中文版:http://python.usyiyi.cn/documents/django_182/topics/db/queries.html#backwards-related-objects
http://python.usyiyi.cn/translate/django_182/topics/db/queries.html#following-relationships-backward
[這個地方介紹的比較好
我們包含一個active的布爾類型字段,這個可以用來對comments進行手動的審核。我們用created字段對評論進行默認時間順序排序。
你剛才新創(chuàng)建的Comment模型還沒有同步到數(shù)據(jù)庫中。運行下面的命令,去生成一個新的遷移前準備。
python manage.py makemigrations blog
你將看到下面的輸出:
Migrations for 'blog':
0002_comment.py:
-create model comment
Django在blog程序的 migrations目錄下生成了一個0002_comment.py的文件,現(xiàn)在你需要去創(chuàng)建關(guān)聯(lián)數(shù)據(jù)庫模式并把更新應(yīng)用到數(shù)據(jù)庫上。運行下面的命令去應(yīng)用已存在的遷移:
python manage.py migrate
你在下面會看到如下的輸出信息:
Applying blog.0002_comment... OK
我們剛才創(chuàng)建的遷移就這樣被應(yīng)用了,在數(shù)據(jù)庫你就能看到一個新的blgo_comment的數(shù)據(jù)表存在。
現(xiàn)在我們可以把我們的新數(shù)據(jù)模型添加到管理后臺頁面中,通過簡單的交互界面去管理評論。打開blog程序里的admin.py文件,添加對Comment的調(diào)用和下面的ModelAdmin:
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')#在管理后臺,可以通過name、email、body進行搜索
admin.site.register(Comment,CommentAdmin)
用python manage.py runserver啟動開發(fā)環(huán)境的服務(wù)器,并用瀏覽器打開 http://127.0.0.1:8000/admin/,你將看到在blog的段落中,已經(jīng)包含了這個新的comment,如下面截圖所示:
XXXXXX
我們的數(shù)據(jù)模型現(xiàn)在被注冊進入到管理頁面,這讓我們可以通過簡單的交互界面去管理Comment實例。
從數(shù)據(jù)模型創(chuàng)建表單
我們還需要去創(chuàng)建一個表單讓我們用戶能在blog帖子里面進行評論。還記得之前我們說過,Django支持兩種基礎(chǔ)類去創(chuàng)建表單:From和ModelForm。在前面的例子中,我們已經(jīng)使用了第一種類去創(chuàng)建表單,讓用戶可以通過e-mail去分享帖子。在當(dāng)前的例子中,你將使用ModelForm類去創(chuàng)建表單,因為你必須從你的Comment數(shù)據(jù)模型中創(chuàng)建一個動態(tài)表態(tài)。編輯blog程序中的forms.py并添加下面的代碼:
from .models import Comment
class CommentForm(forms.ModelForm):
? class Meta:
model =Comment
fields =('name','email','body')
從數(shù)據(jù)模型中創(chuàng)建一個表單,我們只需要在Meta元組中去指明哪個模型??用來創(chuàng)建表單。Django 會自己反查數(shù)據(jù)模型并為我們創(chuàng)建一個動態(tài)的表單。每個表單的字段都有一個相應(yīng)的默認表單字段類型。我們定義我們模型字段的方法是考慮到表單驗證的。默認的,對于每個Django的form字段都在數(shù)據(jù)模型中包含了。然而,你可以通過使用fields_list方法來明確的告訴框架,那個字段是你想在form中使用的,或者使用exclude一個字段列表來定義表單會包含那些你想要包含的字段。在這個CommentForm中,我們只在表單中用了name,email,body字段,因為這些是我們的用戶可以去填寫的。
在views里面處理ModelForm
為了讓表單比較簡單,我們將使用帖子已有的detail視圖去實例化并處理表單。編輯models.py文件,添加Comment數(shù)據(jù)模型和CommentFrom表單的引用,并編輯post_detail視圖,像下面這樣:
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,publist__month=month,publish__day=day)
#List of active comments for this post
comments = post.comments.filter(active=True)
if request.method = 'POST':
#A comment was posted
comment_form = CommentForm(data = request.POST)
if comment_form.is_valid():
#create comment object bug 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 =CommentFrom()
return render(request,'blog/post/detail.html',{'post':post,'comments':comments,'comment_form':comment_form})
我們回顧一下我們剛才添加到視圖中的信息。我們使用post_detail視圖去顯示帖子和他的評論,我們添加了一個QuerySet去檢索當(dāng)前帖子的所有活躍的評論
comments=post.comments.filter(active=True)
我們從post對象創(chuàng)建這個QuerySet。我們用之前在comment數(shù)據(jù)模型里面定義反向數(shù)據(jù)查詢管理器related_name=comments來通過post關(guān)系反向查詢comments。
在這個視圖中我們還需要去實現(xiàn)用戶提交新的評論功能。因此如果調(diào)用視圖的是一個GET請求,我們使用comment_form=CommentFrom()創(chuàng)建一個form實例,如果調(diào)用視圖的請求是一個POST請求,我們使用用戶提交的數(shù)據(jù)實例化表單,并用is_valid()方法來校驗數(shù)據(jù)有效性。如果表單是無效的,我們用校驗錯誤信息渲染模板,如果表單數(shù)據(jù)有效的,我們采取后續(xù)行動:
1、我們調(diào)用form.save()方法創(chuàng)建一個新的Comment對象,像下面這樣:
new_comment = comment_form.save(commit=False)
save()方法創(chuàng)建一個當(dāng)前表單所連接的模型的實例,并保存他到數(shù)據(jù)庫,假如你用commit=False調(diào)用他,你將創(chuàng)建模型的實例,但并沒有將它保存到數(shù)據(jù)庫。當(dāng)你在接下來的下一步,想在最終保存數(shù)據(jù)前,對數(shù)據(jù)進行一些修改,這是很方便的。save()方法對ModelForm是可用的,但對Form實例是不可用的,因為Form實例沒有關(guān)聯(lián)到任何的模型上。
2.我們把當(dāng)前的帖子指定到剛創(chuàng)建的comment上。
new_comment.post=post
通過這樣,我們清晰的描述了,新評論屬于給定的帖子。
3.最終,我們用下面的代碼將新評論保存到數(shù)據(jù)庫中。
new_comment.save()
現(xiàn)在我們的view已經(jīng)可以去處理和顯示新的評論了。
添加評論到帖子的詳情模板中
我們已經(jīng)創(chuàng)建帖子的評論管理功能。現(xiàn)在我們需要去讓我們的post_detail.html模板去適配做這些:
>顯示當(dāng)天帖子的評論總數(shù)
>顯示評論列表
>顯示用戶添加新評論的表單,讓用戶可以再帖子添加新評論
首先,我們將添加評論總數(shù)。打開blog_detail.html模板,并在content模塊里添加下面的代碼:
{% with comments.counts as total_comments %}
<h2>
{{total_comments}} ?comments {{total_comments|pluralize}}
</h2>
{% endwith %}
我們在模板中使用Django ORM,執(zhí)行查詢comments.count().記住django模板語言不用使用圓括號去調(diào)用方法。如上面我們使用的comments.counts.{% with %}標簽允許我們?nèi)ソo一個新的變量賦值,這個變量可以使用到 {% endwith %}標簽處。
注意:{% with %}模板標簽是一個需要規(guī)避多次訪問數(shù)據(jù)庫數(shù)據(jù)或使用比較耗時的方法時的有用方法。
我們使用pluralize模板過濾器,根據(jù)total_comments值去顯示comment的復(fù)數(shù)后綴.模板過濾器 用已被使用的變量的值做為輸入并返回一個計算過的值。在第三章《擴展Blog程序》我們將討論模板過濾器。
如果值是和1不同的,pluralize模板過濾器將顯示一個"s"。前面的文字將被渲染成 0 comments,1 comment, 或者N comments。Django包含豐富的模板標簽和過濾器去幫助你顯示信息,以你想要的方式。
現(xiàn)在,我們再來包含評論列表。在模板中,在之前代碼的后面添加下面行:
{% for comment in comments %}
<div class="comment">
<p class="info">
Comment {{ forloop.counter }} by{{ comment.name }}
{{comment.created}}
</p>
{comment.body|linebreaks}}
</div>
{% empty %}
<p>There are no comments yet.</p>
{% endfor %}
我們使用 {% for %}模板標簽去循環(huán)所有的評論。當(dāng)comments列表是空的時候,我們顯示一個默認的消息,告訴用戶當(dāng)前帖子還沒有評論。我們使用 {{forloop.counter}}去遍歷評論值,在每個迭代中都包含循環(huán)的計數(shù)。然后我們顯示提交評論的用戶名、日期、和評論的正文。
最終,當(dāng)評論提交成功后你需要去渲染表單或者顯示一條成功的信息。在上面的代碼下面添加下面的行:
{% if new_comment %}
<h2>Your comment has been added.</h2>
{% else %}
<h2>Add a new comment</h2>
<form action="." method ="post">
{{comment_form.as_p}}
{{% csrf_token %}}
<p><input type="submit" value="Add comment"></p>
</form>
{% endif %}
這個代碼是相當(dāng)簡單的:假如new_comment對象存在,我們顯示一個成功的信息,因為評論已被成功的創(chuàng)建了。否則,我們?yōu)槊總€字段使用一個段落元素去渲染表單,并且對于POST請求我們需要添加一個CSRF token的驗證碼。在瀏覽器打開http://127.0.0.1:8000/blog/,點擊帖子的標題查看詳情頁,如下圖所示:
XXXX
用表單添加幾個評論,他們應(yīng)該按照時間順序顯示在你的帖子后面。像下圖所示
XXXXXXX
在瀏覽器打開http://127.0.0.1:8000/admin/blog/comment/,在管理頁面,你將看到你創(chuàng)建的評論列表,選一個點擊并編輯,不選Active復(fù)選框,并點擊保存按鈕。我們將重新得到一個評論列表,并且在Active列上面,當(dāng)前評論將顯示一個不活躍的圖標。像下面的截圖中,第一個評論已經(jīng)被標記成inactive.
XXXX
這時候,假如你返回到帖子詳情頁,你將注意到,剛才被刪除的評論在任何地方都沒有再顯示。無論是創(chuàng)建的評論總數(shù)還是評論詳情。幸虧有active字段,你可以在你的帖子中設(shè)置一些評論無效并避免顯示他們。
添加標簽功能
在實現(xiàn)了我們的評論系統(tǒng)后,我們將開始去創(chuàng)建一個標記我們文章的方法,我們計劃在我們項目中整和一個第三方的Django 標簽程序。django-taggit 能夠給你提供一個TAG模型,并且是一個可重用的程序,讓你可以輕松地在任意一個模型中添加、管理標簽。你可以在https://github.com/alex/django-taggit里面查看他的源代碼。
首先,你需要通過pip來安裝django-taggit,運行下面命令安裝:
pip install django-taggit == 0.17.1
然后打開mysite項目的settings.py文件,添加taggit到你的installed_apps里面,添加后如下圖:
INSTALLED_APPS = (
#...
'blog',
'taggit',
)
打開blog程序的models.py文件,使用如下所示的命令,添加django-taggit提供的TabgableManager管理器到Post模型中
from taggit.managers import TaggableManager
class Post(models.Model):
#...
tags = TaggableManager()
tags管理器允許你從post對象中去添加、檢索、刪除標簽。運行下面的命令給模型的改變創(chuàng)建一個遷移事件。
python manage.py makemigrations blog
我們將得到如下的輸出:
Migrations for 'blog':
0003_post_tags.py
-Add field tags to post
現(xiàn)在,運行下面的命令,為django-taggit模型創(chuàng)建需要的數(shù)據(jù)庫表,并同步模型的變更到數(shù)據(jù)庫中。
python manage.py migrate
你將看到一個輸出指示當(dāng)前的遷移已經(jīng)被應(yīng)用了,如下圖所示:
Applying taggit.0001_initial.....ok
Applying taggit.0002_auto_20150616_2121.....ok
Applying blog.0003_post_tags....ok
現(xiàn)在數(shù)據(jù)庫已經(jīng)可以去試用django-taggit模型,試用python manage.py shell命令打開客戶端,我們來學(xué)學(xué)如何使用tags管理器。首先,我們檢索出來一個帖子,(用帖子ID)
>>>from blog.models import Post
>>> post = Post.objects.get(id=1)
然后我們在post對象里面添加一些標簽并將這些標簽再檢索出來,驗證這些標簽被成功添加了:
>>post.tags.add('music','jazz','django')
>>>post.tags.all()
[<tag:jazz>,<tag:django>,[tag:musci]]
最終,刪除一個標簽并再檢查標簽列表:
>>>post.tags.remove('django')
>>>post.tags.all()
[<Tag:jazz>,<Tag:music>]
很簡單,對不對?運行python manage.py runserver去啟動開發(fā)服務(wù)器,并在瀏覽器打開 http://127.0.0.1:8000/admin/taggit/tag/,你講看到在管理頁面有一個taggit應(yīng)用程序,里面有標簽對象的列表。
跳轉(zhuǎn)到http://127.0.0.1:8000/admin/blog/post/,點擊一條帖子并編輯他,你可以看到在帖子里面已經(jīng)包含了一個新的Tags的字段,像下圖所示,你可以很輕松的去編輯標簽信息。
XXXXXXXX
現(xiàn)在我們打算去編輯我們的blog帖子,并顯示這些標簽,打開blog/post/list.html模板,在html的帖子標題下面添加下面代碼:
<p class = "tags">TAGS :{{post.tags.all|join:","}}</p>
上面的 ?join模板過濾器的工作機制和python里面的 字符 join()方法相同,用來連接給定的字符元素。打開http://127.0.0.1:8000/bolg/,在每個帖子標題下面你就能看到一條標簽列表了,如下圖:
XXXXXXXX
現(xiàn)在,我們開始去編輯我們的post_list視圖,讓用戶可以通過一個給定的標簽去列出符合這個標簽的所有帖子。打開你的blog程序中的views.py文件,從django-taggit中加載Tag模板,像下面這樣把post_list視圖換成posts的tag選擇過濾器。
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])
#...
視圖的工作流程如下:
1、在視圖我們?nèi)×艘粋€可選的tag_slug參數(shù),并賦值為None,這個參數(shù)將通過URL取得。
2.在view里面,我們創(chuàng)建最初的檢索,檢索出所有的已發(fā)表的帖子,假如當(dāng)前帖子有 tag_slug,我們通過已知的slug使用 get_object_or_404()快捷功能區(qū)獲取Tag對象。
3.然后我們根據(jù)帖子是否包含已給的tag來過濾出來帖子列表。因為這是一個多對多的關(guān)系,我們必須以 標簽包含在給定的標簽列表來過濾帖子。在我們的例子里面,只有一個元素符合。
請記住,Querysets是惰性的【像Entry.Objects.all(),這些操作返回的是一個QuerySet對象,這個對象比較特別,并不是執(zhí)行Objects.all(),或者filter之后就會與數(shù)據(jù)庫交互,而是執(zhí)行比如打印具體entry某個參數(shù),才會去進行數(shù)據(jù)庫查詢,這個就是django querysets惰性的解釋】,在渲染模板的時候當(dāng)我們循環(huán)完帖子列表Querysets才會去執(zhí)行檢索帖子的操作。
最后,修改view底部的render()功能,將tag變量值傳給模板,最終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 = Paginarot(object_list,3) #每頁顯示3條帖子
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程序的urls.py文件,把PostListView的URL參數(shù)注釋掉,并把 post_list注釋去掉,如下所示:
url(r'^$' ,views.post_list,name='post_list'),
#url(r'^$',views.PostListView.as_view(),name='post_list'),
添加下面的新urlm模式去支持 通過標簽篩選帖子列表:
url(r'^tag/(?P<tag_slug>[-\w]+)/%$',views.post_list,name='post_list_by_tag'),
像看到那樣,兩個url的模式都是指向同一個view,但是我們把它命名不同的名字,第一個模式命名為post_list,沒有任何的參數(shù)項;第二個url模式,需要使用一個tag_slug參數(shù)來調(diào)用視圖。
以后我們用post_list視圖,我們?nèi)ゾ庉媌log/post/list.html模板,并編輯分頁使用posts對象:
{% include "pagination.html" with page=posts %}
在{% for %}循環(huán)上添加下面的行:
{% if tag %}
<h2>Posts tagged with "{{tag.name}}"</h2>
{% endif %}
加入用戶訪問泊客,他將看到帖子列,如果他通過一個給定的標簽去過濾帖子,他將看到這些信息。更改標簽的顯示方式:
<p class = "tags">
?Tags:
{% for tag in post.tags.all %}
? <a href = "{ % url "blog:post_list_by_tag" tag.slug %}">{{tag.name}}</a>
?{% if not forloop.last %},{% enfif %}
{% endfor %}
</p>
現(xiàn)在,我們循環(huán)通過帖子顯示的所有標簽,并通過tag過濾帖子。我們用 {%url "blog:post_list_by_tag" tag.slug%}動態(tài)創(chuàng)建url,并用url名字和標簽slug作為參數(shù),我們通過逗號分開這些標簽。
打開 http;//127.0.0.1:8000/blog/,點擊標簽連接,你可以看到通過標簽過濾出來的帖子列表,如下圖:
XXXXXXX
關(guān)聯(lián)檢索
現(xiàn)在,我們已經(jīng)為我們的博客貼上了標簽,做完這個,我們就可以圍繞標簽做很多有意思的事情。
通過標簽,我們可以把我們的博客進行很好的分類。類似主題的文章通常會有好幾個標簽,我們計劃去創(chuàng)建一個功能,顯示被分享的文章中,相關(guān)標簽的數(shù)量。這樣,當(dāng)一個用戶讀一個文章時,我們就可以建議他再讀其他的相關(guān)的文章。
為實現(xiàn)通過具體文章來進行關(guān)聯(lián)檢索,我們需要:
檢索當(dāng)前文章的所有標簽
獲取所有使用當(dāng)前文章標簽中任意一個標簽標記上的文章
通過分享文章中標簽的數(shù)量,對結(jié)果進行排序;
萬一有兩個或者多個文件,都有相同數(shù)量的標簽,那么推薦最近發(fā)布的。
將查詢限制為要推薦的文章數(shù)量
上面這些步驟被一個復(fù)雜的查詢數(shù)據(jù)集實現(xiàn),計劃在我們的post_view視圖包含這個查詢。打開blog程序的views.py文件,在文件頭部添加下面的import信息:
from django.db.models import Count
這是Django ORM 的Count聚合函數(shù),這個函數(shù)支持我們?nèi)?zhí)行記數(shù)匯總.在post_detail視圖的render()函數(shù)中前,添加下面的行:
#list of similar posts
post_tags_ids = post.tags.values_list('id',flat=True)
similar_posts = Post.published.filter(tags__inpost_tags_ids).exclude(id=post.id)
similar_posts=similar_posts.annotate(same_tags=Count('tags')).order_by('-same_tags','-publish')[:4]
我們來看下上述代碼的運行過程:
1.我們檢索當(dāng)前文章標簽ID的列表.values_list 查詢組將返回一個值的元組列表,values_list()查詢組將返回定值的字段的值的元組數(shù)據(jù)。我們通過傳遞flat=True去獲取一個平坦單調(diào)的列表,[1,2,3,....].
2.我們獲取所有包含任意一個上文標簽的文章并將當(dāng)前文章自己從列表中去掉;
3.我們使用Count聚合函數(shù)去生成一個統(tǒng)計字段same_tags,包含所有需要統(tǒng)計的標簽的總數(shù)
4.我們通過共享標簽的數(shù)量,對結(jié)果進行排序,并且當(dāng)不同的文章含有相同的標簽數(shù)量時,我們使用最近發(fā)布的文章,我們?nèi)‘?dāng)前結(jié)果最開頭的四篇文章作為和當(dāng)前文章關(guān)聯(lián)最緊密的文章去顯示;
添加sililar_post對象到render()函數(shù)的上下文字典字典中,如下圖:
return rendr(request,'blog/post/detail.html',{'post':post,'comments':comments,'comment_form':comment_form,'similar_posts':similar_posts})
現(xiàn)在 編輯 blog/post/detail.html模板,并在文章評論列前面添加下面的代碼:
<h2>Similar posts</h2>
{% for post in similar_posts %}
<p>
<a href = "{{post.get_absolute_url}}">{{post.title}}</a>
</p>
{% empty%}
There are no similar posts yet.
{% endfor %}
也建議 和我們在文章列表模板里面做的一樣 ,再添加標簽列表到你的文章詳情模板。現(xiàn)在你的文章詳情頁看起來應(yīng)該像下面這個樣子:
XXXXXX
現(xiàn)在你可以成功的推薦相關(guān)文章給你的用戶了,django-taggit同樣包含一個similar_objects()管理器,你可以使用這個管理器通過共享標簽檢索對象,你可以通過http://django-taggit.readthedocs.org/en/latest/api.html來查看全部的django-taggit相關(guān)技術(shù)知識。
總結(jié):
在本章節(jié),我們學(xué)習(xí)了如何使用Django 的froms和models froms。我們創(chuàng)建一個通過郵件分享網(wǎng)站內(nèi)容的系統(tǒng),我們給自己的博客創(chuàng)建了一個評論系統(tǒng),我們給我們的博客文章添加了標簽,整合一個第三方可重用的程序,讓我們可以創(chuàng)建關(guān)聯(lián)的查詢組,去查詢關(guān)聯(lián)對象。
在下一章,我們會學(xué)習(xí)如何創(chuàng)建一個 自定義的標簽和過濾器。我們還會去創(chuàng)建一個自定義站點地圖并填充到你的博客文章中,并在我們的程序中聚合一個高級搜索引擎。