Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
程派微信號:codingpy
本文系作者 Django學習小組?授權編程派原創發表,并經編程派編輯,轉載請注明出處及微信ID(codingpy)。
通過前五周的時間我們開發了一個簡單的個人 Blog,本周我們將實現 blog 文章的評論功能。(快速查看前五期內容,請點擊文末鏈接)
實現思路
首先需要為評論(Comment)設計一個數據庫表,并編寫相應的 Model,將評論與文章關聯,再編寫發表評論的視圖,設置相應的 url 即可。
評論的 Model 設計
請點擊閱讀原文,查看詳細代碼
參照大部分博客評論的樣式,我們的 BlogComment Model 包含這些字段:
user_name:用戶在評論前先要填寫他們想使用的昵稱
user_email:用戶在評論前先要填寫他們想使用的郵箱
body:用戶提交的評論內容
created_time:評論提交時間
article:評論關聯的文章,因為一個評論只能關聯某一篇文章,而一篇文章下可能有多個評論,因此是一對多的關系,使用 ForeignKey
評論的表單
表單用來給服務器后臺提交用戶填寫的數據,例如平時我們看到的填寫登錄、注冊信息的頁面就是一個登錄、注冊表單,用戶填寫表單信息后,點擊提交按鈕,表單中填寫的內容就會打包發送給服務器后臺。
我們需要為用戶填寫評論設置一個表單,django 的 form 模塊為我們提供了自動生成表單的功能,如果對表單不熟悉請參閱:官方文檔:表單概述 ,以了解基本的表單使用方法(如果你對表單感覺很陌生的話)。
下面我們使用 Django 的 ModelForm ( django ModelForm 介紹 )類為我們自動生成表單。首先在 blog 目錄下新建一個 forms.py (和 models.py 同一目錄)文件用來存放 form 的代碼:
請點擊閱讀原文,查看詳細代碼
視圖函數
這里我們一如既往堅持使用基于類的通用視圖,由于涉及到評論表單的提交處理,因此我們使用 FormView。這里對 FormView 的使用稍作講解。
在 Django 的基于函數的視圖中,涉及表單的處理的視圖其邏輯一般是這樣的:
請點擊閱讀原文,查看詳細代碼
即,首先判斷用戶是否通過表單 POST 了數據過來,如果是,則根據 POST 過來的數據構建一個表單,如果數據驗證合法(form.is_valid),則創建評論,否則返回表單提交頁。如果沒有 POST 數據,則做其他相應的事情。FormView 把這些邏輯做了整合,無需寫那么多 if else 語句:
請點擊閱讀原文,查看詳細代碼
為了方便地重定向回原來提交評論的文章詳情頁面,我們為文章(Article)的模型新增一個方法:get_absolute_url,調用該方法將得到該 Article 對應的 url,例如這是文章 1 的 url:http://localhost:8000/article/1,則調用后返回 /article/1,這樣調用 HttpResponseRedirect 后將返回該 url 下的文章詳情頁。
請點擊閱讀原文,查看詳細代碼
同時為了在詳情頁渲染一個評論表單,稍微修改一下 ArticleDetailView 的視圖函數,把評論表單 form 插入模板上下文中:
請點擊閱讀原文,查看詳細代碼
URL 設置
設置模板文件
新增了一個 comment.html 文件以渲染評論表單和評論列表,并且修改了 detail.html 文件以在文章詳情頁顯示評論表單和評論列表,修改了blog/tatic 下的 style.css 為評論添加樣式,由于代碼比較多,就不貼出來了,主要是 html 和 css 的前端相關代碼,請到 GitHub 倉庫 更新相關的模板和靜態資源文件。
至此,整個評論功能的框架做好了,顯示效果如下:
當然這只是一個評論的框架,很多細節有待處理和完善,但無論如何,用戶已經可以為我們的文章發表評論意見了。
前情回顧
第一周:Django學習與實戰(一):編寫博客的 Model 和首頁面
第二周:Django學習與實戰(二):博客詳情頁面和分類頁面
第三周:Django學習與實戰(三):文章列表分頁和代碼語法高亮
第四周:Django學習與實戰(四):基于類的通用視圖詳解
第五周:Django學習與實戰(五):標簽云與文章歸檔
在經歷過多說和網易云跟帖后,總算是下定決心自己要寫一個評論系統了。
我們在使用的很多評論系統中,目前比較流行的就是樓中樓的方式了,比如百度貼吧,wordpress等等。在這以前,一般都是按照時間順序進行1樓、2樓、3樓的展示,如果要回復某個人,使用@符號標識出這個用戶的名字,然后回復內容。可是這樣存在一個很大的問題,討論問題沒有集中在一起,其他用戶根本不知道你們在討論什么,原作者在1樓發表評論,你進來回復這個用戶的評論時,已經到10樓了,原作者再回復你又到20樓了。其他用戶看到10樓時,早已經忘記原作者說了什么了。
百度貼吧在改版之前就是這種方式,后來在新版中啟用了樓中樓的方式,這種方式,關于某個話題的討論就能集中在一塊了。
同時,知乎也對他的評論系統進行了一次改版,不過不是改版成樓中樓,而是在每個有對話評論的后面加上一個彈窗鏈接查看對話,點擊鏈接后彈窗能看到這兩個人之間互動的所有評論。
采用時間順序倒序或者正序平鋪的方式展示評論,這種方式實現起來簡單,但是閱讀困難;采用樓中樓的方式展示評論,對用戶的閱讀習慣比較友好,但是實現起來可能比較困難。不過,最后仍然決定采用樓中樓的方式來,雖然本人博客的評論量也少的可憐,不過還是決定要實現一下。
先說下前后端使用的語言和框架,前端考慮到頁面渲染和比較多的事件調用,使用了vue框架,vue應該說不是最好的選擇,畢竟對一個評論的前端部門來說,可能有點大材小用,不過為了快速開發,也就選擇了vue。后端使用的是php語言,數據庫使用的是mysql。
數據庫表的設計,既要考慮到可以導入以前的數據,又能方便以后添加新的評論。這里我創建了3個表: 文章表,用戶表,評論表。
在網易云跟帖關閉之前,我把自己的數據導出來了(多說的數據已經丟失,不知道導出的格式是什么了),我們來看下網易云跟帖里導出數據的格式:
從上面的數據里,可以看到,每個文章都有標題,url和評論,評論的每一項都有自己對應的id,其回復的評論pid,內容content,該評論的用戶userid, nickname和avatar。我這里也就只摘取主要的信息錄入到數據庫中。
2.1 用戶表
用戶表相對來說比較簡單,要考慮的就是原有的userid也要作為字段進行保存,方便在導入評論數據時能找到對應的用戶,在評論數據也導入完成后即可將該字段刪除,以后新添加的注冊用戶用不到這個字段了。用戶表的設計:
字段 | 類型 | 說明 |
---|---|---|
id | int | 自增,主鍵 |
wid | int | 用戶原有的userid |
nickname | varchar(50) | 昵稱 |
avatar | varchar(100) | 頭像 |
status | int | 狀態 |
設計好用戶表后,將原數據中所有的用戶都單獨拿出來,然后使用userid作為key存儲到一個數組中,這樣也能起到一個去重的效果。把拿到的所有的用戶數據存儲到用戶表
2.2 評論表
在設計評論表,主要考慮如下的因素:
評論必須依托于文章和用戶才能存在,因此評論的外鍵是文章標識和userid,留言板是一個文章內容為空的評論形式;
我想以后新的評論能使用自增id,而不是跟隨原有評論的cid來產生新的評論id,因此這次評論表的主鍵是id,原有的評論id只作為其中的一個字段wid來構造樓中樓的關系,這些舊評論插入到數據表時都會有新的評論id;
樓中樓的評論是處在某個評論下的,同時,樓中樓里還有相互之前的互動回復。因此這個評論的pid(parentid)表示當前評論處于哪個評論之下,同時replyid表示是回復的哪個評論;若直接回復的父級評論,則pid與replyid相同,都是父級評論的id,若回復的不是父級評論,則pid為父級評論的id,replyid為回復評論的id;pid或replyid為0時,則表示直接對文章發表評論。
因此我們的評論表是這樣設計的:
字段 | 類型 | 說明 |
---|---|---|
id | int | 自增,主鍵 |
wid | int | 評論原有的主鍵cid |
uid | int | 用戶id |
replyid | int | 該評論回復的評論id,沒有則為0 |
pid | int | 該評論所在的父級id,沒有則為0 |
aid | varchar(100) | 文章的標識 |
content | varchar(300) | 評論內容 |
createtime | int | 評論時間的時間戳 |
表中的aid(文章的標識)可以是文章的url,文章的id或者其他任何能夠唯一識別該文章的東東都可以。這里我們使用的是文章的uri來作為唯一標識,比如上面數據中的文章,我們使用/node/2017/02/20/node-express-forum.html來標識文章。其他文章同理。
將這些評論寫入表時,我們還要注意的是,原數據中,每個評論都對應著一個用戶,在我設計的系統里,用戶與評論分來了,只使用uid來進行關聯。新的用戶與新的評論都是使用自有的自增主鍵,因此在原有評論進行入庫時,需要將原來的userid轉換為新用戶表中的主鍵id,新舊數據進行統一。
文章表不做解釋。
前端部分主要是負責展示每個文章的評論,同時讓登錄用戶可以添加評論。
3.1 展示評論
我們已經對每個評論都添加了文章標識,前端只要根據aid就能拿到當前文章所有的評論。不過我們的評論是要樓中樓的方式展示的,不能一股腦的把數據平鋪到頁面中。我們在2.2中也說了,pid為0的評論都是直接對文章進行評論的,這些評論應該是作為一級評論展示的;pid為其他數據的,必然是屬于某個評論之下,應當作為樓中樓展示。
同時,無論一級評論,還是樓中樓的評論,都有可能產生分頁的情況,因此這里也要做好分頁處理。
那么最終,我們前端拿到的結構應該大致是這樣的:
前端拿到接口返回的數據后,就可以渲染頁面了。在頭像的處理上,也考慮到了https的環境,因此返回的頭像鏈接都是//開頭的形式。
3.2 參與評論
用戶對文章或者某個評論產生了共鳴,需要留言討論一番,我們就需要用戶能夠把自己的評論也添加進去。
評論的類型,細分的話,可以分為3類:
直接對文章發表評論,pid與replyid為空;
對一級評論進行回復,pid與replyid均為一級評論的id;
對樓中樓進行回復,pid為一級評論的id,replyid為你回復的評論的id
我這里前端的實現參考了oschina(開源中國)的評論方式。直接對文章評論,是直接在頂部的評論窗口進行輸入;對其他評論進行回復時,采用彈窗的方式來進行回復。彈窗回復的好處就是,頁面不用滾動,用戶對某個評論的感知也能停留在這個位置;同時也不用增加各種不必要的小輸入框來讓用戶輸入評論。
3.3 登錄
在登錄問題上,我也是糾結了不少的時間,究竟是使用自己的登錄系統呢,還是使用第三方登錄呢,或者是用戶不用注冊登錄,只要輸入郵箱和昵稱就能進行評論呢?
使用自己的評論系統,那么就需要開發一套注冊和登錄流程,開發麻煩,而且對于想要回復一句話的用戶來說,可能就直接放棄注冊了;若只要輸入郵箱和昵稱就能評論,我考慮到可能會引起用戶的無限評論,無法控制。因此,最后還是考慮接入第三方的登錄,這里選擇了使用微博作為第三方登錄的入口,后續會考慮加入github的帳號登錄。
關于如何接入微博的第三方登錄,我們下篇文章再講,文檔齊全,對不熟悉的開發者來說,剛開始可能有點懵逼,不過應該問題不大。
3.4 添加郵箱功能
用戶在第三方登錄成功后,在名字旁邊有個小的input輸入框,可以讓用戶輸入郵箱來接收回復提醒,這個輸入完全是自愿的,不輸入郵箱也依然可以評論。也是考慮到本站是個小站,訪問量極低,用戶可能一時興起評論了兩句,事后又想起這個網站來,又不知道怎么找了。因此就想著添加一個郵件提醒功能,不讓大神的評論石沉大海。
3.5 特別注意
前端部門引入了vue框架,評論模塊在每個文章頁都會加載。為了防止評論模塊中的vue庫對外部的資源造成影響(比如版本沖突等),我先把全局變量給了wzVue,然后在把Vue注銷掉:
同時,在剛開始實現完成評論功能的時候,用戶只要進到這個頁面,評論就會加載。但是有個問題就是,用戶不一定會把你的文章看到底部,不一定就看你的評論。因此后來文章就改成了按需加載,只有用戶滾動到底部,有想要看評論的意向時采取加載評論。
最終展示的效果就是這樣:
作為一名前端開發,用僅有的后端知識開發一套博客的評論系統,顯得是非常的簡陋,整個框架的設計感覺也是很糙。同時緩存系統用的不熟練,不能做到評論信息的立即更新。這個系統依然還有很多改進的地方。歡迎大家對蚊子(師少兵)多多提意見和建議。
在寫這篇文章的時候,想著是以后要改版的時候,可以做成評論同步加載的方式進行。生成后的文章,更新頻率極低,甚至不太變動,那么緩存的就是評論的內容,每當有新的評論時,就刪除當前文章的緩存,重新加載新的數據,然后再緩存上新的數據,這樣在評論數據更新比較低的時候,可以緩存的時間更長,同時也有利于搜索引起對評論內容的抓取。
?前面給大家介紹了Vue的組件功能,本文我們通過一個評論列表的案例來鞏固下組件的內容,具體效果如下:
在這里插入圖片描述
??我們先來整理下基礎的頁面如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
},
methods: {
}
})
</script>
</body>
</html>
??在基礎頁面的基礎上我們來添加 底部的 評論列表,用bootstrap來實現。如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
<link rel="stylesheet" href="./lib/bootstrap-3.3.7.css">
</head>
<body>
<div id="app">
<ul class="list-group">
<!--循環取出列表數據-->
<li class="list-group-item" v-for="(item ,i) in list" :key="item.id">
<span class="badge">{{item.name}}</span>
{{item.content}}
</li>
</ul>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
list: [
{id: Date.now(),name:"波波烤鴨1",content:"非常棒..."},
{id: Date.now(),name:"波波烤鴨2",content:"非常棒..."},
{id: Date.now(),name:"波波烤鴨3",content:"非常棒..."}
]
},
methods: {}
})
</script>
</body>
</html>
在這里插入圖片描述
??現在我們通過Vue的組件來添加 評論的頭部,
在這里插入圖片描述
在這里插入圖片描述
組件使用
在這里插入圖片描述
效果
在這里插入圖片描述
??組件添加好后,我們通過點擊 發表評論 來添加內容到 評論列表中。實現的邏輯是
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
效果
在這里插入圖片描述
添加的效果是實現了,但是在 第一次刷新的時候顯示的還是 固定的數據,這時我們可以 在Vue實例的生命周期的方法的 created 中再顯示的調用一次 加載數據的方法
在這里插入圖片描述
在這里插入圖片描述
這樣開始加載的就是 localStorage中的數據了。搞定~
完整代碼
*請認真填寫需求信息,我們會在24小時內與您取得聯系。