整体思路分析

  1. 1. 文章的渲染
  2. 2. 点赞事件的绑定与存储

第一个设计URL以及文章样式的设计,渲染

整体规划思路

  1. 1. URL的设计
  2. 1. 样式: ```https://www.cnblogs.com/Liuzhijuan/p/15303535.html
  1. 2. 设计: 文章的访问必须是一个链接,由username + article + numbers构成
  2. 3. 特性: 不能原地跳转,需要设计新的页面
  1. 页面内容设计
    1. 左侧和顶部内容通过模板继承的方式取得,不再重复造轮子;继承自同一套样式,URL也不能改变,仍在当前页面;
    2. 详细设计:首先将home代码全部拷贝到base当中,从base中剪切article文章内容部分的代码,将col-md-9内容替换为模板标签 {% block content%},分别在home-site,以及article中引用,并且提取出home中的文章详情页,首先在home中引用base,然后用模板标签渲染文章详情页,article 和 home均共享base的页面,但home需要以模板标签的方式渲染文章详情页; ```

第一级效果—home-sitearticle均有左侧和顶部标签

整体设计思路

  1. 1. 页面内容设计
  2. 1. 左侧和顶部内容通过模板继承的方式取得,不再重复造轮子;继承自同一套样式,URL也不能改变,仍在当前页面;
  3. 2. 详细设计:首先将home代码全部拷贝到base当中,从base中剪切article文章内容部分的代码,将col-md-9内容替换为模板标签 {% block content%},分别在home-site,以及article中引用,并且提取出home中的文章详情页,首先在home中引用base,然后用模板标签渲染文章详情页,article home均共享base的页面,但home需要以模板标签的方式渲染文章详情页;

效果

个人站点

文章详情页的设计 - 图1

文章详情页

文章详情页的设计 - 图2

代码

base

  1. <!DOCTYPE html>
  2. {% load static %}
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>个人站点表</title>
  7. <link rel="stylesheet" href="{% static '/static/blog/bs/css/bootstrap.css' %}">
  8. <style>
  9. * {
  10. margin: 0;
  11. padding: 0;
  12. }
  13. .header {
  14. width: 100%;
  15. height: 60px;
  16. background-color: #d3dce6;
  17. }
  18. .header .title {
  19. font-size: 18px;
  20. font-weight: 100;
  21. line-height: 60px; /*设置上下居中*/
  22. color: lightpink;
  23. margin-left: 14px;
  24. margin-top: -10px;
  25. }
  26. .backend {
  27. float: right;
  28. color: darkslategrey;
  29. text-decoration: none; /*去掉多余的下划线*/
  30. font-size: 16px;
  31. margin-left: 10px;
  32. margin-top: 10px; /*文字下移*/
  33. }
  34. .pub_info {
  35. margin-top: 10px;
  36. color: springgreen;
  37. }
  38. </style>
  39. </head>
  40. <body>
  41. <div class="header">
  42. <div class="content">
  43. <p class="title">
  44. <span>{{ blog.title }}</span>
  45. <a href="" class="backend">管理</a>
  46. </p>
  47. </div>
  48. </div>
  49. <div class="container">
  50. <div class="row">
  51. <div class="col-md-3">
  52. <div class="panel panel-warning">
  53. <div class="panel-heading">我的标签</div>
  54. <div class="panel-body">
  55. {% for tag in tag_list %}
  56. <p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>
  57. {% endfor %}
  58. </div>
  59. </div>
  60. <div class="panel panel-danger">
  61. <div class="panel-heading">随笔分类</div>
  62. <div class="panel-body">
  63. {% for cate in cate_list %}
  64. <p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>
  65. {% endfor %}
  66. </div>
  67. </div>
  68. <div class="panel panel-success">
  69. <div class="panel-heading">随笔归档</div>
  70. <div class="panel-body">
  71. {% for ym in year_month %}
  72. <p><a href="/{{ username }}/archive/{{ ym.0 }}">{{ ym.0 }}({{ ym.1 }})</a></p>
  73. {% endfor %}
  74. </div>
  75. </div>
  76. </div>
  77. <div class="col-md-9">
  78. {% block content %}
  79. {% endblock %}
  80. </div>
  81. </div>
  82. </div>
  83. </body>
  84. </html>

