一、点赞和踩灭

样式

先来做样式,修改article_detail.html,增加div_digg的div

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. </div>
  20. {% endblock %}

在static—>css目录下,创建文件article_detail.css

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. </div>
  20. {% endblock %}

从博客园拷贝2个图片到static—>img目录下

修改base.html,引入article_detail.css

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <link rel="shortcut icon" href="https://common.cnblogs.com/favicon.ico" type="image/x-icon"/>
  7. {#公共样式#}
  8. <link rel="stylesheet" href="/static/css/theme/common.css">
  9. {#个人站点主题样式#}
  10. <link rel="stylesheet" href="/static/css/theme/{{ blog.theme }}">
  11. {#bootstrap#}
  12. <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css">
  13. <script src="/static/js/jquery.js"></script>
  14. <script src="/static/bootstrap/js/bootstrap.js"></script>
  15. {#文章详情#}
  16. <link rel="stylesheet" href="/static/css/article_detail.css">
  17. </head>
  18. <body>
  19. <div class="header">
  20. <p class="title">{{ blog.title }}</p>
  21. </div>
  22. <div class="container-fluid">
  23. <div class="row">
  24. <div class="col-md-3">
  25. {#加载自定义标签模块#}
  26. {% load my_tags %}
  27. {#调用get_query_data标签,它返回left_region.html,是已经被渲染过的文件#}
  28. {% get_query_data username %}
  29. </div>
  30. <div class="col-md-9">
  31. {% block content %}
  32. {% endblock %}
  33. </div>
  34. </div>
  35. </div>
  36. </body>
  37. </html>

点击一篇文章:http://127.0.0.1:8000/xiao/articles/5/

拉到最下面,右侧效果如下:

Day82 点赞和踩灭,用户评论 - 图1

绑定事件

推荐和反对有2个按钮,要绑定2个事件吗?答案是可以,但是不推荐使用!为什么呢?

因为会造成大量代码重复!这2个按钮需要发送的文章id和用户id是一样的,唯一不同的就是:一个是推荐,一个是反对。

修改article_detail.html增加div,测试js代码

注意:在base.html中已经导入了jquery

hasClass() 方法检查被选元素是否包含指定的 class。它返回True和False

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. </div>
  20. <script>
  21. $(".action").click(function () {
  22. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  23. var is_up = $(this).hasClass("diggit");
  24. alert(is_up);
  25. })
  26. </script>
  27. {% endblock %}

刷新网页,点击文章右下角的推荐,提示true

Day82 点赞和踩灭,用户评论 - 图2

点击反对,提示false

Day82 点赞和踩灭,用户评论 - 图3

发送ajax请求

数据分析

到底是用get还是post呢?有一个规范:查询用get,更改用post

查看blog_articleupdown表,这个是点赞表。插入一条记录,需要3个参数,分别是是否点赞、文章id、用户id

注意:这个用户id是当前登录用户,不是文章作者!

由于在视图函数中,有一个全局变量request.user。它是用户登录之后,才有的!所以这个用户id,不需要传。

只需要传2个值就可以了,分别是是否点赞和文章id。

在article_detail.html中,已经有一个article_obj,它是一个model对象,那么就可以得到文章id。

是否点赞,通过js代码中的is_up变量,也可以知道。

所以发送的数据,不需要使用标签选择器来获取。直接使用即可!

操作流程

  1. 用户点击推荐或者反对,触发点击事件
  2. 发送ajax请求给服务器
  3. 服务器接收参数,生成一条点赞或者踩灭的记录
  4. 服务器将返回结果响应给浏览器,浏览器中的ajax代码的success中的data接收响应体
  5. ajax对响应的数据做判断,操作DOM,显示给用户看!

服务器处理

修改urls.py,增加一个路径digg

  1. urlpatterns = [
  2. path('admin/', admin.site.urls),
  3. path('login/', views.login),
  4. path('index/', views.index),
  5. path('logout/', views.logout),
  6. path('', views.index),
  7. #点赞或者踩灭
  8. path('digg/', views.digg),
  9. #文章详情
  10. re_path('(?P<username>\w+)/articles/(?P<article_id>\d+)/$', views.article_detail),
  11. # 跳转
  12. re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite),
  13. # 个人站点
  14. re_path('(?P<username>\w+)/$', views.homesite),
  15. ]

注意:digg要放到文章详情的上面

修改article_detail.html

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. </div>
  20. {% csrf_token %}
  21. <script>
  22. $(".action").click(function () {
  23. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  24. var is_up = $(this).hasClass("diggit");
  25. {#判断是否登录#}
  26. if ("{{ request.user.username }}") {
  27. $.ajax({
  28. url: "/digg/",
  29. type: "post",
  30. data: {
  31. is_up: is_up,
  32. article_id: "{{ article_obj.pk }}",
  33. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  34. },
  35. success: function (data) {
  36. console.log(data);
  37. console.log(typeof data);
  38. }
  39. })
  40. } else {
  41. location.href = "/login/";
  42. }
  43. })
  44. </script>
  45. {% endblock %}

修改views.py,增加视图函数digg

注意:导入2个模块

  1. import json
  2. from django.http import JsonResponse

视图函数如下:

  1. from django.shortcuts import render,HttpResponse,redirect
  2. from django.contrib import auth
  3. from blog.models import Article,UserInfo,Blog,Category,Tag,ArticleUpDown
  4. from django.db.models import Sum,Avg,Max,Min,Count
  5. from django.db.models import F
  6. import json
  7. from django.http import JsonResponse
  8. # Create your views here.
  9. def login(request):
  10. if request.method=="POST":
  11. user=request.POST.get("user")
  12. pwd=request.POST.get("pwd")
  13. # 用户验证成功,返回user对象,否则返回None
  14. user=auth.authenticate(username=user,password=pwd)
  15. if user:
  16. # 登录,注册session
  17. # 全局变量 request.user=当前登陆对象(session中)
  18. auth.login(request,user)
  19. return redirect("/index/")
  20. return render(request,"login.html")
  21. def index(request):
  22. article_list=Article.objects.all()
  23. return render(request,"index.html",{"article_list":article_list})
  24. def logout(request): # 注销
  25. auth.logout(request)
  26. return redirect("/index/")
  27. def query_current_site(request,username): # 查询当前站点的博客标题
  28. # 查询当前站点的用户对象
  29. user = UserInfo.objects.filter(username=username).first()
  30. if not user:
  31. return render(request, "not_found.html")
  32. # 查询当前站点对象
  33. blog = user.blog
  34. return blog
  35. def homesite(request,username,**kwargs): # 个人站点主页
  36. print("kwargs", kwargs)
  37. blog = query_current_site(request,username)
  38. # 查询当前用户发布的所有文章
  39. if not kwargs:
  40. article_list = Article.objects.filter(user__username=username)
  41. else:
  42. condition = kwargs.get("condition")
  43. params = kwargs.get("params")
  44. #判断分类、随笔、归档
  45. if condition == "category":
  46. article_list = Article.objects.filter(user__username=username).filter(category__title=params)
  47. elif condition == "tag":
  48. article_list = Article.objects.filter(user__username=username).filter(tags__title=params)
  49. else:
  50. year, month = params.split("/")
  51. article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,
  52. create_time__month=month)
  53. return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list})
  54. def article_detail(request,username,article_id):
  55. blog = query_current_site(request,username)
  56. #查询指定id的文章
  57. article_obj = Article.objects.filter(pk=article_id).first()
  58. user_id = UserInfo.objects.filter(username=username).first().nid
  59. return render(request,'article_detail.html',{"blog":blog,"username":username,'article_obj':article_obj,"user_id":user_id})
  60. def digg(request):
  61. print(request.POST)
  62. if request.method == "POST":
  63. #ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值
  64. is_up = json.loads(request.POST.get("is_up"))
  65. article_id = request.POST.get("article_id")
  66. user_id = request.user.pk
  67. response = {"state": True, "msg": None} # 初始状态
  68. #判断当前登录用户是否对这篇文章做过点赞或者踩灭操作
  69. obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  70. if obj:
  71. response["state"] = False # 更改状态
  72. else:
  73. #插入一条记录
  74. new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  75. return JsonResponse(response)
  76. else:
  77. return HttpResponse("非法请求")

json处理问题

ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值

json支持7种数据格式,其中true和false是其中2种。那么反序列化之后,会得到一个布尔值。

为什么一定要反序列化呢?因为…create(…is_up=is_up) 等式右边的值,非空。那么ORM会认为是True!

所以不管ajax传过来的是true和falase,它都是字符串,那么ORM执行时,始终都是True

JsonResponse

JsonResponse对象是HttpRespon的子类,它主要和父类的区别在于:

1.它的默认Content-Type 被设置为: application/json

2.第一个参数,data应该是一个字典类型,如果不是字典,抛出 TypeError的异常

它返回json数据,那么ajax接收时,不需要反序列化,可以直接使用!

刷新网页,点击右侧的推荐,查看浏览器控制台

Day82 点赞和踩灭,用户评论 - 图4

查看blog_articleupdown表记录,发现多了一条记录

Day82 点赞和踩灭,用户评论 - 图5

查看Pycharm控制台输出:

  1. <QueryDict: {'article_id': ['6'], 'csrfmiddlewaretoken': ['JgLyFpVgp92Rs8ppPCd2pm9jVj6z8bo9KSsMwKnakpB6CwTCT1K58v2JHLeR5ejN'], 'is_up': ['true']}>

判断用户之前的操作

当用户第一次点击推荐时,直接将数字加1。再次点击时,提示您已经推荐过!再次点击返回时,也提示您已经推荐过。

同理,当用户对一篇点击返回后。之后不论是点击推荐还是反对,都会提示您已经反对过!

不可以取消推荐或者反对!

修改views.py,获取上一次操作的记录

  1. def digg(request):
  2. print(request.POST)
  3. if request.method == "POST":
  4. #ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值
  5. is_up = json.loads(request.POST.get("is_up"))
  6. article_id = request.POST.get("article_id")
  7. user_id = request.user.pk
  8. response = {"state": True, "msg": None} # 初始状态
  9. #判断当前登录用户是否对这篇文章做过点赞或者踩灭操作
  10. obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  11. if obj:
  12. response["state"] = False # 更改状态
  13. response["handled"] = obj.is_up # 获取之前的操作,返回true或者false
  14. print(obj.is_up)
  15. else:
  16. #插入一条记录
  17. new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  18. return JsonResponse(response)
  19. else:
  20. return HttpResponse("非法请求")

修改article_detail.html的js代码

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. </div>
  20. {% csrf_token %}
  21. <script>
  22. $(".action").click(function () {
  23. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  24. var is_up = $(this).hasClass("diggit");
  25. {#判断是否登录#}
  26. if ("{{ request.user.username }}") {
  27. $.ajax({
  28. url: "/digg/",
  29. type: "post",
  30. data: {
  31. is_up: is_up,
  32. article_id: "{{ article_obj.pk }}",
  33. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  34. },
  35. success: function (data) {
  36. console.log(data);
  37. console.log(typeof data);
  38. if (data.state) {
  39. //提交成功
  40. } else {
  41. if (data.handled) { //判断之前的操作记录
  42. $("#digg_tips").html("您已经推荐过!")
  43. } else {
  44. $("#digg_tips").html("您已经反对过!")
  45. }
  46. }
  47. }
  48. })
  49. } else {
  50. location.href = "/login/";
  51. }
  52. })
  53. </script>
  54. {% endblock %}

修改static—>css目录下的article_detail.css,增加样式

  1. #div_digg {
  2. float: right;
  3. margin-bottom: 10px;
  4. margin-right: 30px;
  5. font-size: 12px;
  6. width: 125px;
  7. text-align: center;
  8. margin-top: 10px;
  9. }
  10. .diggit {
  11. float: left;
  12. width: 46px;
  13. height: 52px;
  14. background: url("/static/img/upup.gif") no-repeat;
  15. text-align: center;
  16. cursor: pointer;
  17. margin-top: 2px;
  18. padding-top: 5px;
  19. }
  20. .buryit {
  21. float: right;
  22. margin-left: 20px;
  23. width: 46px;
  24. height: 52px;
  25. background: url("/static/img/downdown.gif") no-repeat;
  26. text-align: center;
  27. cursor: pointer;
  28. margin-top: 2px;
  29. padding-top: 5px;
  30. }
  31. .clear {
  32. clear: both;
  33. }
  34. #digg_tips{
  35. color: red;
  36. }

重启django项目,页面强制刷新几次!

再次点击,提示已经点过了

Day82 点赞和踩灭,用户评论 - 图6

修改article_detail.html的js代码,增加动态效果,1秒后,清空红色文字

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. </div>
  20. {% csrf_token %}
  21. <script>
  22. $(".action").click(function () {
  23. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  24. var is_up = $(this).hasClass("diggit");
  25. {#判断是否登录#}
  26. if ("{{ request.user.username }}") {
  27. $.ajax({
  28. url: "/digg/",
  29. type: "post",
  30. data: {
  31. is_up: is_up,
  32. article_id: "{{ article_obj.pk }}",
  33. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  34. },
  35. success: function (data) {
  36. console.log(data);
  37. console.log(typeof data);
  38. if (data.state) {
  39. //提交成功
  40. } else {
  41. if (data.handled) { //判断之前的操作记录
  42. $("#digg_tips").html("您已经推荐过!")
  43. } else {
  44. $("#digg_tips").html("您已经反对过!")
  45. }
  46. setTimeout(function () {
  47. $("#digg_tips").html("") //清空提示文字
  48. }, 1000)
  49. }
  50. }
  51. })
  52. } else {
  53. location.href = "/login/";
  54. }
  55. })
  56. </script>
  57. {% endblock %}

效果如下:

Day82 点赞和踩灭,用户评论 - 图7

更新文章表

blog_articleupdown表有一个联合唯一索引,即使重复点击,也不会增加记录!

blog_article表有一个up_count和down_count字段,分别表示推荐和反对的计数。

那么这2个字段,也需要同时更新。在原有数值的基础上加1,需要使用F查询!

导入F模块,完整代码如下:

  1. def digg(request):
  2. print(request.POST)
  3. if request.method == "POST":
  4. #ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值
  5. is_up = json.loads(request.POST.get("is_up"))
  6. article_id = request.POST.get("article_id")
  7. user_id = request.user.pk
  8. response = {"state": True, "msg": None} # 初始状态
  9. #判断当前登录用户是否对这篇文章做过点赞或者踩灭操作
  10. obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  11. if obj:
  12. response["state"] = False # 更改状态
  13. response["handled"] = obj.is_up # 获取之前的操作,返回true或者false
  14. print(obj.is_up)
  15. else:
  16. #插入一条记录
  17. new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  18. if is_up: # 判断为推荐
  19. Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)
  20. else: # 反对
  21. Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1)
  22. return JsonResponse(response)
  23. else:
  24. return HttpResponse("非法请求")

清空blog_articleupdown表记录

访问网页,重新点击。再次刷新页面,那么值就会更新了!

Day82 点赞和踩灭,用户评论 - 图8

网页实时展示

上面已经实现了,点击之后,更新数据库。但是需要刷新网页,才会有效果。想要用户能实时看到数字,需要进行DOM操作。

获取网页原有的值,进行加1。最后进行DOM操作,展示给用户看!

修改article_detail.html的js代码

注意:js是弱类型语言,获取到值时,不能直接进行加法,需要强制转换才行!

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. </div>
  20. {% csrf_token %}
  21. <script>
  22. $(".action").click(function () {
  23. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  24. var is_up = $(this).hasClass("diggit");
  25. {#获取提示的span标签#}
  26. var _this= $(this).children("span");
  27. {#判断是否登录#}
  28. if ("{{ request.user.username }}") {
  29. $.ajax({
  30. url: "/digg/",
  31. type: "post",
  32. data: {
  33. is_up: is_up,
  34. article_id: "{{ article_obj.pk }}",
  35. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  36. },
  37. success: function (data) {
  38. console.log(data);
  39. console.log(typeof data);
  40. if (data.state) {
  41. //提交成功
  42. var val=_this.text(); //获取text值
  43. //在原有的基础上加1。注意:一定要进行类型转换
  44. _this.text(parseInt(val)+1)
  45. } else {
  46. if (data.handled) { //判断之前的操作记录
  47. $("#digg_tips").html("您已经推荐过!")
  48. } else {
  49. $("#digg_tips").html("您已经反对过!")
  50. }
  51. setTimeout(function () {
  52. $("#digg_tips").html("") //清空提示文字
  53. }, 1000)
  54. }
  55. }
  56. })
  57. } else {
  58. location.href = "/login/";
  59. }
  60. })
  61. </script>
  62. {% endblock %}

清空blog_articleupdown表记录

将blog_article表的相关字段设置为0

访问网页,重新点击,效果如下:

Day82 点赞和踩灭,用户评论 - 图9

优化js代码

将if判断部分,改成三元运算

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. </div>
  20. {% csrf_token %}
  21. <script>
  22. $(".action").click(function () {
  23. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  24. var is_up = $(this).hasClass("diggit");
  25. {#获取提示的span标签#}
  26. var _this = $(this).children("span");
  27. {#判断是否登录#}
  28. if ("{{ request.user.username }}") {
  29. $.ajax({
  30. url: "/digg/",
  31. type: "post",
  32. data: {
  33. is_up: is_up,
  34. article_id: "{{ article_obj.pk }}",
  35. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  36. },
  37. success: function (data) {
  38. console.log(data);
  39. console.log(typeof data);
  40. if (data.state) {
  41. //提交成功
  42. var val = _this.text(); //获取text值
  43. //在原有的基础上加1。注意:一定要进行类型转换
  44. _this.text(parseInt(val) + 1)
  45. } else {
  46. // 重复提交
  47. var val = data.handled ? "您已经推荐过!" : "您已经反对过!";
  48. $("#digg_tips").html(val);
  49. setTimeout(function () {
  50. $("#digg_tips").html("") //清空提示文字
  51. }, 1000)
  52. }
  53. }
  54. })
  55. } else {
  56. location.href = "/login/";
  57. }
  58. })
  59. </script>
  60. {% endblock %}

事务

是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。

在上面的例子,点赞时,需要更新2个表。一个是blog_articleupdown表,一个是blog_article表。

如果有一个表没有更新,那么就会产生脏数据!为了避免这个问题,需要使用事务!

举例:模拟异常情况

修改digg视图函数,模拟异常

  1. def digg(request):
  2. print(request.POST)
  3. if request.method == "POST":
  4. #ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值
  5. is_up = json.loads(request.POST.get("is_up"))
  6. article_id = request.POST.get("article_id")
  7. user_id = request.user.pk
  8. response = {"state": True, "msg": None} # 初始状态
  9. #判断当前登录用户是否对这篇文章做过点赞或者踩灭操作
  10. obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  11. if obj:
  12. response["state"] = False # 更改状态
  13. response["handled"] = obj.is_up # 获取之前的操作,返回true或者false
  14. print(obj.is_up)
  15. else:
  16. #插入一条记录
  17. new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  18. ask # 这一行故意出错
  19. if is_up: # 判断为推荐
  20. Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)
  21. else: # 反对
  22. Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1)
  23. return JsonResponse(response)
  24. else:
  25. return HttpResponse("非法请求")

清空blog_articleupdown表,将blog_article表的up_count和down_count字段设置为0

刷新页面,重新点击,打开浏览器工具—>network

出行了500错误

Day82 点赞和踩灭,用户评论 - 图10

查看blog_articleupdown表,多了一条记录

Day82 点赞和踩灭,用户评论 - 图11

查看blog_article表,对应文章的up_count字段,发现还是为0

Day82 点赞和踩灭,用户评论 - 图12

使用事务

修改digg视图函数,导入模块transaction。

使用语法很简单,将相关原子操作的代码直接tab一下,就可以了

完整代码如下:

  1. from django.shortcuts import render,HttpResponse,redirect
  2. from django.contrib import auth
  3. from blog.models import Article,UserInfo,Blog,Category,Tag,ArticleUpDown
  4. from django.db.models import Sum,Avg,Max,Min,Count
  5. from django.db.models import F
  6. import json
  7. from django.http import JsonResponse
  8. from django.db import transaction
  9. # Create your views here.
  10. def login(request):
  11. if request.method=="POST":
  12. user=request.POST.get("user")
  13. pwd=request.POST.get("pwd")
  14. # 用户验证成功,返回user对象,否则返回None
  15. user=auth.authenticate(username=user,password=pwd)
  16. if user:
  17. # 登录,注册session
  18. # 全局变量 request.user=当前登陆对象(session中)
  19. auth.login(request,user)
  20. return redirect("/index/")
  21. return render(request,"login.html")
  22. def index(request):
  23. article_list=Article.objects.all()
  24. return render(request,"index.html",{"article_list":article_list})
  25. def logout(request): # 注销
  26. auth.logout(request)
  27. return redirect("/index/")
  28. def query_current_site(request,username): # 查询当前站点的博客标题
  29. # 查询当前站点的用户对象
  30. user = UserInfo.objects.filter(username=username).first()
  31. if not user:
  32. return render(request, "not_found.html")
  33. # 查询当前站点对象
  34. blog = user.blog
  35. return blog
  36. def homesite(request,username,**kwargs): # 个人站点主页
  37. print("kwargs", kwargs)
  38. blog = query_current_site(request,username)
  39. # 查询当前用户发布的所有文章
  40. if not kwargs:
  41. article_list = Article.objects.filter(user__username=username)
  42. else:
  43. condition = kwargs.get("condition")
  44. params = kwargs.get("params")
  45. #判断分类、随笔、归档
  46. if condition == "category":
  47. article_list = Article.objects.filter(user__username=username).filter(category__title=params)
  48. elif condition == "tag":
  49. article_list = Article.objects.filter(user__username=username).filter(tags__title=params)
  50. else:
  51. year, month = params.split("/")
  52. article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,
  53. create_time__month=month)
  54. return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list})
  55. def article_detail(request,username,article_id):
  56. blog = query_current_site(request,username)
  57. #查询指定id的文章
  58. article_obj = Article.objects.filter(pk=article_id).first()
  59. user_id = UserInfo.objects.filter(username=username).first().nid
  60. return render(request,'article_detail.html',{"blog":blog,"username":username,'article_obj':article_obj,"user_id":user_id})
  61. def digg(request):
  62. print(request.POST)
  63. if request.method == "POST":
  64. #ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值
  65. is_up = json.loads(request.POST.get("is_up"))
  66. article_id = request.POST.get("article_id")
  67. user_id = request.user.pk
  68. response = {"state": True, "msg": None} # 初始状态
  69. #判断当前登录用户是否对这篇文章做过点赞或者踩灭操作
  70. obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  71. if obj:
  72. response["state"] = False # 更改状态
  73. response["handled"] = obj.is_up # 获取之前的操作,返回true或者false
  74. print(obj.is_up)
  75. else:
  76. with transaction.atomic():
  77. #插入一条记录
  78. new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  79. ask # 模拟异常
  80. if is_up: # 判断为推荐
  81. Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)
  82. else: # 反对
  83. Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1)
  84. return JsonResponse(response)
  85. else:
  86. return HttpResponse("非法请求")

