文章目錄
一、前言
博客中的評論系統其實是個很複雜的東西,但是網上已經有現成的輪子了,比如django-contrib-comments,可以直接拿過來用。咱們的博客主頁是抓取別人的,目的是使用 Django 還原實現,達到快速藉助 Wordpress 主題,使用 Djano 框架建站。這樣你就可以找一個自己喜歡的前端模板,快速搭建自己的小天地了。那麼這裏就不便使用輪子,這裏會根據 崔慶才 博客主頁評論功能自己設計後臺邏輯。
二、創建評論應用
參考
Django3.0+Python3.8+MySQL8.0 個人博客搭建四|創建第一個APP
創建 comment
評論應用,放入 apps 應用管理文件中,不要忘記在 settings.py
中註冊新添加的 app 應用。
(fswy) blog xiatian$ django-admin startapp comment
下邊編寫老四樣:
- models
- view
- templatetags
- urls.py
三、添加評論模型
這個評論其實是一個很複雜的功能,但是考慮到博客主要是展示自己學習、生活、成長的地方,並不是論壇,所以就沒有考慮很多東西,評論搞的就很簡陋。
數據分析
首先根據頁面分析一下需要什麼表
文章評論、給我留言、關於自己頁面都有評論功能,而且崔慶才個人博客的評論功能,還支持遊民評論
需要一個遊民表(CommentUser
)
需要一個評論信息表 (Comment
)
有三個頁面有評論功能看,在這裏我打算把三個評論區分別建表存儲
- 文章評論(ArticleComment)
- 關於自己(AboutComment)
- 給我留言(MessageComment)
blog -> comment -> models.py
from django.db import models
from django.conf import settings
from apps.fswy.models import Article
import markdown
import emoji
# Create your models here.
# 遊民評論者信息表
class CommentUser(models.Model):
nickname = models.CharField(max_length=20, verbose_name='暱稱')
email = models.CharField(max_length=30, verbose_name='郵箱')
address = models.CharField(max_length=200, verbose_name='地址')
# 評論信息表
class Comment(models.Model):
author = models.ForeignKey(CommentUser,
related_name='%(class)s_related',
verbose_name='評論人',
on_delete=models.CASCADE)
create_date = models.DateTimeField('創建時間', auto_now_add=True)
content = models.TextField('評論內容')
parent = models.ForeignKey('self',
related_name ='%(class)s_child_comments',
verbose_name='父評論',
blank=True,
null=True,
on_delete=models.CASCADE)
rep_to = models.ForeignKey('self',
verbose_name='回覆',
related_name='%(class)s_rep_comments',
blank=True,
null=True,
on_delete=models.CASCADE)
class Meta:
'''這是一個元類,用來繼承的'''
abstract = True
def __str__(self):
return self.content[:20]
def content_to_markdown(self):
# 先轉換成emoji然後轉換成markdown,'escape':所有原始HTML將被轉義幷包含在文檔中
to_emoji_content = emoji.emojize(self.content, use_aliases=True)
to_md = markdown.markdown(to_emoji_content,
safe_mode='escape',
extension=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
])
return to_md
# 文章評論區,據繼承評論信息表
class ArticleComment(Comment):
# 記錄評論屬於哪篇文章
belong = models.ForeignKey(Article,
related_name='article_comments',
verbose_name='所屬文章',
on_delete=models.CASCADE)
class Meta:
verbose_name = '文章評論'
verbose_name_plural = verbose_name
ordering = ['create_date']
# 關於自己頁面評論信息
class AboutComment(Comment):
class Meta:
verbose_name = '關於自己評論'
verbose_name_plural = verbose_name
ordering = ['create_date']
# 給我留言頁面評論信息
class MessageComment(Comment):
class Meta:
verbose_name = '給我留言'
verbose_name_plural = verbose_name
ordering = ['create_date']
這裏評論信息表其實可以只創建一個表即可,但是這裏分開創建,有兩個目的:
- 方便後期擴展
- 瞭解模型內繼承的使用
參考
Django3.0+Python3.8+MySQL8.0 個人博客搭建六|數據庫結構設計
數據表信息變動
每當數據表信息變動時
在終端執行:
(fswy) blog xiatian$ python3 manage.py makemigrations
(fswy) blog xiatian$ python3 manage.py migrate
(fswy) blog xiatian$ python3 manage.py makemigrations
Migrations for 'comment':
apps/comment/migrations/0001_initial.py
- Create model CommentUser
- Create model MessageComment
- Create model ArticleComment
- Create model AboutComment
(fswy) blog xiatian$ python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, comment, contenttypes, django_comments, fswy, sessions, sites, user
Running migrations:
Applying comment.0001_initial... OK
Applying sites.0001_initial... OK
Applying django_comments.0001_initial... OK
Applying django_comments.0002_update_user_email_field_length... OK
Applying django_comments.0003_add_submit_date_index... OK
Applying sites.0002_alter_domain_unique... OK
再在PyCharm中打開Database可以看到:
四、分析評論區信息
因爲後臺邏輯,其實就是對用戶需求進行處理反饋的東西,那麼首先咱們的頁面已經有了,就是 崔慶才 個人博客,所以需要分析一下 崔慶才 個人博客評論區設麼怎麼個功能,這裏怎麼分析呢?
首先進入 抓取到的 崔慶才 博客的前端源碼,結合前端 HTML 標籤對傳遞的數據進行分析
static -> js -> jquery.js
a.ajax({
url:_deel.url+"/ajax/comment.php",
data:a(this).serialize(),type:a(this).attr("method"),
error:function(w) {
a(".comt-loading").hide();
a(".comt-error").show().html(w.responseText);
setTimeout(function(){$submit.attr("disabled",false).fadeTo("slow",1);
a(".comt-error").fadeOut()},3000)},
success:function(B){
a(".comt-loading").hide();
r.push(a("#comment").val());
a("textarea").each(function(){this.value=""});
var y=addComment,A=y.I("cancel-comment-reply-link"),w=y.I("wp-temp-form-div"),C=y.I(y.respondId),x=y.I("comment_post_ID").value,z=y.I("comment_parent").value;
if(!o&&$comments.length){
n=parseInt($comments.text().match(/\d+/));
$comments.text($comments.text().replace(n,n+1))
}
new_htm='" id="new_comm_'+k+'"></';
new_htm=(z=="0")?('\n<ol style="clear:both;' + '" class="commentlist commentnew'+new_htm+"ol>"):('\n<ul class="children'+new_htm+"ul>");
ok_htm='\n<span id="success_'+k+b;ok_htm+="</span><span></span>\n";
if(z=="0"){
if(a("#postcomments .commentlist").length){a("#postcomments .commentlist").before(new_htm)
}else {
a("#respond").after(new_htm)
}
}else{
a("#respond").after(new_htm)}a("#comment-author-info").slideUp();
console.log(a("#new_comm_"+k));a("#new_comm_"+k).hide().append(B);
a("#new_comm_"+k+" li").append(ok_htm);
a("#new_comm_"+k).fadeIn(4000);
$body.animate({scrollTop:a("#new_comm_"+k).offset().top-200},500);
a(".comt-avatar .avatar").attr("src",a(".commentnew .avatar:last").attr("src"));
l();
k++;
o="";
a("*").remove("#edit_id");A.style.display="none";
A.onclick=null;
y.I("comment_parent").value="0";
if(w&&C){
w.parentNode.insertBefore(C,w);
w.parentNode.removeChild(w)
}
}
});
不要被 JS 源碼給嚇到,掌握了JS套路,局部分析,經過前端標籤屬性比如comment_parent、respond等屬性值在這個文件內查找,定位到以上 AJAX 代碼塊
經過調試分析這段 AJAX 代碼給後臺傳遞了這些數據
w:評論內容
comment_post_ID:評論所屬頁面,給我留言、文章留言、關於自己頁面
author:評論者
email:評論者郵箱
url:評論者網址
其實前端都是已經寫好的只需要把
url:_deel.url+"/ajax/comment.php",
改爲:
url: "/comment/add/",
但是由於,這裏我多加了一個判斷登錄用戶和遊民的功能
success: function(B) {
console.log('success');
a(".comt-loading").hide();
r.push(a("#comment").val());
a("textarea").each(function() {
this.value = ""
});
//在這裏添加如下代碼
//獲取seeion信息,判斷用戶是否登錄
var user_id = "<%=session.getAttribute('uid','')%>";
var nick = $('#author').val();
if (user_id != ''){ //如果用戶處於登錄狀態,展示用戶ID
$('#nick').html(nick);
}
else { //如果沒有登錄,證明是遊民,遊民可以換馬甲
$('#nick').html(nick + ' <a class="switch-author" href="javascript:;" data-type="switch-author" style="font-size:12px;">換個身份</a>');
}
完整源碼:Github
五、編寫視圖函數
blog -> comment -> view.py
from django.shortcuts import render
from apps.fswy.models import Article
from .models import ArticleComment, CommentUser, AboutComment, MessageComment
from django.conf import settings
from django.http import HttpResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
# Create your views here.
import re
# 獲取用戶模型
user_model = settings.AUTH_USER_MODEL
# 確定重複是否重複
def confirm(new_content, comment_post_ID, auser):
if comment_post_ID == 'about':
res = AboutComment.objects.filter(content=new_content, author=auser)
elif comment_post_ID == 'message':
res = MessageComment.objects.filter(content=new_content, author=auser)
else:
res = ArticleComment.objects.filter(content=new_content, author=auser, belong_id=comment_post_ID)
if res:
return False
else:
return True
# @login_required
@csrf_exempt
@require_POST
def AddcommentView(request):
if request.is_ajax():
data = request.POST
# 評論內容哦你
new_content = data.get('w')
# 評論對象,指的是頁面留言、文章、等
comment_post_ID = data.get('comment_post_ID')
# 評論者
author = data.get('author', '')
# 評論者郵箱
email = data.get('email', '')
# 評論者網址
url = data.get('url', '')
"""
驗證信息格式
"""
if not re.match('^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$', email):
return HttpResponse('請輸入有效的郵箱格式!', content_type='text/html;charset="utf-8"', status=405)
if not new_content:
return HttpResponse('請寫點什麼吧!', content_type='text/html;charset="utf-8"', status=405)
if not author or not email:
return HttpResponse('請填寫郵箱和暱稱', content_type='text/html;charset="utf-8"', status=405)
# 存儲評論者信息
CommentUser.objects.get_or_create(nickname=author, email=email, address=url)
# 評論對象,父級對象,就是評論的是誰
comment_parent = data.get('comment_parent')
# 獲取用戶信息
auser = CommentUser.objects.get(nickname=author, email=email, address=url)
if not confirm(new_content, comment_post_ID, auser):
return HttpResponse('請勿發表重複內容!', content_type='text/html;charset="utf-8"', status=405)
"""
存儲評論信息
"""
# 關於自己頁面評論
if comment_post_ID == 'about':
# 父級評論
if comment_parent == '0':
new_comment = AboutComment(author=auser, content=new_content, parent=None, rep_to=None)
# 評論他人評論
else:
parent = AboutComment.objects.get(id=comment_parent)
new_comment = AboutComment(author=auser, content=new_content, parent=parent, rep_to=None)
new_comment.save()
# 給我留言頁面評論
elif comment_post_ID == 'message':
if comment_parent == '0':
new_comment = MessageComment(MessageComment, auser, new_content)
else:
parent = MessageComment.objects.get(id=comment_parent)
new_comment = MessageComment(author=auser, content=new_content, parent=parent, rep_to=None)
new_comment.save()
# 文章評論
else:
the_article = Article.objects.get(id=comment_post_ID)
if comment_parent == '0':
new_comment = ArticleComment(author=auser, content=new_content, belong=the_article, parent=None, rep_to=None)
else:
parent = ArticleComment.objects.get(id=comment_parent)
new_comment = ArticleComment(author=auser, content=new_content, belong=the_article, parent=parent, rep_to=None)
new_comment.save()
# 獲取用什麼,分登陸身份和遊民身份
request.session['nick'] = new_comment.author.nickname
request.session['tid'] = new_comment.author.id
# 返回當前評論,直接返回HTML內容剛給前端,使用JS在指定位置進行數據展示
return HttpResponse('''<li class="" id="comment-"><div class="c-avatar"><img alt='' src='https://cuiqingcai.com/avatar/.png' class='avatar avatar-54 photo avatar-default' height='54' width='54' /><div class="c-main" id="div-comment-">{0}<div class="c-meta"><span class="c-author">{1}</span></div></div></div>'''.format(new_content, author), content_type='text/html;charset="utf-8"')
return HttpResponse('參數錯誤', content_type='text/html;charset="utf-8"')
六、配置路由
blog -> blog -> urls.py
一級路由
# 評論欄
path('comment/', include(('apps.comment.urls','apps.comment'), namespace='comment')),
二級路由
blog -> comment -> urls.py
from django.urls import path
from .views import AddcommentView
urlpatterns = [
path(r'add/', AddcommentView, name='add_comment'),
]
七、編寫自定義模板標籤
先在comment
中新建templatetags
目錄,再新建comment_tags.py
文件
blog -> comment -> templatetags -> comment_tags.py
# 創建了新的tags標籤文件後必須重啓服務器
from django.utils.safestring import mark_safe
from django import template
from ..models import ArticleComment,AboutComment, MessageComment
register = template.Library()
@register.simple_tag
def get_comment_count(category, entry=0):
"""獲取一個文章的評論總數"""
if category == 'about':
lis = AboutComment.objects.all()
elif category == 'message':
lis = MessageComment.objects.all()
else:
lis = ArticleComment.objects.filter(belong_id=entry)
return lis.count()
@register.simple_tag
def get_parent_comments(category, entry=0):
"""獲取一個文章的父評論列表"""
if category == 'about':
lis = AboutComment.objects.filter(parent=None)
elif category == 'message':
lis = MessageComment.objects.filter(parent=None)
else:
lis = ArticleComment.objects.filter(belong_id=entry,parent=None)
return lis
@register.simple_tag
def get_child_comments(category,com):
"""獲取一個父評論的子評論列表"""
if category == 'about':
lis = AboutComment.objects.filter(parent=com)
elif category == 'message':
lis = MessageComment.objects.filter(parent=com)
else:
lis = ArticleComment.objects.filter(parent=com)
return lis
@register.simple_tag
def get_comment_user_count(category, entry=0):
"""獲取評論人總數"""
p = []
if category == 'about':
lis = AboutComment.objects.all()
elif category == 'message':
lis = MessageComment.objects.all()
else:
lis = ArticleComment.objects.filter(belong_id=entry)
for each in lis:
if each.author not in p:
p.append(each.author)
return len(p)
# 遞歸查找父評論
def find_father(dic, comment_obj):
# 對字典中的每一組元素進行循環操作
for k, v_dic in dic.items():
# 如果k等於comment_obj的父節點,那麼表示找到了父親。
if k == comment_obj.parent:
# 找到了父親,認祖歸宗,把自己歸位到父親下面,並給將來的兒子留個位置
dic[k][comment_obj] = {}
# 找到了父親,處理完畢,返回
else:
# 剛纔沒找到,剝一層,接着往下找。
find_father(dic[k], comment_obj)
# 遞歸生成HTML字符串,展示評論區內容
def generate_comment_html(sub_comment_dic, category, path, s=2):
html = "<ul class='children'>"
# 對傳入的字典進行循環操作
for k, v_dic in sub_comment_dic.items():
html += '''
<li class="comment odd alt depth-{0}" id="comment-{1}"><div class="c-avatar"><img alt='' data-original='https://cuiqingcai.com/avatar/ee89e6709c344980b7b82d1a13d496fb.png' class='avatar avatar-54 photo' height='54' width='54' /><div class="c-main" id="div-comment-{1}">{2}<div class="c-meta"><span class="c-author"><a href='http://fsfs' rel='external nofollow' class='url'>{3}</a></span>{4}<a rel='nofollow' class='comment-reply-link' href='{6}?replytocom={1}#respond' οnclick='return addComment.moveForm( "div-comment-{1}", "{1}", "respond", "{5}" )' aria-label='回覆給{3}'>回覆</a></div></div></div>'''.format(s,k.id,k.content,k.author.nickname,k.create_date.strftime('%Y-%m-%d %H:%M:%S'),category,path)
# 有可能v_dic中依然有元素, 遞歸繼續加
if v_dic:
s += 1
html += generate_comment_html(v_dic, category, path, s)
html += "</li>"
# 循環完成最後返回html
html += "</ul>"
return html
# 生成層級評論
@register.simple_tag
def build_comment_tree(category, path, entry=0):
if category == 'about':
comment_list = AboutComment.objects.all()
elif category == 'message':
comment_list = MessageComment.objects.all()
else:
comment_list = ArticleComment.objects.filter(belong_id=entry)
# 定義一個空字典用來保存轉換之後的結果
comment_dic = {}
# 對comment_list中的每個元素進行循環
for comment_obj in comment_list:
# 判斷comment_obj是否存在父節點。如果沒有,這把該評論作爲第一個節點
if comment_obj.parent is None:
comment_dic[comment_obj] = {}
else:
# 否則去找該對象的父節點。
find_father(comment_dic, comment_obj)
# 上面執行完畢,comment_dic中會有轉換好的結果
# 開始拼接html字符串
html = "<ol class='commentlist'>"
# 規定一個margin left,每次有遞歸的時候就往右縮進一點。
# 對comment_dic中的每一組元素進行操作
for k, v in comment_dic.items():
# 第一層html
html += '''<li class="comment even thread-even depth-1" id="comment-{0}"><div class="c-avatar"><img alt='' data-original='https://cuiqingcai.com/avatar/5e43cb2c27191170aaece6a30a9d49f4.png' class='avatar avatar-54 photo' height='54' width='54' /><div class="c-main" id="div-comment-{0}">{1}<div class="c-meta"><span class="c-author">{2}</span>{3}<a rel='nofollow' class='comment-reply-link' href='{5}?replytocom={0}#respond' οnclick='return addComment.moveForm( "div-comment-{0}", "{0}", "respond", "{4}" )' aria-label='回覆給{2}'>回覆</a></div></div></div>'''.format(k.id,k.content,k.author.nickname, k.create_date.strftime('%Y-%m-%d %H:%M:%S'), category, path)
# 通過遞歸把他的兒子加上
html += generate_comment_html(v, category, path)
# 最後把ul關上
html += " </ol>"
# 關掉轉義
return mark_safe(html)
八、編寫評論區 HTML
在blog -> templates
中新建comment_list.html
文件
blog -> templates -> comment_list.html
{% load static %}
{% load comment_tags oauth_tags %}
<div id="respond" class="no_webshot">
<form action="/comment/add" method="post" id="commentform">
<div class="comt-title">
{% if request.session.username|default:'' != '' %}
{% get_user_data request.session.uid as user %}
<div class="comt-avatar pull-left">
<img alt='' src='/media/{{ user.avatar }}' class='avatar avatar-54 photo avatar-default' height='54' width='54' /> </div>
<div class="comt-author pull-left" id="nick">
{{ request.session.username }}
</div>
{% endif %}
{% if request.session.nick|default:'' != '' %}
{% get_tourist_data request.session.tid as tourist %}
<div class="comt-avatar pull-left">
<img alt='' src='/media/avatar' class='avatar avatar-54 photo avatar-default' height='54' width='54' /> </div>
<div class="comt-author pull-left" id="nick">
{{ tourist.nickname }}
<a class="switch-author" href="javascript:;" data-type="switch-author" style="font-size:12px;">換個身份</a>
</div>
{% else %}
<div class="comt-author pull-left" id="nick">
</div>
{% endif %}
<a id="cancel-comment-reply-link" class="pull-right" href="javascript:;">取消評論</a>
</div>
<div class="comt">
<div class="comt-box">
<h3>發表我的評論</h3>
<textarea placeholder="寫點什麼..." class="input-block-level comt-area" name="w" id="comment" cols="100%" rows="3" tabindex="1" onkeydown="if(event.ctrlKey&&event.keyCode==13){document.getElementById('submit').click();return false};"></textarea><textarea name="comment" cols="100%" rows="4" style="display:none"></textarea>
<div class="comt-ctrl">
<button class="btn btn-primary pull-right" type="submit" name="submit" id="submit" tabindex="5"><i class="fa fa-check-square-o"></i> 提交評論</button>
<div class="comt-tips pull-right"><input type='hidden' name='comment_post_ID' value='{{ category }}' id='comment_post_ID' />
<input type='hidden' name='comment_parent' id='comment_parent' value='0' />
<p style="display: none;"><input type="hidden" id="akismet_comment_nonce" name="akismet_comment_nonce" value="da9dc5c77a" /></p><p style="display: none;"><input type="hidden" id="ak_js" name="ak_js" value="50"/></p></div>
<span data-type="comment-insert-smilie" class="muted comt-smilie"><i class="fa fa-smile-o"></i> 表情</span>
<span class="muted comt-mailme"><label for="comment_mail_notify" class="checkbox inline" style="padding-top:0"><input type="checkbox" name="comment_mail_notify" id="comment_mail_notify" value="comment_mail_notify" checked="checked"/>有人回覆時郵件通知我</label></span>
</div>
</div>
<div class="comt-comterinfo" id="comment-author-info" >
<h4>Hi,您需要填寫暱稱和郵箱!</h4>
<ul>
<li class="form-inline"><label class="hide" for="author">暱稱</label><input class="ipt" type="text" name="author" id="author" value="{% if user %}{{ user.username }}{% elif tourist %}{{ tourist.nickname }}{% endif %}" tabindex="2" placeholder="暱稱"><span class="help-inline">暱稱 (必填)</span></li>
<li class="form-inline"><label class="hide" for="email">郵箱</label><input class="ipt" type="text" name="email" id="email" value="{% if user %}{{ user.email }}{% elif tourist %}{{ tourist.email }}{% endif %}" tabindex="3" placeholder="郵箱"><span class="help-inline">郵箱 (必填)</span></li>
<li class="form-inline"><label class="hide" for="url">網址</label><input class="ipt" type="text" name="url" id="url" value="{% if user %}{{ user.link }}{% elif tourist %}{{ tourist.address }}{% endif %}" tabindex="4" placeholder="網址"><span class="help-inline">網址</span></li>
</ul>
</div>
</div>
</form>
</div>
<div id="postcomments">
{% if category == 'message'%}
{% get_comment_user_count category as user_num%}
{% build_comment_tree category request.path as htm %}
{% elif category == 'about' %}
{% get_comment_user_count category as user_num%}
{% build_comment_tree category request.path as htm %}
{% else %}
{% get_comment_user_count category category as user_num%}
{% build_comment_tree category request.path category as htm %}
{% endif %}
<div id="comments">
<i class="fa fa-comments-o"></i> <b> ({{ user_num }})</b>個小夥伴在吐槽
</div>
{{ htm }}
<div class="commentnav" >
</div>
</div>
九、添加評論功能
這裏以文章頁面爲例
blog -> templates -> article.html
把評論區添加到文章詳情頁底部
</div>
</div>
{% include 'comment_list.html' %}
</div>
</div>
{% endblock body %}
哪個頁面需要評論功能,就把下邊的代碼放在需要得而位置
{% include 'comment_list.html' %}
關於自己、給我留言也在相應位置添加此代碼即可
完整源碼:Github
十、效果圖
十一、總結
這裏我這裏寫的評論區功能其實很獨立,只要在需要添加評論功能的頁面需要的地方加入
{% include 'comment_list.html' %}
這個評論功能,純自己寫的,沒有寫太多功能,比如刪除評論、過濾垃圾用戶、修改評論、評論郵件提醒等。
教程目錄
Django3.0+Python3.8+MySQL8.0 個人博客搭建一|前言
Django3.0+Python3.8+MySQL8.0 個人博客搭建二|創建虛擬環境
Django3.0+Python3.8+MySQL8.0 個人博客搭建三|創建博客項目
Django3.0+Python3.8+MySQL8.0 個人博客搭建四|創建第一個APP
Django3.0+Python3.8+MySQL8.0 個人博客搭建五|makemigrations連接MySQL數據庫的坑
Django3.0+Python3.8+MySQL8.0 個人博客搭建六|數據庫結構設計
Django3.0+Python3.8+MySQL8.0 個人博客搭建七|makemigrations創建數據庫的坑(第二彈)
Django3.0+Python3.8+MySQL8.0 個人博客搭建八|通過admin管理後臺
Django3.0+Python3.8+MySQL8.0 個人博客搭建九|博客首頁開發(一)
Django3.0+Python3.8+MySQL8.0 個人博客搭建十|整理項目結構
Django3.0+Python3.8+MySQL8.0 個人博客搭建十一|博客首頁開發(二)
Django3.0+Python3.8+MySQL8.0 個人博客搭建十二|博客首頁開發(三)
Django3.0+Python3.8+MySQL8.0 個人博客搭建十三|博客詳情頁面
Django3.0+Python3.8+MySQL8.0 個人博客搭建十四|註冊登錄