home

  1. {% extends 'base.html' %}
  2. {% block content %}
  3. <div class="article_list">
  4. {% for article in article_list %}
  5. <div class="article_item clearfix">
  6. <h5><a href="">{{ article.title }}</a></h5>
  7. <div class="article-desc">
  8. {{ article.desc }}
  9. </div>
  10. <div class="small pub_info pull-right">
  11. <span>发布于&nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;&nbsp;
  12. <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }})&nbsp;&nbsp;&nbsp;
  13. <span class="glyphicon glyphicon-thumbs-up"></span>评论({{ article.up_count }})&nbsp;&nbsp;&nbsp;
  14. </div>
  15. </div>
  16. <hr>
  17. {% endfor %}
  18. </div>
  19. {% endblock %}

article

  1. <!DOCTYPE html>
  2. {% extends 'base.html' %}
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>文章详情页</title>
  7. </head>
  8. <body>
  9. </body>
  10. </html>

第二级效果—内容解耦

整体设计思路

  1. 2. 页面解耦的思路
  2. 1. H5代码挪到base模板中;
  3. 2. 需要解耦的对象: 页面中的顶部栏,左边的标签,分类,时间,顶部栏中的username,管理等文字;
  4. 3. 解耦的方法: 对于站点和articleH5文件,他们可以直接引用base模板;对于base中的H5文件来说,预留可渲染空间,并且再一次解耦功能,将文章列表,category,tags等以自定义标签的方式存储在单独app下的文件中,以模板标签的方式调用给base
  5. home文件中私有文章列表;自定义模板中
  6. 3. 特别注意:
  7. 1. 由于classification中包含了点击左栏跳转的功能,该效果依赖username,因此必须在自定义模板标签中返回该变量;
  8. 2. 由模板标签返回给baseblog数据不能影响head区域,因此需要view.py中的函数home_site使用contextz

views.py

  1. def get_classification_data(username):
  2. user = models.UserInfo.objects.filter(username=str(username)).first()
  3. blog = user.blog
  4. userid = user.nid
  5. nid = blog.nid # 用作原地跳转标签匹配
  6. cate_list = models.Category.objects.filter(blog__nid=nid).values_list("title").annotate(c=Count("Article_category"))
  7. tag_list = models.Tag.objects.values('pk').annotate(c=Count("article")).values_list("title", "c").filter(
  8. blog_id=nid)
  9. year_month = models.Article.objects.filter(user=nid).extra(
  10. select={"y_m_date": "date_format(create_time,'%%Y-%%m')"}).values(
  11. 'y_m_date').annotate(c=Count("nid")).values_list('y_m_date', 'c')
  12. return {"blog": blog, "cate_list": cate_list, "tag_list": tag_list,"year_month": year_month}
  13. def article_detail(request, username, article_id):
  14. user = models.UserInfo.objects.filter(username=str(username)).first()
  15. blog = user.blog
  16. article_obj = models.Article.objects.filter(pk=article_id).first()
  17. return render(request, "blog/article_detail.html",locals())

my_tags.py

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2021/9/17 15:44
  3. # @Author : 41999
  4. # @Email : 419997284@qq.com
  5. # @File : my_tags.py
  6. # @Project : row.js
  7. from django import template
  8. from blog import models
  9. from django.db.models import Count
  10. register=template.Library()
  11. @register.simple_tag
  12. def multi_tag(x, y):
  13. return x * y
  14. @register.inclusion_tag("blog/classification.html")
  15. def get_classification_style(username):
  16. user = models.UserInfo.objects.filter(username=str(username)).first()
  17. blog = user.blog
  18. userid = user.nid
  19. nid = blog.nid # 用作原地跳转标签匹配
  20. cate_list = models.Category.objects.filter(blog__nid=nid).values_list("title").annotate(c=Count("Article_category"))
  21. tag_list = models.Tag.objects.values('pk').annotate(c=Count("article")).values_list("title", "c").filter(
  22. blog_id=nid)
  23. year_month = models.Article.objects.filter(user=nid).extra(
  24. select={"y_m_date": "date_format(create_time,'%%Y-%%m')"}).values(
  25. 'y_m_date').annotate(c=Count("nid")).values_list('y_m_date', 'c')
  26. return {"blog": blog, "cate_list": cate_list, "tag_list": tag_list,"year_month": year_month}