清空blog_articleupdown表

刷新页面,重新点击,再一次返回500错误

查看blog_articleupdown表,发现并没增加一条记录。

Day82 点赞和踩灭,用户评论 - 图13

这样就比较合理了!删除异常代码

  1. def digg(request):
  2. print(request.POST)
  3. if request.method == "POST":
  4. #ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值
  5. is_up = json.loads(request.POST.get("is_up"))
  6. article_id = request.POST.get("article_id")
  7. user_id = request.user.pk
  8. response = {"state": True, "msg": None} # 初始状态
  9. #判断当前登录用户是否对这篇文章做过点赞或者踩灭操作
  10. obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  11. if obj:
  12. response["state"] = False # 更改状态
  13. response["handled"] = obj.is_up # 获取之前的操作,返回true或者false
  14. print(obj.is_up)
  15. else:
  16. with transaction.atomic():
  17. #插入一条记录
  18. new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  19. if is_up: # 判断为推荐
  20. Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)
  21. else: # 反对
  22. Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1)
  23. return JsonResponse(response)
  24. else:
  25. return HttpResponse("非法请求")

刷新页面,重新点击

Day82 点赞和踩灭,用户评论 - 图14

查看blog_articleupdown表,发现增加了一条记录。

Day82 点赞和踩灭,用户评论 - 图15

