相對來講,評論實際上是另一個比較獨立的功能。Django 提倡,若是功能相對比較獨立的話,最好是建立一個應用,把相應的功能代碼寫到這個應用裏。咱們的第一個應用叫 blog,它裏面放了展現博客文章列表和細節等相關功能的代碼。而這裏咱們再建立一個應用,名爲 comments,這裏面將存放和評論功能相關的代碼。首先激活虛擬環境,而後輸入以下命令建立一個新的應用:html
python manage.py startapp comments
咱們能夠看到生成的 comments 應用目錄結構和 blog 應用的目錄是相似的。關於建立應用以及 Django 的目錄結構在 創建 Django 博客應用 中已經有過介紹。建立新的應用後必定要記得在 settings.py 裏註冊這個應用,Django 才知道這是一個應用。前端
blogproject/settings.py ... INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', 'comments', # 註冊新建立的 comments 應用 ] ...
用戶評論的數據必須被存儲到數據庫裏,以便其餘用戶訪問時 Django 能從數據庫取回這些數據而後展現給訪問的用戶,所以咱們須要爲評論設計數據庫模型,這和設計文章、分類、標籤的數據庫模型是同樣的,若是你忘了怎麼作,再回顧一下 建立 Django 博客的數據庫模型 中的作法。咱們的評論模型設計以下(評論模型的代碼寫在 commentmodels.py 裏):python
comments/models.py from django.db import models from django.utils.six import python_2_unicode_compatible # python_2_unicode_compatible 裝飾器用於兼容 Python2 @python_2_unicode_compatible class Comment(models.Model): name = models.CharField(max_length=100) email = models.EmailField(max_length=255) url = models.URLField(blank=True) text = models.TextField() created_time = models.DateTimeField(auto_now_add=True) post = models.ForeignKey('blog.Post') def __str__(self): return self.text[:20]
這裏咱們會保存評論用戶的 name(名字)、email(郵箱)、url(我的網站),用戶發表的內容將存放在 text 字段裏,created_time 記錄評論時間。最後,這個評論是關聯到某篇文章(Post)的,因爲一個評論只能屬於一篇文章,一篇文章能夠有多個評論,是一對多的關係,所以這裏咱們使用了 ForeignKey。關於 ForeKey 咱們前面已有介紹,這裏再也不贅述。git
同時注意咱們爲 DateTimeField
傳遞了一個 auto_now_add=True
的參數值。auto_now_add
的做用是,當評論數據保存到數據庫時,自動把 created_time
的值指定爲當前時間。created_time
記錄用戶發表評論的時間,咱們確定不但願用戶在發表評論時還得本身手動填寫評論發表時間,這個時間應該自動生成。github
建立了數據庫模型就要遷移數據庫,遷移數據庫的命令也在前面講過。在虛擬環境下分別運行下面兩條命令:數據庫
python manage.py makemigrations python manage.py migrate
這一節咱們將學習一個全新的 Django 知識:表單。那麼什麼是表單呢?基本的 HTML 知識告訴咱們,在 HTML 文檔中這樣的代碼表示一個表單:django
<form action="" method="post"> <input type="text" name="username" /> <input type="password" name="password" /> <input type="submit" value="login" /> </form>
爲何須要表單呢?表單是用來收集並向服務器提交用戶輸入的數據的。考慮用戶在咱們博客網站上發表評論的過程。當用戶想要發表評論時,他找到咱們給他展現的一個評論表單(咱們已經看到在文章詳情頁的底部就有一個評論表單,你將看到表單呈現給咱們的樣子),而後根據表單的要求填寫相應的數據。以後用戶點擊評論按鈕,這些數據就會發送給某個 URL。咱們知道每個 URL 對應着一個 Django 的視圖函數,因而 Django 調用這個視圖函數,咱們在視圖函數中寫上處理用戶經過表單提交上來的數據的代碼,好比驗證數據的合法性而且保存數據到數據庫中,那麼用戶的評論就被 Django 後臺處理了。若是經過表單提交的數據存在錯誤,那麼咱們把錯誤信息返回給用戶,並在前端從新渲染,並要求用戶根據錯誤信息修正表單中不符合格式的數據,再從新提交。服務器
Django 的表單功能就是幫咱們完成上述所說的表單處理邏輯,表單對 Django 來講是一個內容豐富的話題,很難經過教程中的這麼一個例子涵蓋其所有用法。所以咱們強烈建議你在完成本教程後接下來的學習中仔細閱讀 Django 官方文檔關於 表單 的介紹,由於表單在 Web 開發中會常常遇到。markdown
下面開始編寫評論表單代碼。在 comments 目錄下(和 models.py 同級)新建一個 forms.py 文件,用來存放表單代碼,咱們的表單代碼以下:session
comments/forms.py from django import forms from .models import Comment class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ['name', 'email', 'url', 'text']
要使用 Django 的表單功能,咱們首先導入 forms 模塊。Django 的表單類必須繼承自 forms.Form
類或者 forms.ModelForm
類。若是表單對應有一個數據庫模型(例如這裏的評論表單對應着評論模型),那麼使用 ModelForm
類會簡單不少,這是 Django 爲咱們提供的方便。以後咱們在表單的內部類 Meta
裏指定一些和表單相關的東西。model = Comment
代表這個表單對應的數據庫模型是 Comment
類。fields = ['name', 'email', 'url', 'text']
指定了表單須要顯示的字段,這裏咱們指定了 name、email、url、text 須要顯示。
Django 爲何要給咱們提供一個表單類呢?爲了便於理解,咱們能夠把表單和前面講過的 Django ORM 系統作類比。回想一下,咱們使用數據庫保存咱們建立的博客文章,可是咱們從頭至尾沒有寫過任何和數據庫有關的代碼(要知道數據庫自身也有一門數據庫語言),這是由於 Django 的 ORM 系統內部幫咱們作了一些事情。咱們遵循 Django 的規範寫的一些 Python 代碼,例如建立 Post、Category 類,而後經過運行數據庫遷移命令將這些代碼反應到數據庫。
Django 的表單和這個思想相似,正常的前端表單代碼應該是和本文開頭所說起的那樣,可是咱們目前並無寫這些代碼,而是寫了一個 CommentForm
這個 Python 類。經過調用這個類的一些方法和屬性,Django 將自動爲咱們建立常規的表單代碼,接下來的教程咱們就會看到具體是怎麼作的。
當用戶提交表單中的數據後,Django 須要調用相應的視圖函數來處理這些數據,下面開始寫咱們視圖函數處理邏輯:
comments/views.py from django.shortcuts import render, get_object_or_404, redirect from blog.models import Post from .models import Comment from .forms import CommentForm def post_comment(request, post_pk): # 先獲取被評論的文章,由於後面須要把評論和被評論的文章關聯起來。 # 這裏咱們使用了 Django 提供的一個快捷函數 get_object_or_404, # 這個函數的做用是當獲取的文章(Post)存在時,則獲取;不然返回 404 頁面給用戶。 post = get_object_or_404(Post, pk=post_pk) # HTTP 請求有 get 和 post 兩種,通常用戶經過表單提交數據都是經過 post 請求, # 所以只有當用戶的請求爲 post 時才須要處理表單數據。 if request.method == 'POST': # 用戶提交的數據存在 request.POST 中,這是一個類字典對象。 # 咱們利用這些數據構造了 CommentForm 的實例,這樣 Django 的表單就生成了。 form = CommentForm(request.POST) # 當調用 form.is_valid() 方法時,Django 自動幫咱們檢查表單的數據是否符合格式要求。 if form.is_valid(): # 檢查到數據是合法的,調用表單的 save 方法保存數據到數據庫, # commit=False 的做用是僅僅利用表單的數據生成 Comment 模型類的實例,但還不保存評論數據到數據庫。 comment = form.save(commit=False) # 將評論和被評論的文章關聯起來。 comment.post = post # 最終將評論數據保存進數據庫,調用模型實例的 save 方法 comment.save() # 重定向到 post 的詳情頁,實際上當 redirect 函數接收一個模型的實例時,它會調用這個模型實例的 get_absolute_url 方法, # 而後重定向到 get_absolute_url 方法返回的 URL。 return redirect(post) else: # 檢查到數據不合法,從新渲染詳情頁,而且渲染表單的錯誤。 # 所以咱們傳了三個模板變量給 detail.html, # 一個是文章(Post),一個是評論列表,一個是表單 form # 注意這裏咱們用到了 post.comment_set.all() 方法, # 這個用法有點相似於 Post.objects.all() # 其做用是獲取這篇 post 下的的所有評論, # 由於 Post 和 Comment 是 ForeignKey 關聯的, # 所以使用 post.comment_set.all() 反向查詢所有評論。 # 具體請看下面的講解。 comment_list = post.comment_set.all() context = {'post': post, 'form': form, 'comment_list': comment_list } return render(request, 'blog/detail.html', context=context) # 不是 post 請求,說明用戶沒有提交數據,重定向到文章詳情頁。 return redirect(post)
這個評論視圖相比以前的一些視圖複雜了不少,主要是處理評論的過程更加複雜。具體過程在代碼中已有詳細註釋,這裏僅就視圖中出現了一些新的知識點進行講解。
首先咱們使用了 redirect
函數。這個函數位於 django.shortcuts 模塊中,它的做用是對 HTTP 請求進行重定向(即用戶訪問的是某個 URL,但因爲某些緣由,服務器會將用戶重定向到另外的 URL)。redirect
既能夠接收一個 URL 做爲參數,也能夠接收一個模型的實例做爲參數(例如這裏的 post)。若是接收一個模型的實例,那麼這個實例必須實現了 get_absolute_url
方法,這樣 redirect
會根據 get_absolute_url
方法返回的 URL 值進行重定向。
另外咱們使用了 post.comment_set.all()
來獲取 post
對應的所有評論。 Comment
和Post
是經過 ForeignKey
關聯的,回顧一下咱們當初獲取某個分類 cate
下的所有文章時的代碼:Post.objects.filter(category=cate)
。這裏 post.comment_set.all()
也等價於 Comment.objects.filter(post=post)
,即根據 post
來過濾該 post
下的所有評論。但既然咱們已經有了一個 Post
模型的實例 post
(它對應的是 Post
在數據庫中的一條記錄),那麼獲取和 post
關聯的評論列表有一個簡單方法,即調用它的 xxx_set 屬性來獲取一個相似於 objects 的模型管理器,而後調用其 all
方法來返回這個 post
關聯的所有評論。 其中 xxx_set 中的 xxx 爲關聯模型的類名(小寫)。例如 Post.objects.filter(category=cate)
也能夠等價寫爲 cate.post_set.all()
。
視圖函數須要和 URL 綁定,這裏咱們在 comment 應用中再建一個 urls.py 文件,寫上 URL 模式:
comments/urls.py from django.conf.urls import url from . import views app_name = 'comments' urlpatterns = [ url(r'^comment/post/(?P<post_pk>[0-9]+)/$', views.post_comment, name='post_comment'), ]
別忘了給這個評論的 URL 模式規定命名空間,即 app_name = 'comments'
。
最後要在項目的 blogprokect 目錄的 urls.py 裏包含 commentsurls.py 這個文件:
blogproject/urls.py urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'', include('blog.urls')), + url(r'', include('comments.urls')), ]
咱們能夠看到評論表單和評論列表是位於文章詳情頁面的,處理文章詳情頁面的視圖函數是 detail,相應地須要更新 detail,讓它生成表單和從數據庫獲取文章對應的評論列表數據,而後傳遞給模板顯示:
blog/views.py import markdown from django.shortcuts import render, get_object_or_404 + from comments.forms import CommentForm from .models import Post, Category def detail(request, pk): post = get_object_or_404(Post, pk=pk) post.body = markdown.markdown(post.body, extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ]) # 記得在頂部導入 CommentForm form = CommentForm() # 獲取這篇 post 下的所有評論 comment_list = post.comment_set.all() # 將文章、表單、以及文章下的評論列表做爲模板變量傳給 detail.html 模板,以便渲染相應數據。 context = {'post': post, 'form': form, 'comment_list': comment_list } return render(request, 'blog/detail.html', context=context)
使用 Django 表單的一個好處就是 Django 能幫咱們自動渲染表單。咱們在表單的視圖函數裏傳遞了一個 form
變量給模板,這個變量就包含了自動生成 HTML 表單的所有數據。在 detail.html 中經過 form 來自動生成表單。刪掉原來用於佔位的 HTML 評論表單代碼,即下面這段代碼:
<form action="#" method="post" class="comment-form"> <div class="row"> <div class="col-md-4"> <label for="id_name">名字:</label> <input type="text" id="id_name" name="name" required> </div> ... </div> <!-- row --> </form>
替換成以下的代碼:
<form action="{% url 'comments:post_comment' post.pk %}" method="post" class="comment-form"> {% csrf_token %} <div class="row"> <div class="col-md-4"> <label for="{{ form.name.id_for_label }}">名字:</label> {{ form.name }} {{ form.name.errors }} </div> <div class="col-md-4"> <label for="{{ form.email.id_for_label }}">郵箱:</label> {{ form.email }} {{ form.email.errors }} </div> <div class="col-md-4"> <label for="{{ form.url.id_for_label }}">URL:</label> {{ form.url }} {{ form.url.errors }} </div> <div class="col-md-12"> <label for="{{ form.text.id_for_label }}">評論:</label> {{ form.text }} {{ form.text.errors }} <button type="submit" class="comment-btn">發表</button> </div> </div> <!-- row --> </form>
{{ form.name }}、{{ form.email }}、{{ form.url }} 等將自動渲染成表單控件,例如 <input>
控件。
{{ form.name.errors }}、{{ form.email.errors }} 等將渲染表單對應字段的錯誤(若是有的話),例如用戶 email 格式填錯了,那麼 Django 會檢查用戶提交的 email 的格式,而後將格式錯誤信息保存到 errors 中,模板便將錯誤信息渲染顯示。
在 detail 視圖函數咱們獲取了所有評論數據,並經過 comment_list
傳遞給了模板。和處理 index 頁面的文章列表方式是同樣的,咱們在模板中經過 {% for %} 模板標籤來循環顯示文章對應的所有評論內容。
刪掉佔位用的評論內容的 HTML 代碼,即以下的代碼:
<ul class="comment-list list-unstyled"> <li class="comment-item"> <span class="nickname">追夢人物</span> <time class="submit-date">2017年3月12日 14:56</time> <div class="text"> 文章觀點又有道理又符合人性,這纔是真正爲了表達觀點而寫,不是爲了迎合某某知名人士粉絲而寫。我以爲若是瓊瑤是前妻,生了三孩子後被一不知名的女人挖了牆角,我不信誰會說那個女人是追求真愛,說同情瓊瑤罵小三的女人都是弱者。 </div> </li> ... </ul>
替換成以下的代碼:
<ul class="comment-list list-unstyled"> {% for comment in comment_list %} <li class="comment-item"> <span class="nickname">{{ comment.name }}</span> <time class="submit-date">{{ comment.created_time }}</time> <div class="text"> {{ comment.text }} </div> </li> {% empty %} 暫無評論 {% endfor %} </ul>
接下來嘗試在詳情頁下的評論表單提交一些評論數據,能夠看到詳情頁的評論列表處渲染了你提交的評論數據。
本章節的代碼位於:Step12: comments。
若是遇到問題,請經過下面的方式尋求幫助。
在 評論 - 追夢人物的博客 的評論區留言。
將問題的詳細描述經過郵件發送到 djangostudyteam@163.com,通常會在 24 小時內回覆。
更多Django 教程,請訪問 追夢人物的博客。