base.html

  1. <!DOCTYPE html>
  2. {% load static %}
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>个人站点表</title>
  7. <link rel="stylesheet" href="{% static '/static/blog/bs/css/bootstrap.css' %}">
  8. <style>
  9. * {
  10. margin: 0;
  11. padding: 0;
  12. }
  13. .header {
  14. width: 100%;
  15. height: 60px;
  16. background-color: #d3dce6;
  17. }
  18. .header .title {
  19. font-size: 18px;
  20. font-weight: 100;
  21. line-height: 60px; /*设置上下居中*/
  22. color: lightpink;
  23. margin-left: 14px;
  24. margin-top: -10px;
  25. }
  26. .backend {
  27. float: right;
  28. color: darkslategrey;
  29. text-decoration: none; /*去掉多余的下划线*/
  30. font-size: 16px;
  31. margin-left: 10px;
  32. margin-top: 10px; /*文字下移*/
  33. }
  34. .pub_info {
  35. margin-top: 10px;
  36. color: springgreen;
  37. }
  38. </style>
  39. </head>
  40. <body>
  41. <div class="header">
  42. <div class="content">
  43. <p class="title">
  44. <span>{{ blog.title }}</span>
  45. <a href="" class="backend">管理</a>
  46. </p>
  47. </div>
  48. </div>
  49. <div class="container">
  50. <div class="row">
  51. <div class="col-md-3">
  52. {% load my_tags %}
  53. {% get_classification_style username %}
  54. </div>
  55. <div class="col-md-9">
  56. {% block content %}
  57. {% endblock %}
  58. </div>
  59. </div>
  60. </div>
  61. </body>
  62. </html>

article.html

  1. <!DOCTYPE html>
  2. {% extends 'base.html' %}
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>文章详情页</title>
  7. </head>
  8. <body>
  9. {% block content %}
  10. <h3 class="text-center">{{ article_obj.title }}</h3>
  11. {% endblock %}
  12. </body>
  13. </html>

home_site.html

  1. {% extends 'base.html' %}
  2. {% block content %}
  3. <div class="article_list">
  4. {% for article in article_list %}
  5. <div class="article_item clearfix">
  6. <h5><a href="">{{ article.title }}</a></h5>
  7. <div class="article-desc">
  8. {{ article.desc }}
  9. </div>
  10. <div class="small pub_info pull-right">
  11. <span>发布于&nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;&nbsp;
  12. <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }})&nbsp;&nbsp;&nbsp;
  13. <span class="glyphicon glyphicon-thumbs-up"></span>评论({{ article.up_count }})&nbsp;&nbsp;&nbsp;
  14. </div>
  15. </div>
  16. <hr>
  17. {% endfor %}
  18. </div>
  19. {% endblock %}

第三级效果

整体思路

  1. 1. 存在的问题
  2. 1. 如果撰写文章时仅保存字符,那么文章渲染不会有任何效果;
  3. 2. 如果撰写文章时带入H5标签,django禁止转义,因此只是输出H5标签 + 内容
  4. 2. 解决方案
  5. 在模板标签引用时,添加safe标记,格式为 * {{ article_obj.content | safe}} *
  6. 3. 细节
  7. 1. 模板标签引用作为逻辑代码,比如循环,content插入,只有一对花括号;而作为数据展示的引用时,则是两对花括号;

现实矛盾

文章详情页的设计 - 图3

safe的妙用

XSS攻击

  1. 原因:提交的文章中包含Js代码,在文章从服务器发送给用户的过程中,可能在浏览器渲染页面时控制用户浏览器;
  2. 设计上的考量:提交文章时不允许包含标签;

效果

文章详情页的设计 - 图4

第四级效果–H5与CSS的解耦

整体思路

  1. 1. 为什么解耦?
  2. 1. CSS样式与H5代码脱离,便于样式的CRU操作,关联了下一个部分的推荐图标链接;
  3. 2. 如何解耦
  4. 1. 将资源存储在 *static/blog/css/article_detail.css*
  5. 2. 后缀名记得改为对应格式,另外引用标签应该这样写 *<link rel="stylesheet" href="{% static 'static/blog/css/article_detail.css' %}">*
  6. 3. CSS引入图片无法适应解耦,模板标签必须包含在H5文件当中,因此CSS样式也需要转移到H5主文件中;

目录分布

  1. 1. H5主文件保存的位置
  2. 1. templates/blog/article_detail.html
  3. 2. templates/blog/home_site.html
  4. 2. CSS存放的位置
  5. 1. static/blog/css/article_detail.css
  6. 2. static/blog/css/home_site.css

样式构成

文章详情页的设计 - 图5

第四级效果—点赞行为模板

整体设计思路

  1. 1. 点赞记录包含那些要件?
  2. 点赞本身生成ID,点赞行为是boolean值,文章ID和用户ID
  3. mysql> desc blog_articleupdown;
  4. +------------+------------+------+-----+---------+----------------+
  5. | Field | Type | Null | Key | Default | Extra |
  6. +------------+------------+------+-----+---------+----------------+
  7. | nid | int | NO | PRI | NULL | auto_increment |
  8. | is_up | tinyint(1) | NO | | NULL | |
  9. | article_id | int | YES | MUL | NULL | |
  10. | user_id | int | YES | MUL | NULL | |
  11. +------------+------------+------+-----+---------+----------------+
  12. 4 rows in set (0.00 sec)
  13. 2. 行为记录方式
  14. 1. 技术基于Ajax
  15. 2. 两种行为的差异仅仅在于布尔值,所以为避免重复,将两种行为绑定同一个事件;
  16. 3. 数据分析
  17. 点赞人即为当前登录对象;

