本渣渣不专注技术,只专注使用技术,不是一个资深的coder,是一个不折不扣的copier |
1、前言
博客中的评论系统其实是个很复杂的东西,但是网上已经有现成的轮子了,比如django-contrib-comments,可以直接拿过来用。咱们的博客主页是抓取别人的,目的是使用 Django 还原实现,达到快速借助 Wordpress 主题,使用 Djano 框架建站。这样你就可以找一个自己喜欢的前端模板,快速搭建自己的小天地了。那么这里就不便使用轮子,这里会根据 崔庆才 博客主页评论功能自己设计后台逻辑。
2、创建评论应用
创建 comment 评论应用,放入 apps 应用管理文件中,不要忘记在 settings.py 中注册新添加的 app 应用
下边编写老四样:models、view、urls.py、templatetags
3、添加评论模型
这个评论其实是一个很复杂的功能,但是考虑到博客主要是展示自己学习、生活、成长的地方,并不是论坛,所以就没有考虑很多东西,评论搞的就很简陋
首先根据页面分析一下需要什么表
文章评论、给我留言、关于自己页面都有评论功能,而且崔庆才个人博客的评论功能,还支持游民评论
需要一个游民表(CommentUser)
需要一个评论信息表 (Comment)
有三个页面有评论功能看,在这里我打算把三个评论区分别建表存储
文章评论(ArticleComment)、关于自己(AboutComment)、给我留言(MessageComment)
blog -> comment -> models.py
from django.db import models from django.conf import settings from storm.models import Article import markdown import emoji # 游民评论者信息表 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='评论人') create_date = models.DateTimeField('创建时间', auto_now_add=True) content = models.TextField('评论内容') parent = models.ForeignKey('self', verbose_name='父评论', related_name='%(class)s_child_comments', blank=True, null=True) rep_to = models.ForeignKey('self', verbose_name='回复', related_name='%(class)s_rep_comments', blank=True, null=True) 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', extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', ]) return to_md # 文章评论区,据继承评论信息表 class ArticleComment(Comment): # 记录评论属于哪篇文章 belong = models.ForeignKey(Article, related_name='article_comments', verbose_name='所属文章') 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']
这里评论信息表其实可以只创建一个表即可,但是这里分开创建
一是可以后期方便扩展,二是可以了解模型内继承的使用
4、分析评论区信息
因为后台逻辑,其实就是对用户需求进行处理反馈的东西,那么首先咱们的页面已经有了,就是 崔庆才 个人博客,所以需要分析一下 崔庆才 个人博客评论区设么怎么个功能,这里怎么分析呢?
首先进入 抓取到的 崔庆才 博客的前端源码,结合前端 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
5、配置路由
blog -> blog -> urls.py
# 评论 url(r'^comment/', include('comment.urls', namespace='comment')), blog -> comment -> urls.py from django.conf.urls import url from .views import AddcommentView urlpatterns = [ url(r'^add/$', AddcommentView, name='add_comment'), ]
6、编写视图函数
blog -> comment -> view.py
from storm.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 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"')
7、编写自定义模板标签
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' onclick='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' onclick='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)
blog -> user -> oauth_tags.py
# 获取评论者信息 @register.simple_tag() def get_tourist_data(uid): """返回评论者的信息""" user = CommentUser.objects.filter(id=uid) if user: return user[0] else: return ''
8、编写评论区 HTML
blog -> templates -> comment_list.html
{% load staticfiles %} {% 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>
9、添加评论功能
这里以文章页面为例
blog -> templates -> article.html
把评论区添加到文章详情页底部
</div> </div> {% include 'comment_list.html' %} </div> </div> {% endblock body %}
哪个页面需要评论功能,就把下边的代码放在需要得而位置
{% include 'comment_list.html' %}
关于自己、给我留言也在相应位置添加此代码即可
完整源码:Github
10、效果图
9、总结
这里我这里写的评论区功能其实很独立,只要在需要添加评论功能的页面需要的地方加入
{% include 'comment_list.html' %}
这个评论功能,纯自己写的,没有写太多功能,比如删除评论、过滤垃圾用户、修改评论、评论邮件提醒等
以后有时间再升级吧
【友情提示】——如果发现有表达错误,或者知识点错误,或者搞不懂的地方,请及时留言,可以在评论区互相帮助,让后来者少走弯路是我的初衷。我也是一步步摸着石头走过来的,深知网络上只言片语的图文教程,给初学者带来的深深困扰。
【建议】——在对项目结构不太熟悉时,参照完整源码少走弯路
转载请注明: StormSha » Django个人博客开发十四 | 评论区