查看blog_article表,对应文章的up_count字段,发现为1了!

Day82 点赞和踩灭,用户评论 - 图16

事务用起来很简单,是因为django把复杂的逻辑给封装好了!

有时间的小伙伴,还可以加一个需求,自己不能给自己点赞!

二、用户评论

评论样式

先写html的样式代码

修改article_detail.html

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. <div class="clearfix"></div>
  20. <div class="comment">
  21. <p>评论列表</p>
  22. <ul class="comment_list list-group">
  23. {% for comment in comment_list %}
  24. <li class="list-group-item">
  25. <div>
  26. <a href="">#{{ forloop.counter }}楼</a>
  27. <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>
  28. <a href="">{{ comment.user.username }}</a>
  29. <a href="" class="pull-right"><span>回复</span></a>
  30. </div>
  31. <div>
  32. <p>{{ comment.content }}</p>
  33. </div>
  34. </li>
  35. {% endfor %}
  36. </ul>
  37. <p>发表评论</p>
  38. <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p>
  39. <div>
  40. <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
  41. </div>
  42. <input type="button" value="submit" class="btn btn-default comment_btn">
  43. </div>
  44. </div>
  45. {% csrf_token %}
  46. <script>
  47. $(".action").click(function () {
  48. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  49. var is_up = $(this).hasClass("diggit");
  50. {#获取提示的span标签#}
  51. var _this = $(this).children("span");
  52. {#判断是否登录#}
  53. if ("{{ request.user.username }}") {
  54. $.ajax({
  55. url: "/digg/",
  56. type: "post",
  57. data: {
  58. is_up: is_up,
  59. article_id: "{{ article_obj.pk }}",
  60. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  61. },
  62. success: function (data) {
  63. console.log(data);
  64. console.log(typeof data);
  65. if (data.state) {
  66. //提交成功
  67. var val = _this.text(); //获取text值
  68. //在原有的基础上加1。注意:一定要进行类型转换
  69. _this.text(parseInt(val) + 1)
  70. } else {
  71. // 重复提交
  72. var val = data.handled ? "您已经推荐过!" : "您已经反对过!";
  73. $("#digg_tips").html(val);
  74. setTimeout(function () {
  75. $("#digg_tips").html("") //清空提示文字
  76. }, 1000)
  77. }
  78. }
  79. })
  80. } else {
  81. location.href = "/login/";
  82. }
  83. })
  84. </script>
  85. {% endblock %}

修改css目录下的article_detail.css

注意放一个gif图片到img目录下

  1. /*推荐和反对*/
  2. #div_digg {
  3. float: right;
  4. margin-bottom: 10px;
  5. margin-right: 30px;
  6. font-size: 12px;
  7. width: 125px;
  8. text-align: center;
  9. margin-top: 10px;
  10. }
  11. .diggit {
  12. float: left;
  13. width: 46px;
  14. height: 52px;
  15. background: url("/static/img/upup.gif") no-repeat;
  16. text-align: center;
  17. cursor: pointer;
  18. margin-top: 2px;
  19. padding-top: 5px;
  20. }
  21. .buryit {
  22. float: right;
  23. margin-left: 20px;
  24. width: 46px;
  25. height: 52px;
  26. background: url("/static/img/downdown.gif") no-repeat;
  27. text-align: center;
  28. cursor: pointer;
  29. margin-top: 2px;
  30. padding-top: 5px;
  31. }
  32. .clear {
  33. clear: both;
  34. }
  35. #digg_tips{
  36. color: red;
  37. }
  38. /*评论*/
  39. input.author{
  40. background-image: url("/static/img/icon_form.gif");
  41. background-repeat: no-repeat;
  42. border: 1px solid #ccc;
  43. padding: 4px 4px 4px 30px;
  44. width: 300px;
  45. font-size: 13px;
  46. }
  47. input.author {
  48. background-position: 3px -3px;
  49. }