添加Jquery资源文件

文章详情页的设计 - 图6

关键逻辑代码

  1. <script>
  2. $("#div_digg .action").click(function(){
  3. var is_up = $(this).hasClass("diggit")
  4. alert(is_up);
  5. })
  6. </script>

最终实现效果

文章详情页的设计 - 图7

文章详情页的设计 - 图8

第五级效果—点赞行为保存与结果显示

整体设计思路

  1. 1. 设计原理
  2. 1. 在原有Ajax的设计基础上,绑定数据传输事件,采用POST请求传输,由于使用Unicode编码。所以需要JSON加载数据进行转义;
  3. 2. 实现步骤
  4. 1. 首先在Ajax中指定参数;URL中编写路径,views中处理数据;修改文章表中点赞的静态显示,用模板标签读取数据库;
  5. 3. 出现Ajax绑定事件失效的问题
  6. 1. reason: 正则匹配的URL规则对当前URL截胡,导致URL不能调用view视图,进而网页返回404
  7. 2. 解决的次级错误: 上次静态文件配置修改之后,每一个模板标签引用的资源路径全部都需要删除第一级目录的static
  8. 3. 第二个错误: 页面标签的图标我直接写进base模板中,再也不会有图标未响应的报错
  9. 4. 其他收获:
  10. 1 明白了数据流,首先URLVIEW的搭配只是为了响应一个结果,数据操作在VIEW中实现。绑定Ajax主要实现的效果是传递数据,是前端给VIEW通过POST请求传递的数据,VIEW拿到之后会将这些数据通过ORM查询写入数据库,实现动态展现信息。然而Ajax次一级的作用就是调用URL显示调用结果,比如HttpResponse返回的OK。它指定的URL无需原地跳转,即使是新的也没关系;
  11. 2. 排除错误的方式:在Ajax中可以设置 alert(123)直接在浏览器标签页显示提示,也可以直接在VIEW中使用if request.is_ajax():print('OK')直接打印效果,该字符串将会在页面中显示;
  12. 3. 前端调用的方式: 比如怎么知道POST提交的数据有没有提交成功呢?可以在浏览器的network中点击对应的响应,上面有POST提交的数据;

第二次思考

  1. 1. 首先需要实现的业务逻辑时用户点赞后,将点赞记录存储至数据库;
  2. 2. 怎么实现这个逻辑?首先为特定H5代码区绑定事件,Ajax功能依赖于jquery,用户点击之后提交特定数据[POST为载体],数据由view视图处理,处理时,通过ORM存储到数据库当中;
  3. 3. 首先页面渲染,点击操作触发事件,AjaxPOST请求处理为载体,将数据传输给VIEW视图,VIEW首先从post请求中取得数据,然后将数据存储到MySQL数据库;
  4. 4. 又遇到新的问题:前端提交的user_id信息为空;解决办法:通过ORM查询读取用户ID,数据从article_detail类中取得;
  5. 5. 业务改进:由于点赞个数显示来自数据库表article,存储点赞时并未更新前者,因此在view视图存储点赞记录的同时,使用ORM查询中的F方法实现自加1

其他功能设计

前端提交的user_id信息为空

文章详情页的设计 - 图9

新增传递的数据

文章详情页的设计 - 图10

前端的响应

文章详情页的设计 - 图11

后端存储的数据

文章详情页的设计 - 图12

实现文章表中点赞数的自增加

文章详情页的设计 - 图13

如何设置和获取CSRF TOKEN

后端设置

文章详情页的设计 - 图14

前端调试

文章详情页的设计 - 图15

数据传递中CSRF TOKEN的写法

文章详情页的设计 - 图16

重复点赞则不存储数据

实现逻辑

  1. 1. 后端的提示信息如何传递?
  2. response字典 + JsonResponse
  3. 2. 判断逻辑是什么?
  4. 使用ORM取得点赞表中本用户,文章ID的记录,若无,则添加点赞记录且修改文章表中的赞或者踩,若有,则修改状态码为false,且返回is_up的值;

