使用高級特性來優化你的博客
在上一章中,你創建了一個基礎的博客應用。現在你將要改造它成為一個功能更加齊全的博客,利用一些高級的特性例如添加評論,給帖子打上tag,檢索出相似的帖子。在本章中,你將會學習以下幾點:
- 通過models創建表單
- 構建復雜的QuerySets
現在我們準備為博客創建一個評論系統,這樣用戶可以在帖子上進行評論。你需要做到以下幾點來創建一個評論系統:
- 創建一個modle保存評論
- 創建一表單用來提交平路你和驗證輸入的數據
- 添加一個view來處理表單和保存新的評論到數據庫中
- 編輯帖子的詳情template來展示評論列表以及用來添加新評論的表單
首先,讓我們創建一個model來存儲評論。打開你博客應用下的models.py文件添加如下代碼:
class Comment(models.Model):
name = models.CharField(max_length=80)
post = models.ForeignKey(Post, related_name='comments')
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中定義多對單的關系是因為每一條評論只能在一個帖子下生成,而每一個帖子又可能包含多個評論。related_name屬性允許我們命名這個屬性這樣我們就可以使用這個關系從有關聯的對象來讀取這兒。定義好這個之后,我們可以從一條評論來取到對應的帖子通過使用 comment.post
和取回一個帖子所有的評論通過使用post.comments.all(
。如果你沒有定義related_name屬性,Django會使用這個model的命名加上_set(例如:comment_set)來命名關聯對象讀取這兒的manager。
你可以學習更多關于多對單的關系通過訪問 多對單的關系。
我們有包含一個active布爾字段用來手動使不好的評論無效不展示。我們使用created字段使評論默認根據創建時間來進行排序。
你剛創建的這個新的Comment model
并沒有同步到數據庫中。運行以下命令通過新的model生成一個新的數據遷移,并創建數據庫:
python manage.py makemigrations blog
python manage.py migrate
添加站點管理
現在,我們可以添加我們新的model到管理站點中通過簡單的接口來管理評論。打開博客應用下的admin.py
文件,添加如下內容:
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)
訪問 http://127.0.0.1:8000/admin/ 會看多多出一個 Comments
;
通過models創建表單
我們仍然需要創建一個表單可以讓我們的用戶在博客帖子下進行評論。請記住,Django有兩個用來創建表單的基礎類:Form和ModelForm。你已經使用過前者可以讓用戶通過email來分享帖子。在下面的例子中,你將需要使用ModelForm因為你必須從你的Comment model中創建一個動態的表單。編輯博客應用下的forms.py,添加如下代碼:
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body')
從model中創建表單,我們只需要在這個表單的Meta類中聲明使用哪個model來構建表單。Django將會解析model并為我們動態的創建表單。每一種model字段類型都有對應的默認表單字段類型。默認的,Django創建的表單包含model中包含的每個字段。當然,你可以明確的告訴框架你想在你的表單中包含哪些字段通過使用fields列,或者定義哪些字段你不需要的通過使用exclude列。對于我們的CommentForm,我們在表單中只需要name,email,和body字段,因為我們只需要用到這3個字段讓我們的用戶來填寫。
在views中操作ModelForms
我們會使用帖子的詳情view來實例化表單,能更簡單的處理它。編輯models.py文件,導入Comment modle和CommentForm表單,并且修改post_detail view如下所示:
from .forms import EmailPostForm, CommentForm
from django.views.generic import ListView
class PostListView(ListView):
queryset = Post.published.all()
context_object_name = 'posts'
paginate_by = 3
template_name = 'blog/post/list.html'
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)
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.post = post
new_comment.save()
else:
comment_form = CommentForm()
return render(request,'blog/post/detail.html',{'post': post, 'comments': comments, 'comment_form': comment_form})
讓我們來回顧下我們剛才對對我的view添加了哪些操作。我們使用post_detail view來顯示帖子和對應的評論。我們添加了一個QuerySet來顯示這個帖子所有有效的評論:
comments = post.comments.filter(active=True)
我們從post對象開始構建這個QuerySet。使用在Comment model中通過related_name屬性定義的關系啦返回所有有關系的對象給comments*。
我們還在這個view中讓我們的用戶添加一條新的評論。如果調用這個view通過GET請求,就我們創建了一個表單實例通過使用comment_fomr = commentForm()
。如果請求是一個已經完成驗證的POST,我們實例化表單來使用提交的數據并且驗證數據通過使用is_valid()方法。如果這個表單是無效的,我們會在template中渲染驗證錯誤的信息。如果表單通過驗證,我們會做以下的操作:
- 我們創建一個新的Comment對象通過調用這個表單的save()方法,如下所示:
new_comment = comment_form.save(commit=False)
saven()方法創建了一個model的實例用來保存表單的數據到數據庫中。如果你調用這個方法通過設置comment=False
,你創建的model實例不會即時保存到數據中。這是非常方便的當你想在最終保存之前修改這個model對象,我們接下來將做這一步驟。save()方法是給ModelForm用的,但不是給表單實例們用的,因為它們沒有關聯上任何model。
- 我們分配一個帖子給我們剛才創建的評論:
new_comment.post = post
通過以上動作,我們指定新的評論是屬于分配的帖子。
3.我們保存新的評論到數據庫,如下所示:
new_comment.save()
我們的view已經準備好顯示和處理新的評論了。
在帖子詳情template中添加評論
我們為帖子創建了一個管理評論的功能。現在我們需要修改我們的post_detail.html template來適應這個功能,通過做到以下步驟:
- 顯示這個帖子的評論總數
- 顯示評論的列
- 顯示一個表單給用戶來添加新的評論
首先,我們來添加評論的總數。打開blog/detail.html template在content區塊中添加如下代碼:
{% with comments.count as total_comments %}
<h2>
{{ total_comments }} comment{{ total_comments|pluralize }}
</h2>
{% endwith %}
我們在template中使用Django ORM執行comments.count()
QuerySet。注意,在Django template語言中調用方法時不使用圓括號。{% with %}
tag允許我們分配一個值給新的變量,這個變量可以一直使用直到遇到{% endwith %}
。
{% whti %}
template tag是非常有用的可以避開直接使用數據庫或花費大量的時間在處理復雜的方法。
我們使用pluralize template filter在單詞comment的后面展示total_comments的復數形式。Template filters使輸入的變量值經過應用然后返回計算后的值。我們將會更多的討論tempalte filters在第三章 擴展你的博客應用中。
這pluralize template filter 會在值的末尾顯示一個"s"如果值不為 1。在之前的文本將會渲染成類似: 0 comments, 1 comment 或者 N comments。Django內置大量的template tags 和 filters來幫助你通過各種方法展示各類信息。
現在,讓我們加入評論列。在blog/detail.html中之前的代碼后面加入以下內容:
{% 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 %}
template tag來循環所有的評論。我們會顯示一個默認的信息如果comments列為空,告訴我們的用戶這篇帖子還沒有任何評論。我們通過使用{{ forloop.counter }}
變量來枚舉評論,該變量包含在每次迭代的循環計數中。之后我們顯示發送評論的用戶名,日期,和評論的內容。
最后,你需要渲染表單或者顯示一條成功的信息來代替表單當表單提交成功后。在之前的代碼后面添加如下內容:
{% 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 %}
這段代碼非常簡潔明了:如果存在new_comment對象,我們會展示一條成功信息因為成功創建了一條新評論。否則,我們通過一個段落<p>
元素渲染表單中每一個字段,并且表明這個POST請求包含有CSRF標記。在瀏覽器中打開 http://127.0.0.1:8000/blog/ 然后點擊任意一篇帖子的標題進入它的詳情頁面。你會看到如下頁面展示:
郵箱一定要是正確格式,不然提交沒反應。