一、文章評論功能實現流程
文章評論包含兩種評論,根評論:對文章的評論;子評論:對評論的評論。兩者的區別在於是否存在父評論。
實現流程:1、構建樣式;2、提交根評論;3、顯示根評論(分為render顯示和Ajax顯示);4、提交子評論;5、顯示子評論(分為render顯示和Ajax顯示);6、評論樹顯示(博客園是樓層顯示)。
二、構建評論樣式
1、article_detail.html:
{# 文章點贊,清除浮動 #}
<div class="clearfix">
<div id="div_digg">
{# 推薦 #}
<div class="diggit action">
<span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
</div>
{# 點滅 #}
<div class="buryit action">
<span class="diggnum" id="bury_count">{{ article_obj.down_count }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;"></div>
</div>
</div>
<div class="comments">
<p>發表評論</p>
<p>昵稱: <input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
<p>評論內容</p>
<textarea name="" id="" cols="60" rows="10"></textarea> {# textarea是一個內聯標簽 #}
<p><button class="btn btn-default comment_btn">提交評論</button></p>
</div>
由於id=div_digg中存在浮動,顯示時會造成點贊區域和評論區域在同一行,在點贊區外包了一層div標簽,添加bootstrap的clearfix清除浮動。
由於textarea是一個內聯標簽,下面直接接button標簽,文本框和按鈕會顯示在同一行,同樣給按鈕包一層p標簽,讓按鈕在文本框下方顯示。
2、article_detail.css評論區樣式調整
/* 評論 */
input.author {
background-image: url("/static/font/icon_form.gif");
background-repeat: no-repeat;
border: 1px solid #ccc;
padding: 4px 4px 4px 30px;
width: 300px;
font-size: 13px;
background-position: 3px -3px;
}
3、顯示效果如下所示:

三、提交根評論
1、創建評論路由
urlpatterns = [
...
path('digg/', views.digg), # 點贊
path('comment/', views.comment), # 評論
...
]
2、創建評論視圖函數comment
def comment(request):
print(request.POST)
article_id = request.POST.get("article_id")
pid = request.POST.get("pid")
content = request.POST.get("content")
user_id = request.user.pk
# 在數據庫生成一條評論對象 父評論為空是根評論,不為空則是子評論
comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
return HttpResponse("comment")
3、在article_detail.html模板中創建評論事件
<script>
// 點贊請求
$('#div_digg .action').click(function () {
...
// 評論請求
$(".comment_btn").click(function () {
var content = $('#comment_content').val(); // 拿到評論框的內容
var pid = ""; // 父評論默認為空
$.ajax({
url: "/comment/",
type: "post",
data: {
'csrfmiddlewaretoken': $("[name= 'csrfmiddlewaretoken']").val(),
'article_id': "{{ article_obj.pk }}",
'content': content,
'pid': pid,
},
success:function (data) {
console.log(data);
// 提交后清空評論框
$("#comment_content").val("");
}
})
})
</script>
4、提交評論后,在數據庫blog_comment表中可以看到評論記錄

四、顯示根評論
1、render顯示根評論
(1)要render顯示評論,需要修改article_detail視圖函數
def article_detail(request, username, article_id):
user = UserInfo.objects.filter(username=username).first()
blog = user.blog
article_obj = models.Article.objects.filter(pk=article_id).first()
comment_list = models.Comment.objects.filter(article_id=article_id)
return render(request, "article_detail.html", locals())
傳遞comment_list到模板中,根據過濾條件獲取的是當前文章的評論。
(2)在article_detail.html中構建評論列表
{# 文章評論列表 #}
<div class="comments">
<p>評論列表</p>
<ul class="list-group comment_list">
{% for comment in comment_list %}
<li class="list-group-item">
<div>
<a href=""># {{ forloop.counter }}樓</a>
<span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>
<a href=""><span><{{ comment.user.username }}/span></a>
<a href="" class="pull-right">回復</a>
</div>
<div class="comment_con">
{# 評論內容 #}
<p>{{ comment.content }}</p>
</div>
</li>
{% endfor %}
</ul>
<p>發表評論</p>
<p>昵稱: <input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
<p>評論內容</p>
<textarea name="" id="comment_content" cols="60" rows="10"></textarea> {# textarea是一個內聯標簽 #}
<p><button class="btn btn-default comment_btn">提交評論</button></p>
</div>
1)利用bootstrap的列表組組件來構建文章列表樣式。
2)修飾評論內容樣式article_detail.css:
.comment_con {
margin-top: 10px;
}
(3)顯示效果

2、Ajax顯示根評論
(1)更新comment視圖函數,准備返回給ajax回調函數處理的數據
def comment(request):
print(request.POST)
article_id = request.POST.get("article_id")
pid = request.POST.get("pid")
content = request.POST.get("content")
user_id = request.user.pk
# 在數據庫生成一條評論對象 父評論為空是根評論,不為空則是子評論
comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
response = {}
# create_time是一個datetime.datetime對象,在json序列化時不能對對象進行json序列化,必須進行strftime的轉換
response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
# 評論人
response["username"] = request.user.username
# 內容
response["content"] = content
return JsonResponse(response)
(2)回調函數處理數據,顯示新的評論
// 評論請求
$(".comment_btn").click(function () {
var content = $('#comment_content').val(); // 拿到評論框的內容
var pid = ""; // 父評論默認為空
$.ajax({
url: "/comment/",
type: "post",
data: {
'csrfmiddlewaretoken': $("[name= 'csrfmiddlewaretoken']").val(),
'article_id': "{{ article_obj.pk }}",
'content': content,
'pid': pid,
},
success: function (data) {
console.log(data);
// 獲取視圖函數返回的數據
var create_time = data.create_time;
var username = data.username;
var content = data.content;
// ES6特性:字符串模板。
// ES6中允許使用反引號 ` 來創建字符串,此種方法創建的字符串里面可以包含由美元符號加花括號包裹的變量${vraible}。
var s = `
<li class="list-group-item">
<div>
<span>${create_time}</span>
<a href=""><span><${username}/span></a>
<a href="" class="pull-right">回復</a>
</div>
<div class="comment_con">
{# 評論內容 #}
<p>${content}</p>
</div>
</li>`;
// DOM操作把標簽字符串整個放入ul的標簽中去
$("ul.comment_list").append(s);
// 提交后清空評論框
$("#comment_content").val("");
}
})
})
注意:1)ES6中允許使用反引號 ` 來創建字符串,此種方法創建的字符串里面可以包含由美元符號加花括號包裹的變量${vraible}。
//產生一個隨機數
var num=Math.random();
//將這個數字輸出到console
console.log(`your num is ${num}`);
2)獲取視圖函數返回的數據:
// 獲取視圖函數返回的數據 var create_time = data.create_time; var username = data.username; var content = data.content;
(3)顯示效果如下:

五、提交子評論
通過點擊評論后的回復按鈕來提交子評論,點擊回復按鈕事件:光標挪到輸出框下;輸出框顯示 @用戶名。
1、修改article_detail.html文章評論列表回復標簽
{# 文章評論列表 #}
<div class="comments">
<p>評論列表</p>
<ul class="list-group comment_list">
{% for comment in comment_list %}
<li class="list-group-item">
<div>
<a href=""># {{ forloop.counter }}樓</a>
<span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>
<a href=""><span><{{ comment.user.username }}/span></a>
<a class="pull-right reply_btn" username="{{ comment.user.username }}">回復</a>
</div>
<div class="comment_con">
{# 評論內容 #}
<p>{{ comment.content }}</p>
</div>
</li>
{% endfor %}
</ul>
<p>發表評論</p>
<p>昵稱: <input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
value="{{ request.user.username }}"></p>
<p>評論內容</p>
<textarea name="" id="comment_content" cols="60" rows="10"></textarea> {# textarea是一個內聯標簽 #}
<p>
<button class="btn btn-default comment_btn">提交評論</button>
</p>
</div>
給<a class="pull-right reply_btn" username="{{ comment.user.username }}">回復</a>,添加了屬性username,拿到當前行評論的用戶。
注意:回復這個a標簽不能添加href=""屬性。否則在觸發回復事件后,會刷新當前頁面。
2、在article_detail.html中編輯點擊回復按鈕事件
// 回復按鈕事件
$(".reply_btn").click(function () {
$('#comment_content').focus(); // 獲取焦點
// 拿到對應的父評論的用戶名
var val = "@" + $(this).attr("username")+"\n";
// 給輸入框賦值
$('#comment_content').val(val);
});
顯示效果如下所示:

3、提交子評論
article_detail.html做如下處理
<script>
// 點贊請求
...
var pid = ""; // 父評論默認為空
// 評論請求
$(".comment_btn").click(function () {
var content = $('#comment_content').val(); // 拿到評論框的內容
if (pid) {
// pid有值,是子評論
// 處理拿到子評論值方法一:
var index = content.indexOf("\n"); // 拿到換行符索引值
content = content.slice(index+1); // 切片處理,從index+1一直取到最后
}
$.ajax({
url: "/comment/",
type: "post",
data: {
'csrfmiddlewaretoken': $("[name= 'csrfmiddlewaretoken']").val(),
'article_id': "{{ article_obj.pk }}",
'content': content,
'pid': pid,
},
success: function (data) {
console.log(data);
// 獲取視圖函數返回的數據
var create_time = data.create_time;
var username = data.username;
var content = data.content;
// ES6特性:字符串模板。
// ES6中允許使用反引號 ` 來創建字符串,此種方法創建的字符串里面可以包含由美元符號加花括號包裹的變量${vraible}。
var s = `
<li class="list-group-item">
<div>
<span>${create_time}</span>
<a href=""><span><${username}/span></a>
<a href="" class="pull-right">回復</a>
</div>
<div class="comment_con">
{# 評論內容 #}
<p>${content}</p>
</div>
</li>`;
// DOM操作把標簽字符串整個放入ul的標簽中去
$("ul.comment_list").append(s);
// 提交后清空評論框
$("#comment_content").val("");
// pid重新賦值
pid = "";
}
})
});
// 回復按鈕事件
$(".reply_btn").click(function () {
$('#comment_content').focus(); // 獲取焦點
// 拿到對應的父評論的用戶名
var val = "@" + $(this).attr("username")+"\n";
// 給輸入框賦值
$('#comment_content').val(val);
// 拿到父評論的主鍵值
pid = $(this).attr("comment_pk");
});
</script>
注意:
(1)將var pid=""; 改為全局變量,拿到事件外。根據pid是否有值,處理評論框內容:
pid沒有值的時候:
var content = $('#comment_content').val();
pid有值的時候:
if (pid) {
// pid有值,是子評論
// 處理拿到子評論值方法一:
var index = content.indexOf("\n"); // 拿到換行符索引值
content = content.slice(index+1); // 切片處理,從index+1一直取到最后
}
(2)給回復按鈕這個a標簽添加一個新的自定義屬性:comment_pk。
<a class="pull-right reply_btn" username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}">回復</a>
點擊回復按鈕拿到父評論的主鍵值
// 拿到父評論的主鍵值
pid = $(this).attr("comment_pk");
(3)每次提交評論后,都要清空pid。這樣發布子評論后,不刷新頁面緊接着又發布評論,這個新評論就不會有父評論了,
(4)查看數據庫中的blog_comment表,子評論的父評論id:

六、顯示子評論
1、render顯示子評論
{# 文章評論列表 #}
<div class="comments">
<p>評論列表</p>
<ul class="list-group comment_list">
{% for comment in comment_list %}
<li class="list-group-item">
<div>
<a href=""># {{ forloop.counter }}樓</a>
<span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>
<a href=""><span><{{ comment.user.username }}/span></a>
<a class="pull-right reply_btn" username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}">回復</a>
</div>
{% if comment.parent_comment_id %}
<div class="pid_info well">
<p>
{# 拿到父評論對象評論人和評論內容 #}
{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}
</p>
</div>
{% endif %}
<div class="comment_con">
{# 評論內容 #}
<p>{{ comment.content }}</p>
</div>
</li>
{% endfor %}
</ul>
<p>發表評論</p>
<p>昵稱: <input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
value="{{ request.user.username }}"></p>
<p>評論內容</p>
<textarea name="" id="comment_content" cols="60" rows="10"></textarea> {# textarea是一個內聯標簽 #}
<p>
<button class="btn btn-default comment_btn">提交評論</button>
</p>
</div>
(1)在模板中判斷評論對象是否有父評論,如果有顯示父評論人和評論對象:
{% if comment.parent_comment_id %}
<div class="pid_info well">
<p>
{# 拿到父評論對象評論人和評論內容 #}
{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}
</p>
</div>
{% endif %}
(2)bootstrap的well用在元素上,就能有嵌入(inset)的簡單效果。
(3)顯示效果:

2、Ajax顯示子評論
(1)改寫article.detail.html評論請求事件回調函數事件處理:
var pid = ""; // 父評論默認為空
// 評論請求
$(".comment_btn").click(function () {
var content = $('#comment_content').val(); // 拿到評論框的內容
if (pid) {
// pid有值,是子評論
// 處理拿到子評論值方法一:
var index = content.indexOf("\n"); // 拿到換行符索引值
content = content.slice(index + 1); // 切片處理,從index+1一直取到最后
}
$.ajax({
url: "/comment/",
type: "post",
data: {
'csrfmiddlewaretoken': $("[name= 'csrfmiddlewaretoken']").val(),
'article_id': "{{ article_obj.pk }}",
'content': content,
'pid': pid,
},
success: function (data) {
console.log(data);
// 獲取視圖函數返回的數據
var create_time = data.create_time;
var username = data.username;
var content = data.content;
var parent_username = data.parent_username;
var parent_content = data.parent_content;
if (pid) {
// ES6特性:字符串模板。
// ES6中允許使用反引號 ` 來創建字符串,此種方法創建的字符串里面可以包含由美元符號加花括號包裹的變量${vraible}。
var s = `
<li class="list-group-item">
<div>
<span>${create_time}</span>
<a href=""><span><${username}/span></a>
<a href="" class="pull-right">回復</a>
</div>
<div class="pid_info well">
<p>
{# 拿到父評論對象評論人和評論內容 #}
${ parent_username }: ${ parent_content }
</p>
</div>
<div class="comment_con">
{# 評論內容 #}
<p>${content}</p>
</div>
</li>`;
} else {
var s = `
<li class="list-group-item">
<div>
<span>${create_time}</span>
<a href=""><span><${username}/span></a>
<a href="" class="pull-right">回復</a>
</div>
<div class="comment_con">
{# 評論內容 #}
<p>${content}</p>
</div>
</li>`;
}
// DOM操作把標簽字符串整個放入ul的標簽中去
$("ul.comment_list").append(s);
// 提交后清空評論框
$("#comment_content").val("");
// pid重新賦值
pid = "";
}
})
});
注意:根據pid是否有值對var s的標簽字符串構建不同的結構和樣式。也就是針對根評論和子評論構建不同的結構。由於構建子評論需要顯示父評論的用戶名和評論內容。因此需要在視圖函數中返回響應的數據。
(2)在comment視圖函數中添加父評論對象用戶名和評論內容
def comment(request):
print(request.POST)
article_id = request.POST.get("article_id")
pid = request.POST.get("pid")
content = request.POST.get("content")
user_id = request.user.pk
# 在數據庫生成一條評論對象 父評論為空是根評論,不為空則是子評論
comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
response = {}
# create_time是一個datetime.datetime對象,在json序列化時不能對對象進行json序列化,必須進行strftime的轉換
response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
# 評論人
response["username"] = request.user.username
# 內容
response["content"] = content
# 父評論對象評論人和評論內容
response["parent_username"] = comment_obj.parent_comment.user.username
response["parent_content"] = comment_obj.parent_comment.content
return JsonResponse(response)
(3)顯示效果:

七、評論樹
前面都是用的評論樓來展示評論列表,但是這種方式結構不夠清晰。因此也需要學會用評論樹來展示評論列表。
1、評論樹的請求數據
(1)構建article_detail.html中評論樹標簽和點擊事件
{# 文章評論列表 #}
<div class="comments list-group">
<p class="tree_btn">評論樹</p>
<div class="comment_tree">
</div>
<script>
$(".tree_btn").click(function () {
$.ajax({
url: "/get_comment_tree/",
type: "get",
data: {
article_id: "{{ article_obj.pk }}",
},
success: function (data) {
console.log(data);
}
})
})
</script>
<p>評論列表</p>
...
</div>
(2)根據ajax的請求url建立對應的url
urlpatterns = [
path('admin/', admin.site.urls),
...
path('comment/', views.comment), # 評論
path("get_comment_tree/", views.get_comment_tree), # 評論樹
...
]
(3)建立對應的視圖函數get_comment_tree
def get_comment_tree(request):
article_id = request.GET.get("article_id")
# 過濾出文章對應的評論,挑出主鍵值、評論內容、父評論id,拿到的是一個queryset,結構類似一個列表里面裝着一個個字典
# 但是queryset並不是一個列表,可以用list()函數將其轉換為列表
ret = list(models.Comment.objects.filter(article_id=article_id).values("pk", "content", "parent_comment_id"))
# JsonResponse對非字典的數據進行序列化,必須設置一個參數safe=False
return JsonResponse(ret, safe=False)
注意:QuerySet雖然結構類似一個列表里面裝着一個個字典但並不是一個列表。可以用list()函數轉換為列表。
其次一般JsonResponse都是用來對字典進行序列化,如果要對一個非字典的數據序列化,必須設置一個參數safe=False,否則會報錯。
(4)訪問頁面點擊頁面中評論樹時,頁面控制台輸出了一條數組數據:

2、展開評論樹
{# 文章評論列表 #}
<div class="comments list-group">
<p class="tree_btn">評論樹</p>
<div class="comment_tree">
</div>
<script>
$(".tree_btn").click(function () {
$.ajax({
url: "/get_comment_tree/",
type: "get",
data: {
article_id: "{{ article_obj.pk }}",
},
success: function (data) {
console.log(data); // data是一個列表,列表中包含一個個字典
$.each(data, function (index, comment_object) {
var pk = comment_object.pk;
var content = comment_object.content;
var parent_comment_id = comment_object.parent_comment_id;
var s = '<div class="comment_item" comment_id='+pk+' style="margin-left: 20px"><span>'+content+'</span></div>';
// 判斷評論是根評論還是子評論
if (!parent_comment_id) { // 感嘆號取反
// 根評論
$(".comment_tree").append(s);
} else {
// 子評論
// 放入父評論的div標簽
// 屬性選擇器,找到comment_id屬性值對應的div
$("[comment_id="+parent_comment_id+"]").append(s);
}
})
}
})
})
</script>
...
</div>
注意:
(1)變量s是將要插入模板中的標簽字符串,標簽字符串內定義的評論的內容。
<div class="comment_item" comment_id='pk' style="margin-left: 20px">
<span>評論內容</span>
</div>
每個標簽字符串都定義了comment_id,對應評論對象的主鍵值。定義樣式,則是為了子評論相對父評論向左移動20像素,樣式得到錯開。
(2)在ajax回調函數循環get_comment_tree視圖函數返回列表
success: function (data) {
console.log(data); // data是一個列表,列表中包含一個個字典
$.each(data, function (index, comment_object) {
var pk = comment_object.pk;
var content = comment_object.content;
var parent_comment_id = comment_object.parent_comment_id;
...
})
}
(3)如果判斷是子評論的話,需要利用屬性選擇器,找到屬性comment_id=父評論的id,這樣篩選到對應的評論將標簽字符串放如對應的div中。
var s = '<div class="comment_item" comment_id='+pk+' style="margin-left: 20px"><span>'+content+'</span></div>';
// 判斷評論是根評論還是子評論
if (!parent_comment_id) { // 感嘆號取反
// 根評論
$(".comment_tree").append(s);
} else {
// 子評論
// 放入父評論的div標簽
// 屬性選擇器,找到comment_id屬性值對應的div
$("[comment_id="+parent_comment_id+"]").append(s);
}
(4)顯示效果:

3、評論樹回顧和優化
(1)在視圖中獲取的評論列表,會不會有子評論在前,父評論在后,導致在回調函數中循環列表時,發現有子評論無法找到父評論無法插入?
答:這個問題是不會發生的,因為評論生成是按主鍵進行排序的,根評論一定在前,與它關聯的子評論一定在后。
為了保險在獲取評論對象時還是order_by做一下排序:
def get_comment_tree(request):
article_id = request.GET.get("article_id")
ret = list(models.Comment.objects.filter(article_id=article_id).order_by("pk").values("pk", "content", "parent_comment_id"))
return JsonResponse(ret, safe=False)
(2)不再通過點擊評論樹標簽,觸發事件來顯示評論樹。直接訪問文章頁面就顯示評論樹?
答:不再把ajax事件內嵌在click事件中,瀏覽器加載標簽字符串時就會直接執行ajax,立刻發新的請求拿到響應結果,由於速度非常快,立刻就將評論樹顯示在頁面上。
<script>
// $(".tree_btn").click(function () {
$.ajax({
url: "/get_comment_tree/",
type: "get",
data: {
article_id: "{{ article_obj.pk }}",
},
success: function (data) {
console.log(data); // data是一個列表,列表中包含一個個字典
$.each(data, function (index, comment_object) {
var pk = comment_object.pk;
var content = comment_object.content;
var parent_comment_id = comment_object.parent_comment_id;
var s = '<div class="comment_item" comment_id='+pk+' style="margin-left: 20px"><span>'+content+'</span></div>';
// 判斷評論是根評論還是子評論
if (!parent_comment_id) { // 感嘆號取反
// 根評論
$(".comment_tree").append(s);
} else {
// 子評論
// 放入父評論的div標簽
// 屬性選擇器,找到comment_id屬性值對應的div
$("[comment_id="+parent_comment_id+"]").append(s);
}
})
}
})
// })
</script>
八、事務操作
比如在數據庫生成一條評論對象,同時將文章的評論數進行更新。這兩步操作需要設計為同進同退,即如果有一步沒完成,完成的操作需要在數據庫回退。
def comment(request):
print(request.POST)
article_id = request.POST.get("article_id")
pid = request.POST.get("pid")
content = request.POST.get("content")
user_id = request.user.pk
# 事務操作:生成記錄和評論數更新同進同退
with transaction.atomic():
# 在數據庫生成一條評論對象 父評論為空是根評論,不為空則是子評論
comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
# 文章評論數更新
models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)
response = {}
# create_time是一個datetime.datetime對象,在json序列化時不能對對象進行json序列化,必須進行strftime的轉換
response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
# 評論人
response["username"] = request.user.username
# 內容
response["content"] = content
# 父評論對象評論人和評論內容
response["parent_username"] = comment_obj.parent_comment.user.username
response["parent_content"] = comment_obj.parent_comment.content
return JsonResponse(response)
1、引入以下模塊來實現事務操作:
from django.db import transaction
2、將幾個操作作為一個事務的方法:
# 事務操作:生成記錄和評論數更新同進同退
with transaction.atomic():
# 在數據庫生成一條評論對象 父評論為空是根評論,不為空則是子評論
comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
# 文章評論數更新
models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)
九、發送郵件
文章被評論了,給作者發一份站內信通知他多了一條評論。這里使用django提供的模塊來實現發送郵件:
from django.core.mail import send_mail
1、send_mail函數的源碼分析
def send_mail(subject, message, from_email, recipient_list,
fail_silently=False, auth_user=None, auth_password=None,
connection=None, html_message=None):
"""
:param subject: 標題
:param message: 郵件內容
:param from_email: 發件郵箱
:param recipient_list: 收件郵箱
:param fail_silently:
:param auth_user:
:param auth_password:
:param connection:
:param html_message:
:return:
"""
connection = connection or get_connection(
username=auth_user,
password=auth_password,
fail_silently=fail_silently,
)
mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, connection=connection)
if html_message:
mail.attach_alternative(html_message, 'text/html')
return mail.send()
2、settings配置官方郵箱
# 郵箱配置 EMAIL_HOST = 'smtp.163.com' # 如果是 qq 改成 smtp.exmail.qq.com EMAIL_PORT = 465 # qqs是465 EMAIL_HOST_USER = 'xxx@163.com' # 帳號 EMAIL_HOST_PASSWORD = 'xxxxxx' # 密碼(授權碼) # DEFAULT_FROM_EMAIL = EMAIL_HOST_USER # 默認使用當前配置的user EMAIL_USE_SSL = True # 是否使用SSL證書, 網易郵箱關閉SSL后SMTP應該為25
3、在comment視圖中配置郵件發送
def comment(request):
...
# 為了發送郵件拿到文章對象
article_obj = models.Article.objects.filter(pk=article_id).first()
...
# 發送郵件
from django.core.mail import send_mail
from cnblog import settings
send_mail(
"您的文章%s新增了一條評論內容" % article_obj.title,
content,
settings.EMAIL_HOST_USER, # 發送方
["44xxxx@qq.com"] # 接收方
)
...
接收的郵箱,應該是用戶注冊時填寫的郵箱信息。
評論后,我的qq郵箱收到郵件:

4、多線程解決郵件發送網絡延遲引起的網頁卡頓
點擊評論會發現,頁面需要卡很久,嚴重影響了用戶體驗。
# 發送郵件
from django.core.mail import send_mail
from cnblog import settings
# send_mail(
# "您的文章%s新增了一條評論內容" % article_obj.title,
# content,
# settings.EMAIL_HOST_USER, # 發送方
# ["44xxxx@qq.com"] # 接收方
# )
import threading
t = threading.Thread(target=send_mail, args=(
"您的文章%s新增了一條評論內容" % article_obj.title,
content,
settings.EMAIL_HOST_USER, # 發送方
["443xxxx@qq.com"] # 接收方
))
t.start()
這樣用一個線程去跑send_email,就不會影響響應結果反饋了。
5、改完后完整的comment視圖函數如下所示
def comment(request): print(request.POST) article_id = request.POST.get("article_id") pid = request.POST.get("pid") content = request.POST.get("content") user_id = request.user.pk # 為了發送郵件拿到文章對象 article_obj = models.Article.objects.filter(pk=article_id).first() # 事務操作:生成記錄和評論數更新同進同退 with transaction.atomic(): # 在數據庫生成一條評論對象 父評論為空是根評論,不為空則是子評論 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 文章評論數更新 models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1) response = {} # create_time是一個datetime.datetime對象,在json序列化時不能對對象進行json序列化,必須進行strftime的轉換 response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X") # 評論人 response["username"] = request.user.username # 內容 response["content"] = content # 發送郵件 from django.core.mail import send_mail from cnblog import settings # send_mail( # "您的文章%s新增了一條評論內容" % article_obj.title, # content, # settings.EMAIL_HOST_USER, # 發送方 # ["443614404@qq.com"] # 接收方 # ) import threading t = threading.Thread(target=send_mail, args=( "您的文章%s新增了一條評論內容" % article_obj.title, content, settings.EMAIL_HOST_USER, # 發送方 ["443614404@qq.com"] # 接收方 )) t.start() # 父評論對象評論人和評論內容 response["parent_username"] = comment_obj.parent_comment.user.username response["parent_content"] = comment_obj.parent_comment.content return JsonResponse(response)
