一、点赞功能
思路是这样的:
1、对点赞和踩都设置一个相同的class,然后对这个class绑定点击事件
2、点击触发ajax请求,我们对赞的标签设置了一个class属性,对踩的标签没有设置这个class属性,如果我们点击的标签有这个class属性,则我们认为这次点击的赞,如果没有,则我们认为是踩
3、ajax向后台发的数据有文章的id、和这次是踩还是赞的信息即可,因为这次操作的用户,可以直接从后台获取,因为我们用了django默认的auth模块,这里要注意,由于ajax这次发的是post请求,则需要把crsf带上
4、后端拿到信息后,先去点赞表中查询,如果没有点赞或者踩过,则直接在数据库中创建信息即可,如果有的话,因为我们这里的设计是这样的,如果点过赞或者踩过,则不允许再次点赞和踩,根据数据库中不同的状态通过dict的方式用Httpresponse的方式发送给前端
5、前端的ajax的success函数接受到后,我们前端可以有两张方式处理
5_1、方式1,可以直接更新当前页面就可以了,但是这样就会加重服务器的负担
5_2、方式2,可以利用ajax的局部刷新的原理,如果操作成功的话,获取到当前赞或者踩标签的数值,然后对该数值+1,就可以了,如果失败,则把报错信息打印在前端即可,这里我们还可以用一个setTimeout的函数,可以设置过多少秒执行某个函数,我们这里设置1000ms后,把报错信息清空即可
6、这里还有一个小技巧要分享一下,我们点击赞或者踩,如何获取当前的文章的id呢,我们当然可以通过js的方式去寻找这个id,但是有一个非常简单的方法,我们可以对赞或者踩这个标签设置一个自定义属性,这个属性就这个文章的id,这样,我们就可以通过$("this").attr("属性名称")来获取我们的文章的id了
先看下前端的代码,如下这段代码是我直接从博客园上爬下来的
<div class="poll 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="burynum" 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>
这里需要注意,同时也要把css文件也扒下来,然后放在的我们的css的block块中
{% block css %} <link rel="stylesheet" href="/static/css/article.css"> {% endblock %}
然后我们看下js的代码,我们这里是用ajax的方式实现点赞和踩的功能,这里我们把点赞和踩的js代码单独放在了一个js文件中,然后在script标签中引入
<script src="/static/js/for_artic_desc.js"></script>
下面我们重点看下js的代码
$("#div_digg .action").click(function () { if ($(".info").attr("username")){ var is_up = $(this).hasClass("diggit"); // {# 如果有diggit这个class,则为true,如果没有diggit,则为false,如果为true,则为赞,如果为false则为踩#} // // {# var article_id = {{ article_obj.nid }};#} // {##} // {# var article_title = {{ article_obj.title }};#} // {# 如果在js中使用模板渲染的话,如果直接按照上面的方式渲染#} // {# #} // {# 则如果渲染出来的值是字符串,则js会把他当做变量,会报错#} // {# 则如果渲染出来的值是数字,则js会把他当做一个数字,不会报错#} // {# #} // {# #} // {# 为了解决这个问题,如果我们在js要使用模板语言,则对大括号外面加一个引号就可以了#} // var article_id = "{{ article_obj.nid }}"; var article_id = $(".info").attr("artic_id"); var article_title = "{{ article_obj.title }}"; $.ajax( { url: "/app1/up_down/", type: "post", data: { "article_id": article_id, "article_title": article_title, "is_up": is_up, "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), }, success: function (data) { data = JSON.parse(data); if (data.state) { if (is_up) { var val = $("#digg_count").text(); val = parseInt(val) + 1; $("#digg_count").text(val) } else { var val = $("#bury_count").text(); val = parseInt(val) + 1; $("#bury_count").text(val); } } else { $("#digg-tips").text(data.error_code); setTimeout(function () { $("#digg_tips").text("aaaaaaaa"); console.log("123") },1000) } } } ) }else { location.href = "/app1/login/" } })
上面的代码比较简单,就是获取需要的值,然后通过ajax通过post方式发送给后端
首先判断有没有登陆
如果没有登陆则跳转到登陆页面
如果有登陆,则首先拿到is_up这个变量的值,这里我们通过判断我们点击的标签是否有某个class属性来判断,如果有则is_up为true,如果没有,则is_up为false
这里还需要注意,在js代码也可以拿到大括号里的值,但是必须要用引号括起来,不然js会把大括号中的值当做一个变量来处理
这里还有一个小技巧,我们可以写一个标签,然后这个为这个标签加上自定义的属性,而这些属性的值就是我们要发给后端的值,我们就可以直接找到这个标签,然后通过attr方法获取到相应的属性,然后就可以发送给后端就可以了
比如我们这里就用这个标签通过自定义的方式存储我们要往后端发送的数据
<div class="info" artic_id="{{ article_obj.nid }}" username="{{ request.user.username }}"></div>
这里还要注意,ajax发送数据,需要在data中添加下面的代码,才可以解决crsf问题
"csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(),
success中的函数就很简单了
success: function (data) { data = JSON.parse(data); if (data.state) { if (is_up) { var val = $("#digg_count").text(); val = parseInt(val) + 1; $("#digg_count").text(val) } else { var val = $("#bury_count").text(); val = parseInt(val) + 1; $("#bury_count").text(val); } } else { $("#digg-tips").text(data.error_code); setTimeout(function () { $("#digg_tips").text("aaaaaaaa"); console.log("123") },1000) } }
最后我们看下后端的代码
from django.views.decorators.csrf import csrf_exempt from django.db.models import F # @csrf_exempt import json def up_down(request): ret = {"state":True,"error_code":""} print(request.POST.get("article_id")) print(request.POST.get("article_title")) is_up = request.POST.get("is_up") user_obj = models.Userinfo.objects.get(username=request.user) try: models.ArticleUpDown.objects.create( user = user_obj, article = models.Article.objects.get(nid=request.POST.get("article_id")), is_up = json.loads(is_up) ) except Exception as e: flag = models.ArticleUpDown.objects.filter(user=user_obj,article=models.Article.objects.get(nid=request.POST.get("article_id")))[0].is_up if flag: if json.loads(is_up): ret = {"state": False, "error_code": "你已经顶过该博客,不能在顶了"} else: ret = {"state": False, "error_code": "你已经顶过该博客,不能在踩了"} else: if json.loads(is_up): ret = {"state": False, "error_code": "你已经踩过该博客,不能在顶了"} else: ret = {"state": False, "error_code": "你已经踩过该博客,不能在踩了"} else: if json.loads(is_up): models.Article.objects.filter(nid=request.POST.get("article_id")).update( up_count = F("up_count") + 1 ) else: models.Article.objects.filter(nid=request.POST.get("article_id")).update( down_count = F("down_count") + 1 ) return HttpResponse(json.dumps(ret))
后端的python代码就非常熟悉了,这里就不做讲解了
二、评论功能
评论功能的思路是这样的
我们先捋一下我们要实现的需求
需求1、实现根评论
需求2、用评论楼的方式实现子评论
需求3、用评论树的方式实现子评论
实现根评论
1、利用ajax,把文章的id和评论的内容发送给后端的视图函数,后端函数处理成功,返回给前端
2、前端直接刷新当前页面,显示评论即可
实现评论楼的方式子评论
1、对回复的按钮绑定一个focus事件,点击这个事件会做两件事情,a、把当前的标签定位的评论内容输入框,b、获取这次评论的评论者,然后做字符串拼接,把@ + “用户名”的值输入到评论框中
2、然后把评论的内容,文章的id,被评论的id发送给后端
3、后端处理成功后,返回前端,前端刷新页面就可以了
实现评论树的方式子评论
1、也是用ajax的方式实现,首先直接在script标签中定义一个函数,这个函数只要页面一加载就会执行,后端把这个文章的所有的评论按照评论的时间排序,最后都返回给前端
2、前端接受到后端所有的评论之后,循环所有的数据,首先把所有的跟评论显示出来,这里我们定义一个标签的字符串,然后把所有的根评论插入到我们的评论树的父标签中,当前我们需要为插入的标签赋值一个自定义的属性,这个属性的值就是这个标签的id值
3、然后在循环子评论的标签,首先找到这个标签评论的父评论的的标签,通过$("[属性=父评论的id]"),然后把标签的字符串格式通过append方式插入进去就可以了
下面我们看下具体的代码
前端的html的代码如下,当然也是从博客园上扒下来的
{% if request.user.username %} <div class="comments"> <p> 昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"> <p>评论内容:</p> <textarea name="comments" id="comments_id" cols="57" rows="10"></textarea> <p><input type="button" id="ajax_comment" value="提交评论"></p> </p> </div> {% else %} <p> <a href="/app1/login/">如果要评论,请先登陆</a> </p> {% endif %}
这里我们先做了一个判断,如果用户登陆,即request.user.username有值,则认为登陆成功,则允许评论,如果没有值,则返回到登陆页面
这里我们用ajax进行评论的提交
下面我们看下js代码是如何实现的
has_p = false $("#ajax_comment").bind("click",function () { var content = $("#comments_id").val(); var article = $(".info").attr("artic_id"); {# alert($(this).attr("username"))#} $.ajax( { url:"/app1/add_comment/", type:"post", data:{ "content":content, "article":article, "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), "has_p":has_p }, success:function (data) { setTimeout(function () { $("#comments_id").val("") },2000); {# 重新加载本页面#} document.location.reload(); has_p = "" } } ) })
这里,我们首先定义了一个全局的变量has_p。如果这个变量有值,则说明是一个子评论,如果这个变量没有值,则说明这是一个根评论,这里我们先看下根评论
通过ajax把评论的内容和文章的id和has_p的值发送给后台,这里也要注意,因为我们是post,需要注意crsf的处理
我们这里也是找了一个没用的标签存储我们需要的文章的id,方便我们在js去查找,这里标签的作用就是方便我们查找文章的id
<div class="info" artic_id="{{ article_obj.nid }}" username="{{ request.user.username }}"></div>
然后我们看下后端的代码
def add_comment(request): ret = {"state":True,"errorcode":""} u = models.Userinfo.objects.get(username=request.user.username) content = request.POST.get("content") has_p = request.POST.get("has_p") article = models.Article.objects.get(nid=request.POST.get("article")) if not json.loads(request.POST.get("has_p")): models.Comment.objects.create( article = article, user = u, content = content, ) return HttpResponse(json.dumps(ret)) # comment_list = models.Comment.objects.all() # return re else: import re obj = re.split("\n",content,1) models.Comment.objects.create( article = article, user = u, content = obj[1], parent_comment_id = int(has_p) ) return HttpResponse(json.dumps(ret))
在这里,我们先把post的请求中的数据拿到,然后判断一下has_p是否有值,如果没有值,则是根评论,如果有值,则说明是子评论
拿到插入数据需要的值,调用create方法在数据库中创建数据即可,然后返回一个json.dumps("dict")的json字符串
前端拿到后端发过来的数据,这里我们做异常处理,只做基本功能的实现,把当前的评论框的内容清空,然后重新加载页面
success:function (data) { setTimeout(function () { $("#comments_id").val("") },2000); {# 重新加载本页面#} document.location.reload(); has_p = "" }
然后看下评论楼的方式实现子评论
先看下html的代码
<h3>评论楼</h3> <div class="comment_list"> {% for comment in comment_list %} <div class="feedbackItem"> <div class="feedbackListTitle"><a href="#4031379" class="layer">#{{ forloop.counter }}楼</a><a name="4031379" id="comment_anchor_4031379"></a></div> <div class="feedbackListSubtitle"> <span class="comment_date">{{ comment.create_time }}</span> | <a id="a_comment_author_4031379" href="#" target="_blank">{{ comment.user }}</a><br> <div align="left"> {% if comment.parent_comment %} <p><a href="">@{{ comment.parent_comment.user.username }} {{ comment.parent_comment.content }}</a></p> {% endif %} <div id="{{ comment.nid }}" class="blog_comment_body">{{ comment.content }}</div> <div class="comment_vote"><a class="comment_digg" style="cursor: pointer" username={{ comment.user }} cid="{{ comment.nid }}">回复</a></div> </div> </div> </div> {% endfor %} </div>
html代码是从博客园拔下来了的,这里我们对回复这个a标签绑定了一个事件,设置了一个属性,这个属性就是cid属性,属性的值就是这次评论的id,因为我们这里做的子评论,所以当前回复的所代码的回复就是我们下次评论的父评论,这里也是方便我们拿到父评论的id,直接通过$("this").attr("cid")就可以拿到值
另外我们对这个a标签绑定了一个focus的事件,会把当前的鼠标定位到评论框中,然后通过字符串拼接,拿到一个@+用户名+“\n”的字符串,然后赋值给评论框中
另外我们在这个事件函数中还对has_p这个变量进行赋值了,赋值为这次评论的id
$(".feedbackItem .comment_digg").bind("click",function () { $("#comments_id").focus() ; var uname = $(this).attr("username"); var temp = "@" + uname + "\n"; has_p = $(this).attr("cid") $("#comments_id").val(temp); })
点击提交评论,就和父评论是一样的,唯一的不一样的就是has_p这次有值了,我们直接看视图函数是如何处理的
import re obj = re.split("\n",content,1) models.Comment.objects.create( article = article, user = u, content = obj[1], parent_comment_id = int(has_p) ) return HttpResponse(json.dumps(ret))
后端的处理很简单,首先要对前端发过来的字符串做split,把接受到的评论的按照第一个换行符做分割,取列表的第二个值,这里就是我们的评论的真正的内容,然后更新数据库就可以了
在前端渲染评论的时候,这里我们先判断该评论是否有父评论,对两种情况做分别的处理
{% if comment.parent_comment %} <p><a href="">@{{ comment.parent_comment.user.username }} {{ comment.parent_comment.content }}</a></p> {% endif %} <div id="{{ comment.nid }}" class="blog_comment_body">{{ comment.content }}</div> <div class="comment_vote"><a class="comment_digg" style="cursor: pointer" username={{ comment.user }} cid="{{ comment.nid }}">回复</a></div> </div>
最后我们看下评论树的实现方式
首先我们在scrip中直接写了一个ajax,实现的效果就是页面加载成功后,他会把我们的文章id发送给后端,不需要绑定什么事件
先看下这段ajax的代码,先不要看success中的函数
$.ajax( { url:"/app1/comment_tree/" + "{{ article_obj.nid }}", success:function (data) { $.each(data,function (i,comment_obj) { var s = '<div class="comment_item" comment_id=' + comment_obj[0] + '> <span class="content">' + comment_obj[3] + '</span> </div>' if (comment_obj[2] == null){ $(".comment_tree").append(s) }else { console.log("[comment_id=" + comment_obj[2] + "]") $("[comment_id=" + comment_obj[2] + "]").append(s) } {# console.log(comment_obj)#} }) } } )
然后我们去看下视图函数的处理
from django.http import JsonResponse def comment_tree(request,nid): ret = models.Comment.objects.filter(article__nid=int(nid)).order_by("create_time").values_list("nid","create_time","parent_comment","content","user") comment_tree_list = [] print("=" * 120) for i in ret: comment_tree_list.append(i) print(comment_tree_list,type(comment_tree_list)) return JsonResponse(list(ret),safe=False)
在后端的视图函数中,我们拿到这个文章的所有的评论,然后发送给前端
这次我们看下success中的函数,
通过$.each来循环后端发来的所有的数据,然后把每个评论都做字符串拼接,弄成一个html标签的字符串
首先判断是否是根评论,如果是根评论,则直接把字符串插入到评论树中即可
如果不是根评论,则一定是子评论,则通过has_p的值,查找到父评论的标签,然后把这个字符串插入到父评论中,这里怎么找到的父评论的标签,这里有一个点睛之笔,我们看下我们的htlm标签的字符串
var s = '<div class="comment_item" comment_id=' + comment_obj[0] + '> <span class="content">' + comment_obj[3] + '</span> </div>'
这个标签有一个comment_id的自定义属性,我们直接通过自定义属性查找父评论的标签即可
通过属性查找器,直接找父评论的标签,然后插入这次的评论的字符串即可