后端关键代码[16-27]

  1. obj = models.ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  2. response = {"status":True}
  3. if not obj:
  4. articleupdown = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  5. queryset = models.Article.objects.filter(pk=article_id)
  6. if is_up:
  7. queryset.update(up_count=F("up_count") + 1)
  8. else:
  9. queryset.update(down_count=F("down_count") + 1)
  10. else:
  11. response["status"] = False
  12. response["handled"] = obj.is_up
  13. return JsonResponse(response)

文章详情页的设计 - 图17

重复点赞则出现提示信息

整体设计思路

  1. 1. 后端处理数据时,会首先判断是否为第一次操作点赞或者点踩,如果是则分别存储点赞记录和文章表中的赞或者踩的个数,然后给前端返回状态码statetrue;
  2. 2. 如果不是第一次操作点赞或者点踩操作,则后端view向前端返回状态码statefalse,同时回传点的是赞还是踩,也就是handle负责记录态度,然后前端绑定div ID: dig_words负责显示,如果点踩则返回“您已反对过”,如果点赞则返回“您已赞同过”;
  3. 3. 在提示信息展示后,限制时长1秒后消失,也就是用空HTML取代前者;

后端关键代码

  1. if(data.state){
  2. } {# 如果状态码为true,说明是第一次操作 #}
  3. else{
  4. if (data.handled){
  5. $("#digg_tips").html("您已经推荐过!")
  6. } else{
  7. $("#digg_tips").html("您已经反对过!")
  8. }
  9. setTimeout(function(){
  10. $("#digg_tips").html("")
  11. }, 1000)
  12. }

提示信息不出现的原因:没有指定ID

文章详情页的设计 - 图18

前端实现效果

文章详情页的设计 - 图19

点赞或者踩之后数据显示的局部刷新

设计思路

  1. 1. render会渲染整个页面,Ajax可以实现局部刷新;
  2. 2. 矛盾点?
  3. 当前设计下,进行点赞或者点踩之后,显示不会立即刷新,需要使用浏览器刷新按钮才会更新数据库值,这样做显然浪费时间;
  4. 3. 更优的解决方案?
  5. Ajax处理逻辑中,也就是第一次点赞成功后,对赞记录的显示,那部分H5代码的值直接加一或者减一,避免了直接读取数据库,但二者的值并不冲突;
  6. 4. 注意点
  7. 在提取H5标签中的数字时,需要将字符串类型转换为整数类型,不能直接对数字进行加减,否则会被认为是字符串拼接;

测试效果[方法:点赞后立马点踩,看数字是否变化,或者提示信息出现]

文章详情页的设计 - 图20

后端关键代码

  1. if(data.state){
  2. if(is_up){
  3. var val = parseInt($("#digg_count").text());
  4. $("#digg_count").text(val + 1);
  5. }else{
  6. var val = parseInt($("#bury_count").text());
  7. $("#bury_count").text(val + 1);
  8. }

代码优化[Code optimization]

优化思路

  1. 1. 对点赞或者点踩的动态刷新优化[4, 19, 12]
  2. 从父ID处开始分流,直接指定子IDspan标签,该分流执行的逻辑是从点击点赞或者点踩开始,而不是上一个版本代码后端回传数据从状态值判断开始;执行点赞或者点踩,且成功存储之后Ajax触发事件修改对应行为显示的值;
  3. 1. 对提示信息的优化 [23,24]
  4. 这里使用C语言中的逻辑选择运算符 条件 ?值 :值,条件满足显示者的值,条件不满足显示的值;

关键代码

  1. $("#div_digg .action").click(function(){
  2. var is_up = $(this).hasClass("diggit");
  3. $obj = $(this).children("span");
  4. $.ajax({
  5. url:"/blog/updown/", {# 需要自行实现该路径,也就是实现点赞行为记录之后返回提示的页面#}
  6. type:"post", {# 提交数据,使用PIOST请求#}
  7. data:{
  8. "csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val(), {# 帮助进行安全性校验 #}
  9. "is_up":is_up,
  10. "user_id": "{{ user.nid }}",
  11. "article_id":"{{ article_obj.pk }}", {# 仅需要传递文章ID,点赞人与评论人即为当前文章的登陆者 #}
  12. }, {# 哪一个用户,对哪一篇文章,进行什么行为 #}
  13. success:function (data){
  14. console.log(data);
  15. if(data.state){
  16. var val = parseInt($obj.text());
  17. $obj.text(val + 1);
  18. } {# 如果状态码为true,说明是第一次操作 #}
  19. else{
  20. var val = data.handled?"您已经推荐过!":"您已经反对过!"
  21. $("#digg_tips").html(val);
  22. setTimeout(function(){
  23. $("#digg_tips").html("")
  24. }, 1000)
  25. }
  26. }
  27. })
  28. })