刷新网页,拉到最下面,效果如下:

Day82 点赞和踩灭,用户评论 - 图17

评论步骤

  • 提交根评论请求
  • 显示根评论
  1. render
  2. Ajax显示
  • 提交子评论
  • 显示子评论
  • 评论树

发送ajax请求

查看评论表模型

  1. class Comment(models.Model):
  2. """
  3. 评论表
  4. """
  5. nid = models.AutoField(primary_key=True)
  6. article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid', on_delete=models.CASCADE)
  7. user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid', on_delete=models.CASCADE)
  8. content = models.CharField(verbose_name='评论内容', max_length=255)
  9. create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
  10. parent_comment = models.ForeignKey(verbose_name="父级评论id", to='Comment',null=True, on_delete=models.CASCADE)

它有6个字段,其中nid、user、parent_comment、create_time是可选的。

user 在视图函数中,可以直接获取,不需要ajax传此参数。

parent_comment字段的值为空,表示这是一条根评论。否则为一条子评论!

那么ajax如果传一个空值,表示为根评论,否则为子评论。

create_time字段有一个属性:auto_now_add=True。

字段在实例第一次保存的时候会保存当前时间,不管你在这里是否对其赋值。但是之后的save()是可以手动赋值的。

也就是新实例化一个model,想手动存其他时间,就需要对该实例save()之后赋值然后再save()

