整体设计逻辑

  1. 1. 评论的构成要件
  2. 根评论:对文章的评论
  3. 子评论: 对评论的评论
  4. 区别:是否有父评论
  5. 2. 评论的功能设计
  6. 1. 构建评论样式
  7. 2. 提交根评论
  8. 3. 显示根评论
  9. ---render显示
  10. ---Ajax显示
  11. 4. 提交子评论
  12. 5. 显示子评论
  13. ---render显示
  14. ---Ajax显示
  15. 6. 评论树的显示
  16. 楼层结构 s
  17. 树形结构 一目了然

现在版本CNBLOG评论区样式分析

样式展示

文章评论 - 图1

样式分析

  1. 1. 信息分类
  2. 1. 动态展示的内容
  3. 作者头像,关注数,粉丝数;点赞,反对按钮;提交日期 作者 阅读量和评论量;上一篇文章,快捷跳转;
  4. 2. 静态展示的内容
  5. 发表评论,点赞或者点踩,评论框;
  6. 2. 权限区分
  7. 1. 作者可编辑,点击转入文章后台页面;收藏和举报为登录用户功能,普通用户不予展示;
  8. 2. 刷新评论和页面属于局部刷新,返回顶部也属于页面跳转,原地操作;
  9. 3. 订阅评论和加关注属于钩子,用于建立数据表关系
  10. 3. 技术探寻
  11. 1. 如何嵌入markdown编辑器?
  12. 2.

第一个基础功能—评论区样式构建

实现逻辑

  1. 注意点:
  2. 1. 不要将用户名写死,username放在div盒子的value中,以模板标签的方式
  3. 2. CSS样式引入图片仍然不能解耦,需在H5同文件中实现;
  4. 3. 如何清除浮动效果? bs中的clearfix,将其设置在dive中;

实现效果

文章评论 - 图2

后端代码

  1. <div class="comments">
  2. <p>发表评论</p>
  3. <p>昵称:<input type="text" id="xyz" class="author" disabled="disabled" size="50"
  4. value="{{ user.username }}"></p>
  5. <p>评论内容</p>
  6. <textarea name="" id="" cols="45" rows="8"></textarea>
  7. <button class="btn btn-default comment_btn"></button>
  8. </div>
  9. # CSS样式
  10. input.author {
  11. background-image: url('{% static 'font/icon_form.gif' %}');
  12. background-repeat: no-repeat;
  13. border: 1px solid #cccccc;
  14. padding: 4px 4px 4px 30px;
  15. width: 300px;
  16. font-size: 13px;
  17. background-position: 3px -3px;
  18. }

第二个功能—-为评论功能绑定Ajax事件

处理逻辑

  1. 1. 功能设计
  2. 原理与点赞类似,首先为评论按钮绑定一个Ajax事件,行为触发之后将数据交予view视图处理;
  3. 2. Ajax的数据结构
  4. 首先需要绑定标签,然后分别指定URL,POST方式,以及data,最后控制台打印数据;
  5. 3. 步骤
  6. 先绑定基本的Ajax事件;然后创建URL以及处理视图;
  7. 4. 注意事项
  8. 1. Ajax中是否传递csrfmiddlewaretoken会影响客户端与服务器的连接
  9. 5. 评论数据的构成要件
  10. 1. article ID 必传,用于定位;content必须;parent comment用于定位;
  11. 2. user即当前登录对象; create_time即为入库时间;

测试ajax基本功能

文章评论 - 图3