根评论

修改article_detail.html的js代码,只需要发送3个参数即可。分别是content,atricle_id,parent_comment

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. <div class="clearfix"></div>
  20. <div class="comment">
  21. <p>评论列表</p>
  22. <ul class="comment_list list-group">
  23. {% for comment in comment_list %}
  24. <li class="list-group-item">
  25. <div>
  26. <a href="">#{{ forloop.counter }}楼</a>
  27. <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>
  28. <a href="">{{ comment.user.username }}</a>
  29. <a href="" class="pull-right"><span>回复</span></a>
  30. </div>
  31. <div>
  32. <p>{{ comment.content }}</p>
  33. </div>
  34. </li>
  35. {% endfor %}
  36. </ul>
  37. <p>发表评论</p>
  38. <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
  39. value="{{ request.user.username }}"></p>
  40. <div>
  41. <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
  42. </div>
  43. <input type="button" value="submit" class="btn btn-default comment_btn">
  44. </div>
  45. </div>
  46. {% csrf_token %}
  47. <script>
  48. // 点赞和踩灭
  49. $(".action").click(function () {
  50. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  51. var is_up = $(this).hasClass("diggit");
  52. {#获取提示的span标签#}
  53. var _this = $(this).children("span");
  54. {#判断是否登录#}
  55. if ("{{ request.user.username }}") {
  56. $.ajax({
  57. url: "/digg/",
  58. type: "post",
  59. data: {
  60. is_up: is_up,
  61. article_id: "{{ article_obj.pk }}",
  62. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  63. },
  64. success: function (data) {
  65. console.log(data);
  66. console.log(typeof data);
  67. if (data.state) {
  68. //提交成功
  69. var val = _this.text(); //获取text值
  70. //在原有的基础上加1。注意:一定要进行类型转换
  71. _this.text(parseInt(val) + 1)
  72. } else {
  73. // 重复提交
  74. var val = data.handled ? "您已经推荐过!" : "您已经反对过!";
  75. $("#digg_tips").html(val);
  76. setTimeout(function () {
  77. $("#digg_tips").html("") //清空提示文字
  78. }, 1000)
  79. }
  80. }
  81. })
  82. } else {
  83. location.href = "/login/";
  84. }
  85. })
  86. // 提交评论
  87. $(".comment_btn").click(function () {
  88. {#评论内容#}
  89. var content = $("#comment_content").val();
  90. {#默认为空#}
  91. var pid = "";
  92. $.ajax({
  93. url: "/comment/",
  94. type: "post",
  95. data: {
  96. content: content,
  97. article_id: "{{ article_obj.pk }}",
  98. pid: pid,
  99. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  100. },
  101. success: function (data) {
  102. console.log(data);
  103. // 清空输入框的内容
  104. $("#comment_content").val("")
  105. }
  106. })
  107. })
  108. </script>
  109. {% endblock %}

修改urls.py,增加路径comment

  1. urlpatterns = [
  2. path('admin/', admin.site.urls),
  3. path('login/', views.login),
  4. path('index/', views.index),
  5. path('logout/', views.logout),
  6. path('', views.index),
  7. #点赞或者踩灭
  8. path('digg/', views.digg),
  9. # 评论
  10. path('comment/', views.comment),
  11. #文章详情
  12. re_path('(?P<username>\w+)/articles/(?P<article_id>\d+)/$', views.article_detail),
  13. # 跳转
  14. re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite),
  15. # 个人站点
  16. re_path('(?P<username>\w+)/$', views.homesite),
  17. ]

修改views.py,增加视图函数comment,完整代码如下:

  1. from django.shortcuts import render,HttpResponse,redirect
  2. from django.contrib import auth
  3. from blog.models import Article,UserInfo,Blog,Category,Tag,ArticleUpDown,Comment
  4. from django.db.models import Sum,Avg,Max,Min,Count
  5. from django.db.models import F
  6. import json
  7. from django.http import JsonResponse
  8. from django.db import transaction
  9. # Create your views here.
  10. def login(request):
  11. if request.method=="POST":
  12. user=request.POST.get("user")
  13. pwd=request.POST.get("pwd")
  14. # 用户验证成功,返回user对象,否则返回None
  15. user=auth.authenticate(username=user,password=pwd)
  16. if user:
  17. # 登录,注册session
  18. # 全局变量 request.user=当前登陆对象(session中)
  19. auth.login(request,user)
  20. return redirect("/index/")
  21. return render(request,"login.html")
  22. def index(request):
  23. article_list=Article.objects.all()
  24. return render(request,"index.html",{"article_list":article_list})
  25. def logout(request): # 注销
  26. auth.logout(request)
  27. return redirect("/index/")
  28. def query_current_site(request,username): # 查询当前站点的博客标题
  29. # 查询当前站点的用户对象
  30. user = UserInfo.objects.filter(username=username).first()
  31. if not user:
  32. return render(request, "not_found.html")
  33. # 查询当前站点对象
  34. blog = user.blog
  35. return blog
  36. def homesite(request,username,**kwargs): # 个人站点主页
  37. print("kwargs", kwargs)
  38. blog = query_current_site(request,username)
  39. # 查询当前用户发布的所有文章
  40. if not kwargs:
  41. article_list = Article.objects.filter(user__username=username)
  42. else:
  43. condition = kwargs.get("condition")
  44. params = kwargs.get("params")
  45. #判断分类、随笔、归档
  46. if condition == "category":
  47. article_list = Article.objects.filter(user__username=username).filter(category__title=params)
  48. elif condition == "tag":
  49. article_list = Article.objects.filter(user__username=username).filter(tags__title=params)
  50. else:
  51. year, month = params.split("/")
  52. article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,
  53. create_time__month=month)
  54. return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list})
  55. def article_detail(request,username,article_id):
  56. blog = query_current_site(request,username)
  57. #查询指定id的文章
  58. article_obj = Article.objects.filter(pk=article_id).first()
  59. user_id = UserInfo.objects.filter(username=username).first().nid
  60. return render(request,'article_detail.html',{"blog":blog,"username":username,'article_obj':article_obj,"user_id":user_id})
  61. def digg(request):
  62. print(request.POST)
  63. if request.method == "POST":
  64. #ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值
  65. is_up = json.loads(request.POST.get("is_up"))
  66. article_id = request.POST.get("article_id")
  67. user_id = request.user.pk
  68. response = {"state": True, "msg": None} # 初始状态
  69. #判断当前登录用户是否对这篇文章做过点赞或者踩灭操作
  70. obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  71. if obj:
  72. response["state"] = False # 更改状态
  73. response["handled"] = obj.is_up # 获取之前的操作,返回true或者false
  74. print(obj.is_up)
  75. else:
  76. with transaction.atomic():
  77. #插入一条记录
  78. new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  79. if is_up: # 判断为推荐
  80. Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)
  81. else: # 反对
  82. Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1)
  83. return JsonResponse(response)
  84. else:
  85. return HttpResponse("非法请求")
  86. def comment(request):
  87. print(request.POST)
  88. if request.method == "POST":
  89. # 获取数据
  90. user_id = request.user.pk
  91. article_id = request.POST.get("article_id")
  92. content = request.POST.get("content")
  93. pid = request.POST.get("pid")
  94. # 生成评论对象
  95. with transaction.atomic(): # 增加事务
  96. # 评论表增加一条记录
  97. comment = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
  98. # 当前文章的评论数加1
  99. Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1)
  100. response = {"state": False} # 初始状态
  101. if comment.user_id: # 判断返回值
  102. response = {"state": True}
  103. return JsonResponse(response) # 返回json对象
  104. else:
  105. return HttpResponse("非法请求")

刷新网页,发表一条评论

Day82 点赞和踩灭,用户评论 - 图18

查看表blog_comment,多了一条记录

Day82 点赞和踩灭,用户评论 - 图19

查看blog_article表,对应的comment_count值,数值加1了。

Day82 点赞和踩灭,用户评论 - 图20

渲染评论

修改article_detail视图函数,将评论列表传给模板

  1. def article_detail(request,username,article_id):
  2. blog = query_current_site(request,username)
  3. #查询指定id的文章
  4. article_obj = Article.objects.filter(pk=article_id).first()
  5. user_id = UserInfo.objects.filter(username=username).first().nid
  6. comment_list = Comment.objects.filter(article_id=article_id)
  7. dict = {"blog":blog,
  8. "username":username,
  9. 'article_obj':article_obj,
  10. "user_id":user_id,
  11. "comment_list":comment_list,
  12. }
  13. return render(request,'article_detail.html',dict)

修改article_detail.html,使用for循环,遍历列表

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="article_info">
  4. <h4 class="text-center">{{ article_obj.title }}</h4>
  5. <div class="content">
  6. {{ article_obj.content|safe }}
  7. </div>
  8. <div id="div_digg">
  9. <div class="diggit action">
  10. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  11. </div>
  12. <div class="buryit action">
  13. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  14. </div>
  15. <div class="clear"></div>
  16. <div class="diggword" id="digg_tips">
  17. </div>
  18. </div>
  19. <div class="clearfix"></div>
  20. <div class="comment">
  21. <p>评论列表</p>
  22. <ul class="comment_list list-group">
  23. {% for comment in comment_list %}
  24. <li class="list-group-item">
  25. <div>
  26. <a href="">#{{ forloop.counter }}楼</a>
  27. <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>
  28. <a href="">{{ comment.user.username }}</a>
  29. <a href="" class="pull-right"><span>回复</span></a>
  30. </div>
  31. <div>
  32. <p>{{ comment.content }}</p>
  33. </div>
  34. </li>
  35. {% endfor %}
  36. </ul>
  37. <p>发表评论</p>
  38. <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
  39. value="{{ request.user.username }}"></p>
  40. <div>
  41. <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
  42. </div>
  43. <input type="button" value="submit" class="btn btn-default comment_btn">
  44. </div>
  45. </div>
  46. {% csrf_token %}
  47. <script>
  48. // 点赞和踩灭
  49. $(".action").click(function () {
  50. {#hasClass() 方法检查被选元素是否包含指定的 class#}
  51. var is_up = $(this).hasClass("diggit");
  52. {#获取提示的span标签#}
  53. var _this = $(this).children("span");
  54. {#判断是否登录#}
  55. if ("{{ request.user.username }}") {
  56. $.ajax({
  57. url: "/digg/",
  58. type: "post",
  59. data: {
  60. is_up: is_up,
  61. article_id: "{{ article_obj.pk }}",
  62. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  63. },
  64. success: function (data) {
  65. console.log(data);
  66. console.log(typeof data);
  67. if (data.state) {
  68. //提交成功
  69. var val = _this.text(); //获取text值
  70. //在原有的基础上加1。注意:一定要进行类型转换
  71. _this.text(parseInt(val) + 1)
  72. } else {
  73. // 重复提交
  74. var val = data.handled ? "您已经推荐过!" : "您已经反对过!";
  75. $("#digg_tips").html(val);
  76. setTimeout(function () {
  77. $("#digg_tips").html("") //清空提示文字
  78. }, 1000)
  79. }
  80. }
  81. })
  82. } else {
  83. location.href = "/login/";
  84. }
  85. })
  86. // 提交评论
  87. $(".comment_btn").click(function () {
  88. {#评论内容#}
  89. var content = $("#comment_content").val();
  90. {#默认为空#}
  91. var pid = "";
  92. $.ajax({
  93. url: "/comment/",
  94. type: "post",
  95. data: {
  96. content: content,
  97. article_id: "{{ article_obj.pk }}",
  98. pid: pid,
  99. csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
  100. },
  101. success: function (data) {
  102. console.log(data);
  103. // 清空输入框的内容
  104. $("#comment_content").val("")
  105. }
  106. })
  107. })
  108. </script>
  109. {% endblock %}

刷新网页,效果如下:

Day82 点赞和踩灭,用户评论 - 图21

评论实时展示

上面提交评论后,网页不能立即看到,需要刷新之后,才能看到。这样体验不好!

查看博客园的评论,提交之后,会立即看到评论。此时不会显示楼层,只会显示评论内容!

那么只需要提交成功之后,操作DOM,在评论列表中,追加一段li标签,展示一下,就可以了!

数据获取问题

那么内容从何而来呢?

1.直接从html中获取相关数据

2.让服务器返回相关数据,从响应体中取数据。

针对这2种方式,我们选择第2种!

为什么不选择第一种呢?因为第一种是原始输入框中的值,那么存储到数据后之后。就不一定还是输入框的值!

服务器存储到数据库之前,会将提交的数据,做一次处理!

我们想要的效果,就是不论是DOM操作,追加一段html代码。还是刷新网页,加载评论。这2种方式,评论内容是一摸一样的!

所以,我们必须选择第二种方案,让服务器返回存储的值给ajax,ajax操作DOM,最加一段html代码,给用户展示!

数据展示

修改comment视图函数,返回3个变量给ajax

def comment(request):
    print(request.POST)
    if request.method == "POST":
        # 获取数据
        user_id = request.user.pk
        article_id = request.POST.get("article_id")
        content = request.POST.get("content")
        pid = request.POST.get("pid")
        # 生成评论对象
        with transaction.atomic():  # 增加事务
            # 评论表增加一条记录
            comment = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
            # 当前文章的评论数加1
            Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1)

        response = {"state": False}  # 初始状态

        if comment.user_id:  # 判断返回值
            response = {"state": True}

        #响应体增加3个变量
        response["timer"] = comment.create_time.strftime("%Y-%m-%d %X")
        response["content"] = comment.content
        response["user"] = request.user.username

        return JsonResponse(response)  # 返回json对象

    else:
        return HttpResponse("非法请求")

修改article_detail.html中的js代码,使用append最加一段li标签

{% extends "base.html" %}

{% block content %}
    <div class="article_info">
        <h4 class="text-center">{{ article_obj.title }}</h4>
        <div class="content">
            {{ article_obj.content|safe }}
        </div>
        <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">
            </div>
        </div>
        <div class="clearfix"></div>

        <div class="comment">
            <p>评论列表</p>
            <ul class="comment_list list-group">
                {% for comment in comment_list %}
                    <li class="list-group-item">
                        <div>
                            <a href="">#{{ forloop.counter }}楼</a>  
                            <span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>  
                            <a href="">{{ comment.user.username }}</a>
                            <a href="" class="pull-right"><span>回复</span></a>

                        </div>
                        <div>
                            <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>
            <div>
                <textarea name="" id="comment_content" cols="60" rows="10"></textarea>
            </div>
            <input type="button" value="submit" class="btn btn-default comment_btn">
        </div>

    </div>
    {% csrf_token %}
    <script>
        // 点赞和踩灭
        $(".action").click(function () {
            {#hasClass() 方法检查被选元素是否包含指定的 class#}
            var is_up = $(this).hasClass("diggit");
            {#获取提示的span标签#}
            var _this = $(this).children("span");
            {#判断是否登录#}
            if ("{{ request.user.username }}") {
                $.ajax({
                    url: "/digg/",
                    type: "post",
                    data: {
                        is_up: is_up,
                        article_id: "{{ article_obj.pk }}",
                        csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
                    },
                    success: function (data) {
                        console.log(data);
                        console.log(typeof data);
                        if (data.state) {
                            //提交成功
                            var val = _this.text();  //获取text值
                            //在原有的基础上加1。注意:一定要进行类型转换
                            _this.text(parseInt(val) + 1)
                        } else {
                            // 重复提交
                            var val = data.handled ? "您已经推荐过!" : "您已经反对过!";
                            $("#digg_tips").html(val);
                            setTimeout(function () {
                                $("#digg_tips").html("")  //清空提示文字
                            }, 1000)
                        }

                    }
                })
            } else {
                location.href = "/login/";
            }

        })

        // 提交评论
        $(".comment_btn").click(function () {
            {#评论内容#}
            var content = $("#comment_content").val();
            {#默认为空#}
            var pid = "";
            $.ajax({
                url: "/comment/",
                type: "post",
                data: {
                    content: content,
                    article_id: "{{ article_obj.pk }}",
                    pid: pid,
                    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
                },
                success: function (data) {
                    console.log(data);
                    {#获取3个值#}
                    var comment_time = data.timer;
                    var comment_content = data.content;
                    var comment_user = data.user;
                    {#组织li标签#}
                    var $li = ` <li class="list-group-item">
                                       <div>
                                           <span class="small">${comment_time}</span>  
                                           <a href="">${comment_user}</a>
                                       </div>
                                       <div>
                                           <p>${comment_content}</p>
                                       </div>
                                    </li>`;
                    {#追加到评论列表中#}
                    $(".comment_list").append($li);
                    // 清空输入框的内容
                    $("#comment_content").val("")
                }
            })

        })
    </script>

{% endblock %}

刷新网页,重新评论,效果如下:

评论可以实时展示了

Day82 点赞和踩灭,用户评论 - 图22

刷新网页,效果如下:

显示出楼层

Day82 点赞和踩灭,用户评论 - 图23