基础代码

  1. # 处理逻辑代码
  2. # 用于提交评论
  3. path('comment/', views.comment, name='comment'),
  4. def comment(request):
  5. print(request.POST)
  6. return HttpResponse("comment")
  7. # 事件绑定代码
  8. $(".comment_btn").click(function (){
  9. $.ajax({
  10. url:"/blog/comment/", {# 指定URL #}
  11. type:"post", {# 请求方式 #}
  12. data:{
  13. "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), {# 帮助进行安全性校验 #}
  14. },
  15. success:function (data){ {# 回调函数,成功处理请求之后 #}
  16. console.log(data)
  17. }
  18. })
  19. })

第三个功能—-数据传输

业务逻辑设计

  1. 1. 数据要件
  2. 1. article ID 必传,用于定位;content必须;parent comment用于定位;
  3. 2. user即当前登录对象; create_time即为入库时间;
  4. 2. 所作修改
  5. 1. uer.id无法从request中取得,使用模板标签从数据库抽取;
  6. 2. URL转到子app下;
  7. 3. 传输逻辑
  8. 1. Ajax绑定特定字段,其中评论内容从标签中抽取;
  9. 2. 包括CSRF_TOKEN在内的,模板标签抽取,标签抽取三类数据,通过post传递给view视图处理;
  10. 3. view视图分别接收数据和使用ORM存储数据;

关键代码

  1. def comment(request):
  2. print(request.POST)
  3. article_id = request.POST.get("article_id")
  4. pid = request.POST.get("pid")
  5. content = request.POST.get("content")
  6. user_id = request.POST.get("user_id")
  7. comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
  8. return HttpResponse("comment")
  1. $(".comment_btn").click(function (){
  2. var pid = ""
  3. var content = $("#comment_content").val();
  4. $.ajax({
  5. url:"/blog/comment/", {# 指定URL #}
  6. type:"post", {# 请求方式 #}
  7. data:{
  8. "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), {# 帮助进行安全性校验 #}
  9. "article_id":"{{ article_obj.pk }}",
  10. "content": content, {# 该数据从H5标签中取得 #}
  11. "user_id": "{{ user.nid }}",
  12. "pid":pid, {# 默认为空,根评论本身就是父评论 #}
  13. },
  14. success:function (data){ {# 回调函数,成功处理请求之后 #}
  15. console.log(data)
  16. {# 清空评论框内容 #}
  17. $("#comment_content").val("");
  18. }
  19. })
  20. })

效果

文章评论 - 图4

文章评论 - 图5

第三个功能—通过render显示评论

业务逻辑

  1. 1. 使用bootstrap搭建基本样式;
  2. 2. 使用a标签嵌套,预留跳转接口,用于回复和访问评论人的个人资料;
  3. 3. 从负责渲染页面的视图函数中,通过ORM查询数据库数据,从而实现H5页面的数据展示;
  4. 4. 设置中修改 USE_TZ 的值为 False,使用当地时间;

bootstrap模板代码

  1. <ul class="list-group">
  2. <li class="list-group-item">Cras justo odio</li>
  3. <li class="list-group-item">Dapibus ac facilisis in</li>
  4. <li class="list-group-item">Morbi leo risus</li>
  5. <li class="list-group-item">Porta ac consectetur ac</li>
  6. <li class="list-group-item">Vestibulum at eros</li>
  7. </ul>

关键代码 [view & H5]

  1. def article_detail(request, username, article_id):
  2. user = models.UserInfo.objects.filter(username=str(username)).first()
  3. blog = user.blog
  4. article_obj = models.Article.objects.filter(pk=article_id).first()
  5. comment_list = models.Comment.objects.filter(article_id=article_id)
  6. return render(request, "blog/article_detail.html",locals())
  1. <p>评论列表</p>
  2. <ul class="list-group comment_list">
  3. {% for comment in comment_list %}
  4. <li class="list-group-item">
  5. <div>
  6. <a href=""># {{ forloop.counter }}</a> &nbsp;&nbsp;
  7. <span> {{ comment.create_time|date:"Y-m-d H:i" }}</span> &nbsp;&nbsp;
  8. <a><span>{{ comment.user.username }}</span></a> &nbsp;&nbsp;
  9. <a href="" class="pull-right">回复</a> &nbsp;&nbsp;
  10. </div>
  11. <div class="comment-con">
  12. <p>{{ comment.content }}</p>
  13. </div>
  14. </li>
  15. {% endfor %}
  16. </ul>
  1. .comment-con{
  2. margin-top: 10px;
  3. text-align: center;
  4. }

最终效果

文章评论 - 图6

第三个功能—-Ajax局部刷新,实时展现评论提交行为

整体设计

  1. 1. opportunity
  2. 当提交完成评论之后,view视图向前端返回数据,此时应该返回提交的评论(相当于局部实时刷新)在Ajax的回调函数中设置插入评论的功能[分为数据抽取和插入标签数据];
  3. 2. information for display
  4. 1. 评论入库时间;评论人即登录者;评论内容;
  5. 3. How to get data?
  6. 1. 评论行为触发Ajax事件,view处理完数据,返回数据时取得数据;
  7. 4. the way to get data?
  8. 1. view中设置一个变量,类型为字典;将创建事件,用户,内容回传至Ajax的回调函数,由回调函数中的插入功能,一次性将三个数据导入进去;
  9. 2. 特别注意,create_time存储时必须序列化,因为它是一个对象而非数据;

关键代码

views.py[9-16]

  1. def comment(request):
  2. print(request.POST)
  3. article_id = request.POST.get("article_id")
  4. pid = request.POST.get("pid")
  5. content = request.POST.get("content")
  6. user_id = request.POST.get("user_id")
  7. comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
  8. response = {}
  9. response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
  10. response["username"] = comment_obj.user.username
  11. response["content"] = content
  12. return JsonResponse(response)

Ajax事件绑定

  1. success: function (data) { {# 回调函数,成功处理请求之后 #}
  2. console.log(data);
  3. var create_time = data.create_time;
  4. var username = data.username;
  5. var content = data.content;
  6. var s = `
  7. <li class= "list-group-item" >
  8. < div >
  9. <span> ${create_time}</span> &nbsp;&nbsp;
  10. <a href=""><span>${username}</span></a> &nbsp;&nbsp;
  11. </div>
  12. <div class="comment-con">
  13. <p>${content}</p>
  14. </div>
  15. </li>`;
  16. $("ul.comment_list").append(s);

最终效果

文章评论 - 图7

第四个功能—回复按钮事件

整体功能设计

  1. 1. 用于点击回复则跳转至评论区,重定位;
  2. 2. 重定位的时候,自动填充每一个父评论的用户,这里首先需要定位每一个父评论,通过为回复标签建立唯一username,进行唯一定位,尔后填充数据;
  3. 3. 自动填充数据后面加一个换行符用于光标自动换行;

给每层评论的回复下面增加一个用户名—用来子评论添加时定位父评论

文章评论 - 图8

点击后触发文本填充

文章评论 - 图9

关键代码

  1. <a class="pull-right reply_btn" username="{{ comment.user.username }}">回复</a> &nbsp;&nbsp;
  2. $(".reply_btn").click(function (){
  3. $('#comment_content').focus();
  4. var val = "@" +$(this).attr("username")+"\n";
  5. $('#comment_content').val(val);
  6. })

第五个功能—提交子评论

业务逻辑

  1. 1. 调试前提
  2. 1. 切换用户,便于实现提交子评论;
  3. 2. 未完善的点
  4. 1. 当前业务逻辑下,提交子评论会作为根评论被存储到数据库,出现的原因是PID默认为空,也就是所有的评论均为根评论;其次提交子评论时会将@username一起提交到数据库,因此需要对其进行拆分;提交成功后,除了删除评论内容,还需要重置PID
  5. 3. 处理逻辑
  6. 1. 判断PID为空的条件? 如果点击回复,PID应该等于同层级评论的用户ID,如果未点击回复直接提交,则PID认为空,作为父评论进行提交;
  7. 2. 提交成功才清空内容和重置PID
  8. 3. PID作为全局变量,提交之前就设置全局默认的PID;该逻辑绑定的是提交按钮;

默认状态下PID为空

文章评论 - 图10

为标签增加comment.pk属性便于给PID赋值

文章评论 - 图11

comment.pk的前端预期效果

文章评论 - 图12

绑定评论功能时对提交评论的标签所含的PID重新赋值

文章评论 - 图13

PID实时提交效果

文章评论 - 图14

需要清理的数据[@developer]

文章评论 - 图15

content进行切片

文章评论 - 图16

实现效果

文章评论 - 图17

提交完成之后清空评论框和重置pid

文章评论 - 图18

提交后自动删除评论内容以及重置PID

文章评论 - 图19

第六个功能—显示父评论和子评论

整体功能设计

  1. 1. 预期实现的效果
  2. 要求Ajax提交评论,显示局部刷新时就应该显示子评论和父评论;
  3. 2. 实现逻辑
  4. 1. 通过模板字符串设定条件,当评论的父评论ID存在时,才能继续显示一下子评论的对象[用户名名和评论内容];
  5. 2. 父评论插入在评论内容样式之前,回复标签之后[展示页面];

关键代码

  1. {% if comment.parent_comment_id %}
  2. <div class="pid_info well">
  3. <p>
  4. {{ comment.parent_comment.user.username }}:{{ comment.parent_comment.content }}
  5. </p>
  6. </div>

文章评论 - 图20

最终实现效果

文章评论 - 图21

第七个功能—评论树显示层级关系

业务逻辑

  1. 1. 为什么使用树形结构?
  2. 1. 层级分明,逻辑清晰;还有其他应用场景[权限:递归];
  3. 2. 应用逻辑
  4. 1. 新建一个区域,使用Ajax绑定事件,传递文章IDview视图拿到文章ID,返回主键,内容,父评论ID;注意序列化时变量的数据类型;
  5. 2. 该功能绑定的标签是 ```评论树```;获取到数据,也就是view视图传递过来的数据时,首先处理根评论;将数据填充在 ```comment_tree```标签中;
  6. 3. 其他理解
  7. 1. Ajax事件中指定的URL,仅仅是用于获取数据,充当中介[intermediary]的作用;

源码解析[19-20]

  1. class JsonResponse(HttpResponse):
  2. """
  3. An HTTP response class that consumes data to be serialized to JSON.
  4. :param data: Data to be dumped into json. By default only ``dict`` objects
  5. are allowed to be passed due to a security flaw before EcmaScript 5. See
  6. the ``safe`` parameter for more information.
  7. :param encoder: Should be a json encoder class. Defaults to
  8. ``django.core.serializers.json.DjangoJSONEncoder``.
  9. :param safe: Controls if only ``dict`` objects may be serialized. Defaults
  10. to ``True``.
  11. :param json_dumps_params: A dictionary of kwargs passed to json.dumps().
  12. """
  13. def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
  14. json_dumps_params=None, **kwargs):
  15. if safe and not isinstance(data, dict):
  16. raise TypeError(
  17. 'In order to allow non-dict objects to be serialized set the '
  18. 'safe parameter to False.'
  19. )
  20. if json_dumps_params is None:
  21. json_dumps_params = {}
  22. kwargs.setdefault('content_type', 'application/json')
  23. data = json.dumps(data, cls=encoder, **json_dumps_params)
  24. super().__init__(content=data, **kwargs)

关键代码

  1. def get_comment_tree(request):
  2. article_id = request.GET.get("article_id")
  3. ret = list(models.Comment.objects.filter(article_id=article_id).values("pk", "content", "parent_comment_id"))
  4. return JsonResponse(ret, safe=False)
  1. <script>
  2. $(".tree_btn").click(function (){
  3. $.ajax({
  4. url:"/blog/get_comment_tree/",
  5. type:"get",
  6. data:{
  7. article_id:"{{ article_obj.pk }}"
  8. },
  9. success:function (data){
  10. console.log(data);
  11. }
  12. })
  13. })
  14. </script>

点击 评论树实现的效果

文章评论 - 图22

第八个功能—展开评论树(根)

业务逻辑

  1. 1. 展现形式
  2. 1. 与楼层式展现的评论一样,仍然需要展示父子评论,但区别在于梯度式;
  3. 2. 实现方式
  4. 1. 首先处理view视图传送的数据,分别重新用变量接收;
  5. 2. 设置条件,父评论为空则将数据填充至 ```comment_tree```
  6. 3. 此外单独处理每一条评论时,首先有两个变量,索引值以及评论对象[自命名];
  7. 4. 构建标签字符串,用于展示数据[此段代码放置于Ajax的回调函数中];
  8. 5. 字符串拼接并且append填充即可;
  9. 3. 技巧
  10. 将目标样式首先放在目标位置,从前端测试显示效果,然后再放到Ajax的回调函数中;

关键代码

  1. $.each(data, function (index, comment_object) {
  2. var pk = comment_object.pk;
  3. var content = comment_object.content;
  4. var parent_comment_id = comment_object.parent_comment_id;
  5. if (!parent_comment_id) {
  6. var s = '<div comment_id="+pk+"><span>'+content+'</span></div>'
  7. $(".comment_tree").append(s);
  8. }
  9. })

实现效果

文章评论 - 图23

第八个功能—展开评论树(根以及子评论)

业务逻辑

  1. 1. 通过comment_id找到父评论标签,然后插入信息;
  2. 2. 插入数据时,通过标签指定,比如插入第一级的根评论,标签IDcomment_tree,而第二级子评论标签则为comment_id,因为第一级根评论定位仅仅需要找到标签,而子评论的定位需要找到对应的父评论ID,而这个ID已经定义在H5的标签中,用于唯一区别每一个标签,以及用于定位;

关键核心代码

  1. success: function (comment_list) {
  2. console.log(comment_list);
  3. $.each(comment_list, function (index, comment_object) {
  4. var pk = comment_object.pk;
  5. var content = comment_object.content;
  6. var parent_comment_id = comment_object.parent_comment_id;
  7. var s = '<div class="comment_item" comment_id=' + pk + '><span>' + content + '</span></div>';
  8. if (!parent_comment_id) {
  9. $(".comment_tree").append(s);
  10. } else {
  11. $("[comment_id=" + parent_comment_id + "]").append(s);
  12. }
  13. })
  14. }

为实现阶梯分层

文章评论 - 图24

最终实现效果

文章评论 - 图25

文章评论 - 图26

第九个功能—-对评论树的数据优化

业务逻辑

  1. 子评论跟随根评论,因此子评论对于根评论的顺序无感;而根评论是按照创建顺序排列和传递的;
  2. 通过数据库查询语句以主键排序,确保数据永远按照PK顺序排列;
  3. 去掉Ajax绑定评论树标签的局部刷新,直接改为全局刷新,每一次render直接能够看到结果;

代码优化

文章评论 - 图27

第十个功能—评论行为与文章评论计数同步—数据库优化[原子性]

知识补充(事务[transaction])

  1. 1. 概念
  2. 数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列;
  3. 事务由事务开始与事务结束之间执行的全部数据库操作组成;
  4. 2. 特性
  5. 1、原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。 [1]
  6. 2、一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。 [1]
  7. 3、隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。 [1]
  8. 4、持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。 [1]
  9. 3. 事务的ACID特性是由关系数据库系统(DBMS)来实现的;

关键代码

  1. with transaction.atomic():
  2. comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
  3. models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)

最终效果

文章评论 - 图28

文章评论 - 图29

第十一个功能—评论通知管理员

业务逻辑

  1. 1. 首先在settings文件导入邮件配置信息
  2. 2. 然后在评论区导入配置信息,同时开始配置邮件发送函数;
  3. 3. [优化]用异步多线程配置发送邮件的函数;
  4. 4. 参考文档: [https://cloud.tencent.com/developer/article/1745008]
  5. 5. 官方文档:[EIMIL配置] [https://docs.djangoproject.com/zh-hans/3.2/topics/email/]
  6. 6. 官方文档:[SETTINGS配置] [https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#email-host]

settings中的配置

  1. # 邮件相关配置
  2. EMAIL_HOST = "smtp.163.com"
  3. EMAIL_PORT = 25
  4. EMAIL_HOST_USER = '19970266104@163.com'
  5. EMAIL_HOST_PASSWORD = 'PGYSJZGFGBJCFSWV'
  6. EMAIL_FROM = 'caesartylor<admin@caesartylor.com>'
  7. EMAIL_USE_TLS = True

view.py

  1. def comment(request):
  2. """
  3. from django.db import transaction : 用于数据库事务同步
  4. """
  5. article_title = models.Article.objects.get(nid=article_id)
  6. # 发送邮件
  7. from django.core.mail import send_mail
  8. from whereabouts import settings
  9. import threading
  10. t = threading.Thread(target=send_mail, args=(
  11. "您的文章%s新增了一条评论内容"%article_title,
  12. content,
  13. settings.EMAIL_HOST_USER,
  14. ["419997284@qq.com"]
  15. ))
  16. t.start()
  17. return JsonResponse(response)

开启163邮箱的SMTP服务

文章评论 - 图30

最终效果

文章评论 - 图31

Solved Problems

1 jquery-3.6.0.min.js:2 GET http://127.0.0.1:8001/blog/get_comment_tree/?article_id=4 500 (Internal Server Error)

文章评论 - 图32

解决方法: 将HttpResponse 修改为 JsonResponse[函数名使用错误]

文章评论 - 图33

环境保存

  1. $.ajax({
  2. url: "/blog/get_comment_tree/",
  3. type: "get",
  4. data: {
  5. article_id: "{{ article_obj.pk }}"
  6. },
  7. success: function (comment_list) {
  8. console.log(comment_list);
  9. $.each(comment_list, function (index, comment_object) {
  10. var pk = comment_object.pk;
  11. var content = comment_object.content;
  12. var parent_comment_id = comment_object.parent_comment_id;
  13. var s = '<div class="comment_item" comment_id=' + pk + '><span>' + content + '</span></div>';
  14. if (!parent_comment_id) {
  15. $(".comment_tree").append(s);
  16. } else {
  17. $("[comment_id=" + parent_comment_id + "]").append(s);
  18. }
  19. })
  20. }
  21. })

data backup

views.py

  1. from django.shortcuts import render, HttpResponse, redirect
  2. from django.http import JsonResponse
  3. from django.contrib import auth
  4. from django.db import transaction
  5. from django.db.models import Count, F, Q
  6. from django.db.models.functions import TruncMonth
  7. import PIL, random, json
  8. from blog.models import UserInfo
  9. from blog.Myforms import UserForm
  10. from blog.utils.validCode import get_valide_code_img
  11. from blog import models
  12. def login(request):
  13. """
  14. 功能设计:验证码和用户信息的校验
  15. 不区分验证码大小写,统一转换为大写 uppercase
  16. auth.login:在请求中保留用户id和后端。这样,用户就不必在每次请求时都重新验证。请注意,匿名会话期间的数据集在用户登录时保留。
  17. auth.authenticate: 从client请求中提取数据,将数据与数据库进行匹配
  18. response: 字典,作为message传递提示信息
  19. request.POST: 包含所有前端传递的信息
  20. auth.login:保存单个用户的单次登录信息
  21. JsonResponse:Json化后端生成的提示信息
  22. """
  23. if request.method == "POST":
  24. response = {"user": None, "msg": None}
  25. user = request.POST.get("user")
  26. # print(user)
  27. pwd = request.POST.get("pwd")
  28. # 前端提交的验证码
  29. valid_code_one = request.POST.get("valid_code")
  30. valid_code = str(valid_code_one)
  31. # 后端生成的验证码,由get_validCode_img负责生成
  32. valid_code_str = request.session.get("valid_code_str")
  33. print(valid_code) # 测试后端在提交前端显示之前保存的验证码
  34. print(valid_code_str) # 测试前端POST请求提交时给出的验证码
  35. if valid_code.upper() == valid_code_str.upper():
  36. user = auth.authenticate(username=user, password=pwd) # 将前端提交的密码与后端MySQL存储的用户名与密码匹配
  37. if user:
  38. auth.login(request, user) # 匹配成功后则将其注册request.user==当前登录对象,存储当前登录对象
  39. response["user"] = user.username
  40. else:
  41. response["msg"] = "username or password error!"
  42. else:
  43. response["msg"] = "vali de code error!"
  44. return JsonResponse(response)
  45. return render(request, "blog/login.html")
  46. def get_validCode_img(request):
  47. """
  48. 调用blog/utils/valid_code程序生成代码
  49. 用request.session传递后端生成验证码
  50. """
  51. data = get_valide_code_img(request)
  52. # print(type(data))
  53. return HttpResponse(data)
  54. def index(request):
  55. """
  56. 需要导入整个models模块,然后导出所有的文章
  57. 文章数据从models提取出来,然后由views视图将数据渲染的时候传递给首页index,首页index再进行相关的渲染
  58. """
  59. article_list = models.Article.objects.all()
  60. return render(request, "blog/index.html", {"article_list": article_list})
  61. def registry(request):
  62. """
  63. UserForm验证提交的用户名,密码,邮箱等数据
  64. 用settings中的media处理头像文件
  65. 如果提交的数据错误,则由一个字典在原页面上显示提示信息
  66. """
  67. if request.is_ajax():
  68. # print(request.POST) # 输出结果 <QueryDict: {'csrfmiddlewaretoken': [
  69. # '1DRQx9q2UOwhlL3gRDMwhiGsxOvEmjrt6RgrnJVW4O1zhA6E2IjPAiAofmcfoXxl'], 'avatar': ['undefined']}>
  70. form = UserForm(request.POST) # 由UserForm做验证
  71. # print(form)
  72. response = {"user": None, "msg": None} # 用于前端交互,传递message
  73. if form.is_valid():
  74. response["user"] = form.cleaned_data.get("user") # 验证通过则会传递用户名
  75. # 生成一张用户记录 UserInfo不仅是自己设计的用户表,也是用户验证组件的那一张表
  76. # 该属性用于处理形成摘要的用户注册信息,不能用UserInfo.objects.create
  77. user = form.cleaned_data.get("user")
  78. pwd = form.cleaned_data.get("pwd")
  79. email = form.cleaned_data.get("email")
  80. avata_obj = request.FILES.get("avatar") # 指定前端提交时的字段名字,隶属于formdata对象
  81. extra = {}
  82. if avata_obj:
  83. extra["avatar"] = avata_obj
  84. UserInfo.objects.create_user(username=user, password=pwd, email=email,
  85. **extra) # avatar是UserInfo的field, avatar_obj是前端传递的文件
  86. else:
  87. # print(form.cleaned_data)
  88. # print(form.errors)
  89. response["msg"] = form.errors
  90. return JsonResponse(response)
  91. # 实例化对象,
  92. form = UserForm()
  93. # form为提示信息
  94. return render(request, "blog/registry.html", {"form": form})
  95. def logout(request):
  96. # from django.contrib import auth
  97. auth.logout(request) # 等同于request.session.flush
  98. # return redirect("templates/blog/login.html")
  99. return redirect("/login/")
  100. def home_site(request, username, **kwargs):
  101. """
  102. 个人站点视图函数
  103. """
  104. print("kwargs", kwargs)
  105. print("username",username)
  106. user = models.UserInfo.objects.filter(username=str(username)).first()
  107. if not user:
  108. return render(request, "blog/not_found.html")
  109. # 查询当前站点对象以及id
  110. blog = user.blog
  111. userid = user.nid
  112. nid = blog.nid # 用作原地跳转标签匹配
  113. # 当前用户或者当前站点对应的所有文章
  114. article_list = models.Article.objects.filter(user=userid)
  115. if kwargs:
  116. condition = kwargs.get("condition")
  117. param = kwargs.get("param") # 2012-12
  118. if condition == "category":
  119. article_list = article_list.filter(category__title__icontains=param)
  120. elif condition == 'tag': # 通过tags字段回到Tag
  121. article_list = article_list.filter(tags__title__icontains=param)
  122. else:
  123. year, month = param.split("-")
  124. article_list = article_list.filter(create_time__year=year,
  125. create_time__month=month)
  126. # 查询当前站点的每一个分类名称以及对应的文章数目; 能用Article_category是因为article包含了外键category
  127. # cate_list = models.Category.objects.filter(blog__nid=nid).values_list("title").annotate(c=Count("Article_category"))
  128. # 查询当前站点的每一个标签名称以及对应的文章数
  129. # tag_list = models.Tag.objects.values('pk').annotate(c=Count("article")).values_list("title", "c").filter(
  130. # blog_id=nid)
  131. # 查询当前站点每一个年月的名称以及对应的文章数---单表分组查询
  132. # 引入函数专门处理日期分组:from django.db.models.functions import TruncMonth
  133. # year_month = models.Article.objects.filter(user=nid).extra(
  134. # select={"y_m_date": "date_format(create_time,'%%Y-%%m')"}).values(
  135. # 'y_m_date').annotate(c=Count("nid")).values_list('y_m_date', 'c')
  136. return render(request, "blog/home_site.html", {"username":username,"blog":blog, "article_list":article_list})
  137. def get_classification_data(username):
  138. user = models.UserInfo.objects.filter(username=str(username)).first()
  139. blog = user.blog
  140. userid = user.nid
  141. nid = blog.nid # 用作原地跳转标签匹配
  142. cate_list = models.Category.objects.filter(blog__nid=nid).values_list("title").annotate(c=Count("Article_category"))
  143. tag_list = models.Tag.objects.values('pk').annotate(c=Count("article")).values_list("title", "c").filter(
  144. blog_id=nid)
  145. year_month = models.Article.objects.filter(user=nid).extra(
  146. select={"y_m_date": "date_format(create_time,'%%Y-%%m')"}).values(
  147. 'y_m_date').annotate(c=Count("nid")).values_list('y_m_date', 'c')
  148. return {"username":username,"blog": blog, "cate_list": cate_list, "tag_list": tag_list,"year_month": year_month}
  149. def article_detail(request, username, article_id):
  150. user = models.UserInfo.objects.filter(username=str(username)).first()
  151. blog = user.blog
  152. article_obj = models.Article.objects.filter(pk=article_id).first()
  153. comment_list = models.Comment.objects.filter(article_id=article_id)
  154. return render(request, "blog/article_detail.html",locals())
  155. def updown(request):
  156. """
  157. 用于处理点赞行为执行后前端通过POST请求发送过来的数据
  158. json用于反序列化
  159. from django.db.models import F 用于自加一
  160. from django.http import JsonResponse 用于返回字典
  161. """
  162. print(request.POST)
  163. article_id = request.POST.get("article_id")
  164. is_up = json.loads(request.POST.get("is_up")) # 'true'
  165. print(is_up)
  166. print(type(is_up))
  167. user_id = request.POST.get("user_id") # 由session提供
  168. print(user_id)
  169. obj = models.ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
  170. response = {"state":True}
  171. if not obj:
  172. articleupdown = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
  173. queryset = models.Article.objects.filter(pk=article_id)
  174. if is_up:
  175. queryset.update(up_count=F("up_count") + 1)
  176. else:
  177. queryset.update(down_count=F("down_count") + 1)
  178. else:
  179. response["state"] = False
  180. response["handled"] = obj.is_up
  181. return JsonResponse(response)
  182. def comment(request):
  183. """
  184. from django.db import transaction : 用于数据库事务同步
  185. """
  186. print(request.POST)
  187. article_id = request.POST.get("article_id")
  188. pid = request.POST.get("pid")
  189. content = request.POST.get("content")
  190. user_id = request.POST.get("user_id")
  191. article_title = models.Article.objects.get(nid=article_id)
  192. with transaction.atomic():
  193. comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)
  194. models.Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)
  195. response = {}
  196. response["create_time"] = comment_obj.create_time.strftime("%Y-%m-%d %X")
  197. response["username"] = comment_obj.user.username
  198. response["content"] = content
  199. # 发送邮件
  200. from django.core.mail import send_mail
  201. from whereabouts import settings
  202. import threading
  203. #
  204. # send_mail(
  205. # "您的文章%s新增了一条评论内容"%article_title,
  206. # content,
  207. # settings.EMAIL_HOST_USER,
  208. # ["419997284@qq.com"]
  209. # )
  210. t = threading.Thread(target=send_mail, args=(
  211. "您的文章%s新增了一条评论内容"%article_title,
  212. content,
  213. settings.EMAIL_HOST_USER,
  214. ["419997284@qq.com"]
  215. ))
  216. t.start()
  217. return JsonResponse(response)
  218. def get_comment_tree(request):
  219. article_id = request.GET.get("article_id")
  220. ret = list(models.Comment.objects.filter(article_id=article_id).order_by("pk").values("pk", "content", "parent_comment_id"))
  221. return JsonResponse(ret, safe=False)

article_detail.html

  1. <!DOCTYPE html>
  2. {% extends 'base.html' %}
  3. {% load static %}
  4. <html lang="en">
  5. {% block content %}
  6. <head>
  7. <meta charset="UTF-8">
  8. <title>文章详情页</title>
  9. </head>
  10. {% csrf_token %}
  11. <style>
  12. .diggit {
  13. background: url('{% static 'font/upup.gif' %}') no-repeat;
  14. /*background: url("data:image/gif;base64,R0lGODlhLgA0AMQAAP/00P/22vfqt/b29v/11f/45P/56P/34PjtwfXlqf/55v/11//00v/44vnwyfn5+f/22Pbnrf/21vny0Pn00//33vDw8P/12KioqP/10ufm61B1uv/33f/33P////H4+iH5BAAAAAAALAAAAAAuADQAAAX/oJeMZGmeaJqKQOu+cCzPcxLJmZvlcM4DPlosE4kwMsfdLrlEIo8MZvRJjTKbxaZyy+16v94iYUwum8/odDlDjgjU8Lh8LBAs7mQ8YTHmS/aAfHyBeHeCg3l1dxAQFwsXkBeMjJILEJaNko2bk4+Pl4x3knUBpaanqKmqq6sCCAEdsR2ws6mytbSwprGns766HQgIHByxxR3FxMi3y8nIycTO0MqywtTH0Mu30s/PytLRt9bYxt3Hstzf4eDUsePlzc7bzebr8e3Bw+Tx2uj06t/unct3rVzAeeC8pQtXTR+8bAP/1Qs4beA7bRCZLVT4j6E7hxjNaezIkZ1FkOQO//pLGG0hvosp5a3k17LjSwQVcurcybOnz58/ETg4QLSo0aNIkypV6sBBg6dPC0CdWkBqg6pTs1q1qpWrgwlVw4odS7as2bMTJihYy7at27dw48adQMGA3bt48+rdy5cv3b6AAwu2S8EDhcOIEytezLhxYw+QI0ueTLmy5cuYM2veDPmD58+gQ4seTbq0htIPBqguzbq159OjH2jQgEHDANe4RcMWPUCDBwsYHuQe/pr0bA8PMNwmnnt36N6/lYve8Jk69enVdYdO3ds38OWgr4sPv6G8+fOfndPGwB5DdPAf0J8vjz2+ds8eMFigPEB/aOvjVTfffOl91t96+0X2HYB58REYHmu7JWfZgp7RZ9+FDw4I2m79teebgrVVeGGAD5ZYoGcSVjbBBvCJ52CDDb4Y4Xq1SQYcBSWSaN91Ioa222+0WSDkkBiwmGOPJmJ4ooHsbVDkkw4I6CJ62cV4H2gPFHDABVsWAB9zEIIppnFjlrnhbGimqeaabLbp5mwhAAA7") no-repeat;*/
  15. float: left;
  16. width: 46px;
  17. height: 52px;
  18. text-align: center;
  19. cursor: pointer;
  20. margin-top: 2px;
  21. padding-top: 5px;
  22. }
  23. .buryit {
  24. float: right;
  25. margin-left: 20px;
  26. width: 46px;
  27. height: 52px;
  28. background: url('{% static 'font/downdown.gif' %}') no-repeat;
  29. /*background:url("data:image/gif;base64,R0lGODlhLgA0AMQAAP////39/fn5+fj4+Pb29vX19fPz8/Ly8vHx8fDw8O/v7+7u7u3t7ezs7Ovr6+rq6ujo6Ofn5+bm5uXl5ePj4+Li4uHh4dzc3NfX19HR0c/Pz6qqqqioqKenp4WFhYODgyH5BAQUAP8ALAAAAAAuADQAAAX/IKCNZGmeaJqKUeu+cCzP80jfeN5mGeT/wKBwSCTyisik0sd7OJ/QqHRKpWYw1ax268RgHGDuFkwuO6Res3rNbre9jbh8Tq/b73fMpcHo+xl2f3iDdRcXC4iJiol/jY2LkIh/hpGQjpcMlYuTh5qMmI+ekn6UogugoaKcpqeofqyrpq6vsqSdqrOZtX2luLOwtsC/uwy9nrm6vsW3x7nCy6zR0ogXFgrX2Nna29zd3RYWCeLj5OXm5+jo4Ajs7e7v8PHy8hQUB/f4+fr7/P399QYCChxIsKDBgwcBIlzIsKGBCQAmSJxIsaLFixgxAtjIsaPHjyBDihxJsqTJjQVS8KpcybKly5cwIcCcSbOmSpk2c+pMiZOlAHYOFDhAQGBnzp4rN2z4wIEp0wpGayIFkOABB3MdPBh4+aHrB5pfb64UYJUDBwgdFXCYsNJr2LZu265EKoBDSAoftqp8O5MvX6QFzJpFyzHBWbhuu+5NmVgxz7EcAoA0XHQxS78FMItdedZsgo4HOFRmnNmy3NKoH6sEwAHBxwEcFlxOzZc06b8sIwg2S5WD3tNfa5cOG3xuSwEECPjoPdq2c8uaVb9ES3l25uKmoxcAPBdt3ebB/Tpu6dV4TAChBUSVClOADw4Smq93yX1scvnzWy7Zzz9ICAA7") no-repeat;*/
  30. text-align: center;
  31. cursor: pointer;
  32. margin-top: 2px;
  33. padding-top: 5px;
  34. }
  35. .clear {
  36. clear: both;
  37. color: orangered;
  38. font-size: 14px;
  39. }
  40. .diggword {
  41. clear: both;
  42. color: palevioletred;
  43. font-size: 14px;
  44. }
  45. input.author {
  46. background-image: url('{% static 'font/icon_form.gif' %}');
  47. background-repeat: no-repeat;
  48. border: 1px solid #cccccc;
  49. padding: 4px 4px 4px 30px;
  50. width: 300px;
  51. font-size: 16px;
  52. background-position: 3px -3px;
  53. }
  54. </style>
  55. <div class="article_info">
  56. <h3 class="text-center">{{ article_obj.title }}</h3>
  57. <div class="content">
  58. {{ article_obj.content | safe }}
  59. </div>
  60. <div class="clearfix">
  61. <div id="div_digg">
  62. <div class="diggit action">
  63. <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
  64. </div>
  65. <div class="buryit action">
  66. <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
  67. </div>
  68. <div class="clear"></div>
  69. <div class="diggword" id="digg_tips">
  70. </div>
  71. </div>
  72. </div>
  73. <div class="comments list-group">
  74. <p class="tree_btn">评论树</p>
  75. <div class="comment_tree">
  76. </div>
  77. <script>
  78. {# 用于评论树的局部刷新,与view中的get_comment_tree函数关联 #}
  79. $.ajax({
  80. url: "/blog/get_comment_tree/",
  81. type: "get",
  82. data: {
  83. article_id: "{{ article_obj.pk }}"
  84. },
  85. success: function (comment_list) {
  86. console.log(comment_list);
  87. $.each(comment_list, function (index, comment_object) {
  88. var pk = comment_object.pk;
  89. var content = comment_object.content;
  90. var parent_comment_id = comment_object.parent_comment_id;
  91. var s = '<div class="comment_item" comment_id=' + pk + '><span>' + content + '</span></div>';
  92. if (!parent_comment_id) {
  93. $(".comment_tree").append(s);
  94. } else {
  95. $("[comment_id=" + parent_comment_id + "]").append(s);
  96. }
  97. })
  98. }
  99. })
  100. </script>
  101. </div>
  102. <p>评论列表</p>
  103. <ul class="list-group comment_list">
  104. {% for comment in comment_list %}
  105. <li class="list-group-item">
  106. <div>
  107. <a href=""># {{ forloop.counter }}</a> &nbsp;&nbsp;
  108. <span> {{ comment.create_time|date:"Y-m-d H:i" }}</span> &nbsp;&nbsp;
  109. <a><span>{{ comment.user.username }}</span></a> &nbsp;&nbsp;
  110. <a class="pull-right reply_btn" username="{{ comment.user.username }}"
  111. comment_pk="{{ comment.pk }}">回复</a> &nbsp;&nbsp;
  112. </div>
  113. {% if comment.parent_comment_id %}
  114. <div class="pid_info well">
  115. <p>
  116. {{ comment.parent_comment.user.username }}:{{ comment.parent_comment.content }}
  117. </p>
  118. </div>
  119. {% endif %}
  120. <div class="comment-con">
  121. <p>{{ comment.content }}</p>
  122. </div>
  123. </li>
  124. {% endfor %}
  125. </ul>
  126. <p>发表评论</p>
  127. <p>昵称:<input type="text" id="xyz" class="author" disabled="disabled" size="50"
  128. value="{{ user.username }}"></p>
  129. <p>评论内容</p>
  130. <textarea name="" id="comment_content" cols="45" rows="8"></textarea><br>
  131. <button class="btn btn-default comment_btn"></button>
  132. </div>
  133. <script>
  134. {# 用于提交评论,数据从标签中取得,交给views.comment存储,提交成功后触发对多次点赞的阻止和提示 #}
  135. $("#div_digg .action").click(function () {
  136. var is_up = $(this).hasClass("diggit");
  137. $obj = $(this).children("span");
  138. $.ajax({
  139. url: "/blog/updown/", {# 需要自行实现该路径,也就是实现点赞行为记录之后返回提示的页面#}
  140. type: "post", {# 提交数据,使用PIOST请求#}
  141. data: {
  142. "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), {# 帮助进行安全性校验 #}
  143. "is_up": is_up,
  144. "user_id": "{{ user.nid }}",
  145. "article_id": "{{ article_obj.pk }}", {# 仅需要传递文章ID,点赞人与评论人即为当前文章的登陆者 #}
  146. }, {# 哪一个用户,对哪一篇文章,进行什么行为 #}
  147. success: function (data) {
  148. console.log(data);
  149. if (data.state) {
  150. var val = parseInt($obj.text());
  151. $obj.text(val + 1);
  152. } {# 如果状态码为true,说明是第一次操作 #}
  153. else {
  154. var val = data.handled ? "您已经推荐过!" : "您已经反对过!"
  155. $("#digg_tips").html(val);
  156. setTimeout(function () {
  157. $("#digg_tips").html("")
  158. }, 1000)
  159. }
  160. }
  161. })
  162. });
  163. {# 从评论中获取数据,完成点击回复后跳转,输入内容作为子评论存入数据库的功能#}
  164. var pid = "";
  165. $(".comment_btn").click(function () {
  166. var content = $("#comment_content").val();
  167. if (pid) {
  168. var index = content.indexOf("\n");
  169. content = content.slice(index + 1)
  170. }
  171. $.ajax({
  172. url: "/blog/comment/", {# 指定URL #}
  173. type: "post", {# 请求方式 #}
  174. data: {
  175. "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), {# 帮助进行安全性校验 #}
  176. "article_id": "{{ article_obj.pk }}",
  177. "content": content, {# 该数据从H5标签中取得 #}
  178. "user_id": "{{ user.nid }}",
  179. "pid": pid, {# 默认为空,根评论本身就是父评论 #}
  180. },
  181. success: function (data) { {# 回调函数,成功处理请求之后 #}
  182. console.log(data);
  183. var create_time = data.create_time;
  184. var username = data.username;
  185. var content = data.content;
  186. var s = `
  187. <li class= "list-group-item" >
  188. < div >
  189. <span> ${create_time}</span> &nbsp;&nbsp;
  190. <a href=""><span>${username}</span></a> &nbsp;&nbsp;
  191. </div>
  192. <div class="comment-con">
  193. <p>${content}</p>
  194. </div>
  195. </li>`;
  196. $("ul.comment_list").append(s);
  197. {# 清空评论框内容 #}
  198. pid = "",
  199. $("#comment_content").val("");
  200. }
  201. })
  202. });
  203. {# 回复按钮事件 #}
  204. $(".reply_btn").click(function () {
  205. $('#comment_content').focus();
  206. var val = "@" + $(this).attr("username") + "\n";
  207. $('#comment_content').val(val);
  208. pid = $(this).attr("comment_pk");
  209. })
  210. </script>
  211. {% endblock %}
  212. </html>

article_detail.css

  1. {% load static %}
  2. .article_info .title{
  3. margin-bottom: 20px;
  4. }
  5. #div_digg {
  6. float: right;
  7. margin-bottom: 10px;
  8. margin-right: 30px;
  9. font-size: 12px;
  10. width: 125px;
  11. text-align: center;
  12. margin-top: 10px;
  13. }
  14. .diggit {
  15. float: left;
  16. width: 46px;
  17. height: 52px;
  18. /*background: url("data:image/gif;base64,R0lGODlhLgA0AMQAAP/00P/22vfqt/b29v/11f/45P/56P/34PjtwfXlqf/55v/11//00v/44vnwyfn5+f/22Pbnrf/21vny0Pn00//33vDw8P/12KioqP/10ufm61B1uv/33f/33P////H4+iH5BAAAAAAALAAAAAAuADQAAAX/oJeMZGmeaJqKQOu+cCzPcxLJmZvlcM4DPlosE4kwMsfdLrlEIo8MZvRJjTKbxaZyy+16v94iYUwum8/odDlDjgjU8Lh8LBAs7mQ8YTHmS/aAfHyBeHeCg3l1dxAQFwsXkBeMjJILEJaNko2bk4+Pl4x3knUBpaanqKmqq6sCCAEdsR2ws6mytbSwprGns766HQgIHByxxR3FxMi3y8nIycTO0MqywtTH0Mu30s/PytLRt9bYxt3Hstzf4eDUsePlzc7bzebr8e3Bw+Tx2uj06t/unct3rVzAeeC8pQtXTR+8bAP/1Qs4beA7bRCZLVT4j6E7hxjNaezIkZ1FkOQO//pLGG0hvosp5a3k17LjSwQVcurcybOnz58/ETg4QLSo0aNIkypV6sBBg6dPC0CdWkBqg6pTs1q1qpWrgwlVw4odS7as2bMTJihYy7at27dw48adQMGA3bt48+rdy5cv3b6AAwu2S8EDhcOIEytezLhxYw+QI0ueTLmy5cuYM2veDPmD58+gQ4seTbq0htIPBqguzbq159OjH2jQgEHDANe4RcMWPUCDBwsYHuQe/pr0bA8PMNwmnnt36N6/lYve8Jk69enVdYdO3ds38OWgr4sPv6G8+fOfndPGwB5DdPAf0J8vjz2+ds8eMFigPEB/aOvjVTfffOl91t96+0X2HYB58REYHmu7JWfZgp7RZ9+FDw4I2m79teebgrVVeGGAD5ZYoGcSVjbBBvCJ52CDDb4Y4Xq1SQYcBSWSaN91Ioa222+0WSDkkBiwmGOPJmJ4ooHsbVDkkw4I6CJ62cV4H2gPFHDABVsWAB9zEIIppnFjlrnhbGimqeaabLbp5mwhAAA7") no-repeat;*/
  19. text-align: center;
  20. cursor: pointer;
  21. margin-top: 2px;
  22. padding-top: 5px;
  23. }
  24. .buryit {
  25. float: right;
  26. margin-left: 20px;
  27. width: 46px;
  28. height: 52px;
  29. /*background:url("data:image/gif;base64,R0lGODlhLgA0AMQAAP////39/fn5+fj4+Pb29vX19fPz8/Ly8vHx8fDw8O/v7+7u7u3t7ezs7Ovr6+rq6ujo6Ofn5+bm5uXl5ePj4+Li4uHh4dzc3NfX19HR0c/Pz6qqqqioqKenp4WFhYODgyH5BAQUAP8ALAAAAAAuADQAAAX/IKCNZGmeaJqKUeu+cCzP80jfeN5mGeT/wKBwSCTyisik0sd7OJ/QqHRKpWYw1ax268RgHGDuFkwuO6Res3rNbre9jbh8Tq/b73fMpcHo+xl2f3iDdRcXC4iJiol/jY2LkIh/hpGQjpcMlYuTh5qMmI+ekn6UogugoaKcpqeofqyrpq6vsqSdqrOZtX2luLOwtsC/uwy9nrm6vsW3x7nCy6zR0ogXFgrX2Nna29zd3RYWCeLj5OXm5+jo4Ajs7e7v8PHy8hQUB/f4+fr7/P399QYCChxIsKDBgwcBIlzIsKGBCQAmSJxIsaLFixgxAtjIsaPHjyBDihxJsqTJjQVS8KpcybKly5cwIcCcSbOmSpk2c+pMiZOlAHYOFDhAQGBnzp4rN2z4wIEp0wpGayIFkOABB3MdPBh4+aHrB5pfb64UYJUDBwgdFXCYsNJr2LZu265EKoBDSAoftqp8O5MvX6QFzJpFyzHBWbhuu+5NmVgxz7EcAoA0XHQxS78FMItdedZsgo4HOFRmnNmy3NKoH6sEwAHBxwEcFlxOzZc06b8sIwg2S5WD3tNfa5cOG3xuSwEECPjoPdq2c8uaVb9ES3l25uKmoxcAPBdt3ebB/Tpu6dV4TAChBUSVClOADw4Smq93yX1scvnzWy7Zzz9ICAA7") no-repeat;*/
  30. text-align: center;
  31. cursor: pointer;
  32. margin-top: 2px;
  33. padding-top: 5px;
  34. }
  35. .clear {
  36. clear: both;
  37. }
  38. .comment-con{
  39. margin-top: 10px;
  40. text-align: center;
  41. }
  42. .comment_item{
  43. margin-left: 20px;
  44. }

settings.py

  1. # 用于部署的静态文件(绝对路径),可以通过python manage.py collectstatic收集
  2. # simpleui依赖的配置项
  3. # STATIC_ROOT = os.path.join(BASE_DIR, "static")
  4. # Static files (CSS, JavaScript, Images)
  5. # https://docs.djangoproject.com/en/3.2/howto/static-files/
  6. # 引用位于 STATIC_ROOT 中的静态文件时要使用的 URL。
  7. STATIC_URL = '/fly/'
  8. # FileSystemFinder 查找器时将穿越的额外位置,使用 Unix 风格的斜线
  9. # https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#std:setting-STATICFILES_DIRS
  10. # file path style : "C:/Users/user/mysite/extra_static_content"
  11. STATICFILES_DIRS = [
  12. "static",
  13. ]
  14. # 文件系统默认配置
  15. # https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#staticfiles-finders
  16. STATICFILES_FINDERS = [
  17. 'django.contrib.staticfiles.finders.FileSystemFinder',
  18. 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
  19. ]
  20. # 邮件相关配置
  21. EMAIL_HOST = "smtp.163.com"
  22. EMAIL_PORT = 25
  23. EMAIL_HOST_USER = '19970usde04@163.com'
  24. EMAIL_HOST_PASSWORD = 'PGYSJZGIPNJWWV'
  25. EMAIL_FROM = 'caesartylor<admin@caesartylor.com>'
  26. EMAIL_USE_TLS = True