17.模板页面
文章展示的页面布局和左侧栏使用的个人站点样式, 右侧展示文章详细内容.
0. 新建base.html模板页面
1. 将个人站点页面代码复制到模板页面base.html中
2. 设置替换的区域
3. 个人站点页面继承模板页面
17.1 模板页面
base.html页面代码 ↓
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>个人站点</title>
<!--0. 动态获取静态文件路径 -->
{% load static %}
<!--1. 导入 jQuery js 文件-->
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<!--2. 导入 bootstrap css 文件-->
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<!--3. 导入 bootstrap js 文件-->
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
<!--4. 导入 sweetalert css 文件-->
<link rel="stylesheet" href="{% static 'bootstrap-sweetalert-master/dist/sweetalert.css' %}">
<!--5. 导入 sweetalert js 文件-->
<script src="{% static 'bootstrap-sweetalert-master/dist/sweetalert.min.js' %}"></script>
<!-- 导入个人样式-->
<link rel="stylesheet" href="/media/css/{{ blog_obj.site_theme }}/">
</head>
<body>
<!--6. 导入 导航条-->
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!--6.1 图标 展示当前用户-->
<a class="navbar-brand" href="#">{{ request.user.username }}</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<!--6.2 导航条链接1 -->
<li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
<!--6.2 导航条链接2 -->
<li><a href="#">文章</a></li>
<li class="dropdown">
<!--6.3 导航条链接3 -->
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">更多 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<ul class="nav navbar-nav navbar-right">
<!-- 6.4 判断是有用户登入 is_authenticated会自动加括号调用函数 CallableBool(True)/ CallableBool(False)-->
{% if request.user.is_authenticated %}
<!-- 6.5 导航条链接4 显示用户名称 -->
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<!-- 6.6 导航条链接5 -->
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
<ul class="dropdown-menu">
<!-- 6.7 修改密码链接 -->
<li><a href="" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
<!-- 6.8 修改头像链接 -->
<li><a href="#">修改头像</a></li>
<!-- 6.9 后台管理链接 -->
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<!-- 6.10 退出登入链接 -->
<li><a href="/logout/">退出登入</a></li>
</ul>
</li>
{% else %}
<!-- 6.11 注册链接 -->
<li><a href="/register/">注册</a></li>
<!-- 6.10 登入链接 -->
<li><a href="/login/">登入</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<!--9.页面布局 3 9 -->
<div class="container-fluid">
<div class="row">
<!--8.1 右侧菜单栏-->
<div class="col-md-3">
<!-- 右侧菜单栏 第一个表单 -->
<div class="panel panel-primary">
<div class="panel-heading">分类</div>
<div class="panel-body">
{% for sort_obj in sort_list %}
<p>
<a href="/{{ site_name }}/sort/{{sort_obj.sort__pk }}">
{{ sort_obj.sort__name }}({{ sort_obj.count_num }})
</a>
</p>
{% endfor %}
</div>
</div>
<!-- 右侧菜单栏 第二个表单 -->
<div class="panel panel-info">
<div class="panel-heading">标签</div>
<div class="panel-body">
{% for tag_obj in tag_list %}
<p>
<a href="/{{ site_name }}/tag/{{ tag_obj.tag__pk }}/">
{{ tag_obj.tag__name }}({{ tag_obj.count_num }})
</a>
</p>
{% endfor %}
</div>
</div>
<!-- 右侧菜单栏 第三个表单 -->
<div class="panel panel-default">
<div class="panel-heading">时间归档</div>
<div class="panel-body">
{% for year_month_obj in year_month_list %}
<!-- 手动拼接格式 年-月 -->
<p><a href="/{{ site_name }}/archiving/{{ year_month_obj.month|date:"Y-m" }}/">{{ year_month_obj.month|date:"Y年m月" }}({{ year_month_obj.count_num }})</a></p>
{% endfor %}
</div>
</div>
</div>
<!--设置替换的区域-->
{% block centent %}
{% endblock %}
</div>
</div>
</body>
</html>
17.2 继承模板页面
个人站点继承模板页面
{% extends 'base.html' %}
{% block centent %}
<!--8.2 文章内容 使用媒体对象展示-->
<div class="col-md-9">
<!--分页器-->
{% for queryset_obj in article_query_set %}
<!--媒体对象列表-->
<div class="media">
<h4 class="media-heading"><a href="">{{ queryset_obj.title }}</a></h4>
<div class="media-left media-middle">
<a href="#">
<img class="media-object" src="/media/{{ queryset_obj.blog.userinfo.avatar }}" alt="..."
width="80">
</a>
</div>
<div class="media-body">
{{ queryset_obj.desc }}
</div>
<br>
<div class="pull-right">
<p>
<!--博客表 反向解析两次拿到用户名-->
<span>pasted @ </span>
<span><a href="">{{ queryset_obj.blog.userinfo.username }} </a></span>
<!--文章发布时间-->
<span>{{ queryset_obj.create_time|date:'Y-m-d' }} </span>
<span class="glyphicon glyphicon-comment">评论({{ queryset_obj.comment_num }}) </span>
<span class="glyphicon glyphicon-thumbs-up">点赞({{ queryset_obj.up_num }}) </span>
<span class="glyphicon glyphicon-thumbs-down">点踩({{ queryset_obj.down_num }}) </span>
<span><a href="">编辑</a> </span>
</p>
</div>
<br>
<hr>
</div>
<nav aria-label="Page navigation"></nav>
{% endfor %}
{# 利用自动分页器 #}
{{ page_obj.page_html|safe }}
</div>
{% endblock %}
18. 文章详细展示
在点击文件的标题之后, 展示文章的详细内容.
url的设计格式
127.0.0.1:8000/用户/article/文章的主键
18.1 路由层
# 9. 文章详细展示
def article_desc(request, site_name, article_id):
# 9.1 通过文章id去获取文章
article_obj = models.Article.objects.filter(pk=article_id).first()
# 9.2 判断访问的文章是否存在
if not article_obj:
# .1 文章不存在返回404页面
return render(request, 'error.html')
# 9.3 文章展示页面
return render(request, 'article.html', locals())
18.2 文章展示页面
文章展示页面继承 base.html 模板页面.
# 9. 文章详细展示
def article_desc(request, site_name, article_id):
# 9.1 通过文章id去获取文章
article_obj = models.Article.objects.filter(pk=article_id).first()
# 9.2 站点样式
blog_obj = models.Blog.objects.filter(site_name=site_name).first()
# 9.3 判断访问的文章是否存在
if not article_obj:
# .1 文章不存在返回404页面
return render(request, 'error.html')
# 9.4 文章展示页面
return render(request, 'article.html', locals())
18.3 绑定文章id
主页 与 个人站点 的页面中点击文章标题展示文章的内容, 为文章标题的a标签设置路由.
<!--主页 文章标题 a标签绑定路由 主页没有站点名称的 通过反向解析新获取文章的绑定的站点名称-->
<a href="/{{ queryset_obj.blog.site_name }}/article/{{ queryset_obj.pk }}/">
{{ queryset_obj.title }}
</a>
<!--个人站点 文章标题 a标签绑定路由-->
<h4 class="media-heading"><a href="/{{ site_name }}/article/{{ queryset_obj.pk }}/">{{ queryset_obj.title }}</a></h4>
19. 制作包含标签
文章详细左侧表单需要数据才能展示出来.
获取数据的方法:
1. 可以将个人站点的分类归档代码复制一份. (不推荐)
2. 将左侧栏制作成inclusion_tag包含标签, base模板页面调用inclusion_tag包含标签即可.
制作inclusion_tag包含标签步骤:
1. 在app应用下创建一个templatetags目录
2. 在该文件内创建一个menu.py文件(命名无特殊要求)
3. 在tempales中创建一个left_menu.html文件, 包含标签生成页面的文件.
4. 将需要数据的三个表单html代码复制到 left_menu.html文件中.
3. 在py文件中写上固定代码导入需要的模块, 写业务逻辑, 最后将数据传递给指定的页面.
19.1 自定义模板语法
将个人站点的归档代码复制到menu.py文件中, 导入需要的模块.
将个人站点归档代码可以删除掉.
主表.objects.条件.annotate(别名=聚合函数('从表的字段')).valeu(需要展示的字段)
Count 操作的字段针对的是从表的主键, 主键有唯一标识, 只要涉及统计什么的数量就直接使用 '从表__id'.
Max, Min, Sum, Avg 操作它的字段.
# 0. 导入模板
from django import template
# 1. 生成一个模板对象
register = template.Library()
# 3. 导入需要的模块
from app01 import models
# 8.3.2 时导入
from django.db.models import Max, Min, Sum, Count, Avg
# 8.3.5 时导入 获取时间格式 年月 工具
from django.db.models.functions import TruncMonth
# 2. 装饰器中指定作用的页面, 在该页面中生成一个模板
@register.inclusion_tag('left_menu.html')
def left_menu(site_name): # 定义包含标签 左侧菜单
# 2.1 将 个人站点的归档代码复制到这里 构造模板需要的数据
# 8.2 判断访问的站点是否存在
blog_obj = models.Blog.objects.filter(site_name=site_name).first()
# 8.3 存在返回个人站点的数据对象
if blog_obj:
# 8.3.1 用户存在 获取该用户的所有文章封装的 query_set对象
article_query_set = models.Article.objects.filter(blog=blog_obj)
# 8.3.2 导入OMR聚合函数
# 8.3.4 查询当前用户的所有分类, 和分类下的文章数 使用聚合函数以类名分组 获取每个分组下文章的总数 统计主键 __id可以省略
sort_list = models.Sort.objects.filter(blog=blog_obj).annotate(
count_num=Count('article__pk')).values('pk', 'name', 'count_num')
print(article_query_set)
print(sort_list)
# 8.3.5 查询当前用户站点的所有标签及标签下的文章总数
tag_list = models.Tag.objects.filter(blog=blog_obj).annotate(
count_num=Count('article__pk')).values('pk', 'name', 'count_num')
# 8.3.6 导入 模块
# 8.3.7 按年月分类, 统计分类下的文章总数
year_month_list = models.Article.objects.filter(blog=blog_obj).annotate(month=TruncMonth('create_time')).values(
'month').annotate(count_num=Count('pk')).values('month', 'count_num')
# 将数据放回给模板页面
return locals()
19.2 获取数据生成模板
将左侧栏表单复制到这里left_menu.html页面中
menu.py生成的数据会被传递到这个页面中, 并渲染出信息.
<!--将左侧栏表单复制到这里-->
<!--个人站点样式-->
<link rel="stylesheet" href="/media/css/{{ blog_obj.site_theme }}/">
<!-- 右侧菜单栏 第一个表单 -->
<div class="panel panel-primary">
<div class="panel-heading">分类</div>
<div class="panel-body">
{% for sort_obj in sort_list %}
<p>
<a href="/{{ site_name }}/sort/{{sort_obj.pk }}">
{{ sort_obj.name }}({{ sort_obj.count_num }})
</a>
</p>
{% endfor %}
</div>
</div>
<!-- 右侧菜单栏 第二个表单 -->
<div class="panel panel-info">
<div class="panel-heading">标签</div>
<div class="panel-body">
{% for tag_obj in tag_list %}
<p>
<a href="/{{ site_name }}/tag/{{ tag_obj.pk }}/">
{{ tag_obj.name }}({{ tag_obj.count_num }})
</a>
</p>
{% endfor %}
</div>
</div>
<!-- 右侧菜单栏 第三个表单 -->
<div class="panel panel-default">
<div class="panel-heading">时间归档</div>
<div class="panel-body">
{% for year_month_obj in year_month_list %}
<!-- 手动拼接格式 年-月 -->
<p><a href="/{{ site_name }}/archiving/{{ year_month_obj.month|date:"Y-m" }}/">{{ year_month_obj.month|date:"Y年m月" }}({{ year_month_obj.count_num }})</a></p>
{% endfor %}
</div>
</div>
19.3 使用包含标签
在模板页面base.html 中调用包含标签.
<!-- 先导入创建的自动模板语法的文件 py文件的名字-->
{% load menu %}
<!--调用 函数的名字 后面跟参数 需要当前站点的名称 -->
{% left_menu site_name %}
19.4 文章展示问题
现在文章就是一个段落p标签展示, 没有任何的样式, 如果想让文章变的好看就因该使用html来写.
使用Typora写好之后到处HTML格式文件.
打开导出的文件, 开发开发者工具复制html写的博客代码.
进入后台管理, 将HTML代码文章写入数据库中.
文章页面中 使用safe过滤器将渲染成HTML页面.
<p>{{ article_obj.content|safe }}</p>
复制博客园的文章 复制div id="cnblogs_post_body" 的标签.
将所有的文章内容都替换成html格式的文章, 图片有防盗技术就无法展示.
20. 文章点赞点踩
2.1 点赞点踩按钮
复制博客园的点赞, 点赞的HTML代码.
1. 复制HTML标签
<div id="div_digg">
<div class="diggit" onclick="votePost(16081428,'Digg')">
<span class="diggnum" id="digg_count">0</span>
</div>
<div class="buryit" onclick="votePost(16081428,'Bury')">
<span class="burynum" id="bury_count">0</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips">
</div>
</div>
2. 将点击事件删除掉后, 复制到article.html页面中
3. 复制 div_digg diggit diggnum buryit burynum clear diggword 的样式, 一个一个复制下来.
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 125px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(//static.cnblogs.com/images/upup.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.diggnum {
font-size: 14px;
color: #075db3;
}
.buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url(//static.cnblogs.com/images/downdown.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.burynum {
font-size: 14px;
color: #075db3;
}
.clear {
clear: both;
}
.diggword {
margin-top: 5px;
margin-left: 0;
font-size: 12px;
color: #808080;
}
4. 博客园的图片有防盗技术, 将图片下载到本地.
https://static.cnblogs.com/images/upup.gif
https://static.cnblogs.com/images/downdown.gif
/* 修改图片的url */
background: url(/static/img/upup.jpg) no-repeat;
background: url(/static/img/downdown.jpg) no-repeat;
5. 在base.html的<heah>标签中设置css的替换区域.
<!--替换区域-->
{% block css %}
{% endblock %}
{% extends 'base.html' %}
{% block css %}
<style>
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 125px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/img/upup.jpg) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.diggnum {
font-size: 14px;
color: #075db3;
}
.buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url(/static/img/downdown.jpg) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.burynum {
font-size: 14px;
color: #075db3;
}
.clear {
clear: both;
}
.diggword {
margin-top: 5px;
margin-left: 0;
font-size: 12px;
color: #808080;
}
</style>
{% endblock %}
{% block centent %}
<div class="col-md-9">
<!--文章展示-->
<div>
<h1 class="text-center">{{ article_obj.title }}</h1>
<p>{{ article_obj.content|safe }}</p>
</div>
<!--点赞点踩区域-->
<div>
<div id="div_digg">
<!--点赞-->
<div class="diggit action ">
<span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
</div>
<!--点踩-->
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
</div>
<!--清除浮动-->
<div class="clear"></div>
<div class="diggword" id="digg_tips"><span id="msg" style="color: red"></span></div>
</div>
</div>
</div>
{% endblock %}
<!--清除浮动-->
<div class="clear"></div>
2.2 绑定事件
在base.html设置js独立的区域.
{% block js %}
<script>
// 给点赞点踩绑div标签添加一个 同一个属性action 然后绑定点击事件
$(".action").on('click', function () {
// 判断按下的按键是否具备diggit属性, 如果是就说明按下的按键是点赞, 否则就点踩
let is_Up = $(this).hasClass('diggit')
// console.log(is_Up, typeof is_Up) // true 'boolean'
// 发送ajax请求
$.ajax({
// 数据提交地址
url: '/up_or_down/',
// 请求方式
type: 'post',
// 提交的数据
data: {
// csrf检验, 值是一个input标签, 需要转为字符串格式 使用''包裹
"csrfmiddlewaretoken": '{{ csrf_token }}',
// 文章的主值
'article_id': '{{ article_obj.pk }}',
// 点赞还是点踩
is_Up: is_Up
},
// 回调函数
success: function (args) {
// 判断响应状态码
if (args.code === 200) {
// 展示返回信息
// 点成功的信息
$('#msg').text(args.right_msg)
// 刷新计数
$('#digg_count').text(args.up_num)
$('#bury_count').text(args.down_num)
}else{
// 操作失败的信息, 在没有登入的时候会返回一个a标签, 跳转到登入页面.
$('#msg').html(args.error_msg)
}
}
})
})
</script>
{% endblock %}
2.3 获取ajax数据
1. 判断请求方式
2. 判断用户是否登入, (文章可以是不登入的用户看, 点赞点踩则需要登入)
3. 判断用户是否给自己的文章点赞
4. 判断用户是否点击过这篇文件
5. 判断点赞还是点踩
逻辑有点先, 先写正确的逻辑在写错误的逻辑.
操作两张表:
Article 中使用F查询, 多点击的数据进行操作
UpAndDown 表中记录 那个用户给哪篇文章点赞还是点踩.
user_id article_id is_up (True为点赞/ False为点踩)
1 3 True
判断某个用户时候给某篇文章是否点击过. 点赞点踩表对象.filter (user_id and article_id) 满足这两个条件
说明点过了.
# 10. 点赞点赞
def up_or_down(request):
# 10.1 判断请求方式
if request.is_ajax():
# 组织返回的数据
back_dict = {'code': 200}
# 10.2 判断用户是否登入, (文章可以是不登入的用户看, 点赞点踩则需要登入)
if request.user.is_authenticated():
# 有用户登入执行的语句
# 10.3 获取请求中的数据
post_obj = request.POST
article_id = post_obj.get('article_id')
is_up = post_obj.get('is_Up')
# 布尔值问题, 前端js的True是字符串
# print(is_up, type(is_up)) # true <class 'str'>
# 可以使用json模块转为布尔值
import json
is_up = json.loads(is_up)
# print(is_up, type(is_up)) # True <class 'bool'>
# 10.4 判断用户是否给自己的文章点赞
# 通过文章id得到文章对象 文章对象跨表查询用户名
article_obj = models.Article.objects.filter(pk=article_id).first()
# 两个数据对象的用户值做比较
# 不是自己的文章
if article_obj.blog.userinfo.username != request.user.username:
# 10.5 判断用户是否点击过这篇文件
up_or_down_obj = models.UpAndDown.objects.filter(user_id=request.user.pk, article_id=article_obj)
# 没有点过
if not up_or_down_obj:
# 10.6 判断 这次是 点赞 还是 点踩
# 点赞操作
if is_up:
# 10.7 导入F模块
# 10.8 使用F查询更新文章表中 点击的数量
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
back_dict['right_msg'] = '点赞成功!'
# 点踩操作
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') - 1)
back_dict['right_msg'] = '点踩成功!'
# 10.9 将点击操作写入数据库
models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up) # 从新获取文章对象 , 传给后端, 刷新计数显示
article_obj = models.Article.objects.filter(pk=article_id).first()
back_dict['up_num'] = article_obj.up_num
back_dict['down_num'] = article_obj.down_num
# 点过就不能在点了
else:
# 组则报错信息
back_dict = {'code': 401, 'error_msg': '已经评论过不能在评论!'}
# 自己不能给自己点击
else:
# 组织错误信息
back_dict = {'code': 402, 'error_msg': '自己的文章此功能不可操作!'}
# 如果没有则在点赞点踩下面提示先登入用户
else:
# 组织返回的提示信息
back_dict = {'code': 403, 'error_msg': '<a href="/login/">请先登入账户!</a>'}
# 10.10 返回数据给回调函数
print(back_dict)
return JsonResponse(back_dict)
# 非法访问
else:
return HttpResponse('非法访问')
# 获取请求中的数据
is_up = request.POST.get('is_Up')
# 布尔值问题, 前端js的True是字符串
# print(is_up, type(is_up)) # true <class 'str'>
# 可以使用json模块转为布尔值
import json
is_up = json.loads(is_up)
# print(is_up, type(is_up)) # True <class 'bool'>
点击别人的文章
点击自己的文章
没有登入时点击
back_dict = {'code': 403, 'error_msg': '<a href="/login/">请先登入账户!</a>'}
2.4 点赞点踩计数
两种方法:
1. 后端写入数据之后, 将文章对象发给前端刷新计数.
2. 后端获取包含计数标签的值, 做加减计算, 再刷新显示.
采用方式1, 在后端已经计算过, 就没必要在后端计算了.
back_dict['up_num'] = article_obj.up_num
back_dict['down_num'] = article_obj.down_num
$('#digg_count').text(args.up_num)
$('#bury_count').text(args.down_num)
21. 评论
21.1 根评论
在文章展示页面article.html部署一个评论框.
<!--评论功能-->
<div>
<p><span class="glyphicon glyphicon-comment">发表评论</span></p>
<textarea name="comment" id="id_comment" cols="45" rows="10"></textarea>
<p>
<button id="id_btn">提交</button>
<span id="id_error"></span>
</p>
</div>
设置浮动让评论和点赞点赞不在同一行当中.
<!--点赞点踩区域 设置清除浮动-->
<div class="clearfix">
<div id="div_digg">
...
</div>div>
21.2 提交评论
ajax提交评论的信息, 那个用户给哪篇文章评论了什么.
// 给提交按钮绑定点击事件
$('#id_btn').on('click', function () {
// 获取大段文本款内的数据
let comment = $("[name='comment']").val()
// 提交数据
$.ajax({
// 提交地址
url: '/submit_comment/',
// 提交方式
type: 'post',
// 提交的数据
data: {
// 那个用户给哪篇文章评论了什么 (用户在auth中)
// 文章的id
article_id: '{{ article_obj.pk }}',
// 评论的内容
comment: comment,
// csrf
"csrfmiddlewaretoken": '{{ csrf_token }}',
},
success: function (args) {
alert(args)
}
})
})
21.3 将提交信息写入表中
先写正确的逻辑.
# 11. 提交评论
def submit_comment(request):
# 11.1 判断提交请求
if request.is_ajax():
# 11.2 判断用户是否登入
if request.user.is_authenticated():
# 11.3 获取提交的数据
post_obj = request.POST
article_id = post_obj.get('article_id')
comment = post_obj.get('comment')
print(article_id, comment)
# 11.4 将文章评论数写进文章表 将文章评论评论内容添加到评论表
models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
# 获取文章对象 前端需要评论数据
article_obj = models.Article.objects.filter(pk=article_id).first()
models.Comment.objects.create(user=request.user, article=article_obj, content=comment)
# 组织返回的信息
back_dict = {'code': 200, 'right_msg': '评论成功'}
# 用户没有登入 提示用户登入
else:
back_dict = {'code': '400', 'error_msg': '<a href="/login/">请先登入在评论</a>'}
return JsonResponse(back_dict)
else:
return HttpResponse('非法访问')
21.4 展示根评论
文章详细展示中, 获取评论的内容, 并渲染到前端.
# 9.5 评论表信息
comment_obj = models.Comment.objects.filter(article_id=article_id)
<!--评论展示-->
<div>
<!--标题面板-->
{% for obj in comment_obj %}
<div class="panel panel-default">
<!--评论的其他信息 几楼, 评论时间 评论者 回复a标签-->
<div class="panel-heading">
<p>
<span>{{ forloop.counter }}楼</span>
<span>{{ obj.comment_time|date:'Y-m-d h:i:s' }}</span>
<!--评论表通过外键获取用户名 -->
<span>{{ obj.user.username }}</span>
<span class="pull-right"><a href="#">回复</a></span>
</p>
</div>
<div class="panel-body">
<!--评论的信息-->
<div>
{{ obj.content }}
</div>
</div>
</div>
{% endfor %}
</div>
21.5 提交评论时渲染
1. 点击提交评论按键后清空评论框内的内容,
2. 渲染自己的评论
方式1: DOM临时渲染
方式2: 页面刷新render渲染
`模板代码${替换的内容}...`
success:function (args) {
// 判断响应状态
if (args.code === 200) {
// 评论成功, 将评论款内信息清空
$('#id_comment').val('')
// 将评论的内容临时展示到评论区
// 获取当前用户名
let username = '{{ request.user.username }}'
// 制作一个模板
let tem = `
<div class="panel panel-default">
<div class="panel-heading">
<p>
<span class='glyphicon glyphicon-comment'> ${username}</span>
</p>
</div>
<div class="panel-body">
<div>
${comment}
</div>
</div>
</div>
`
// 将模板添加进展示区
$('#id_exhibit').append(tem)
}
}else{
}
没有用户登入时渲染提示登入信息.
if (args.code === 200) {
...
}else{
// 错误的提示信息 只有一种错误用户没有登入
$('#id_error').html(args.error_msg)
21.6 子评论
点击回复书写字评论.
点击回复的时候, 鼠标聚焦到评论框中.
格式:
@用户名(换行)
|(<--光标在这)
* 为回复按钮绑定点击事件, 这个回复是在for循环内不能使用id值来标记, 可以使用类来标记. 之后查找该标签.
需要获取的信息, 评论用户的名字, 根评论的主键id, 可以在for循环中为标签自定义两个属性接收需要的值.
注意点 a标签不要有href, href='#'会跳转到页面头部.
<!-- 评论表通过user外键 获取到用户名, -->
<span class="pull-right"><a class="reply" username="{{ obj.user.username }}" parent_id="{{ obj.pk }}">回复</a></span>
// 给回复所在标签绑定事件
$('.reply').on('click', function (){
// 获取评论根评论的用户名
let username = $(this).attr('username')
// 获取根评论的主键id
let comment_id = $(this).attr('parent_id')
// 拼接@用户名 + 换行 + 聚集focus()
$('#id_comment').val('@' + username + '\n').focus()
})
自评与根评论都是同一个按钮提交数据.
区分根评论和子评论: 子评论的parent_id有值. parent_id指向评论表中的id.
{% block js %}
<script>
// 给点赞点踩绑div标签添加一个 同一个属性action 然后绑定点击事件
$(".action").on('click', function () {
// 判断按下的按键是否具备diggit属性, 如果是就说明按下的按键是点赞, 否则就点踩
let is_Up = $(this).hasClass('diggit')
// console.log(is_Up, typeof is_Up) // true 'boolean'
// 发送ajax请求
$.ajax({
// 数据提交地址
url: '/up_or_down/',
// 请求方式
type: 'post',
// 提交的数据
data: {
// csrf检验, 值是一个input标签, 需要转为字符串格式 使用''包裹
"csrfmiddlewaretoken": '{{ csrf_token }}',
// 文章的主值
'article_id': '{{ article_obj.pk }}',
// 点赞还是点踩
'is_Up': is_Up
},
// 回调函数
success: function (args) {
// 判断响应状态码
if (args.code === 200) {
// 展示返回信息
$('#msg').text(args.right_msg)
// 刷新计数
$('#digg_count').text(args.up_num)
$('#bury_count').text(args.down_num)
} else {
$('#msg').html(args.error_msg)
}
}
})
})
// 设置一个全局的变量 初始值值为 null
var parent_id = null
// 给提交按钮绑定点击事件
$('#id_btn').on('click', function () {
// 获取大段文本款内的数据
let comment = $("[name='comment']").val()
// 提交数据
$.ajax({
// 提交地址
url: '/submit_comment/',
// 提交方式
type: 'post',
// 提交的数据
data: {
// 那个用户给哪篇文章评论了什么 (用户在auth中)
// 文章的id
'article_id': '{{ article_obj.pk }}',
// 评论的内容
'comment': comment,
// 根评论的id, parent_id 要么为Null 要么有值, 如果有值则是一个子评论
'parent_id': parent_id,
// csrf
"csrfmiddlewaretoken": '{{ csrf_token }}',
},
success: function (args) {
// 判断响应状态
if (args.code === 200) {
// 评论成功, 将评论款内信息清空
$('#id_comment').val('')
// 将评论的内容临时展示到评论区
// 获取当前用户名
let username = '{{ request.user.username }}'
// 制作一个模板
let tem = `
<div class="panel panel-default">
<div class="panel-heading">
<p>
<span class='glyphicon glyphicon-comment'> ${username}</span>
</p>
</div>
<div class="panel-body">
<div>
${comment}
</div>
</div>
</div>
`
// 将模板添加进展示区
$('#id_exhibit').append(tem)
}
}
})
})
// 给回复所在标签绑定事件
$('.reply').on('click', function () {
// 获取评论根评论的用户名
let username = $(this).attr('username')
// 获取根评论的主键id 修改全局变量parent_id的值
parent_id = $(this).attr('parent_id')
// 拼接@用户名 + 换行 + 聚集focus()
$('#id_comment').val('@' + username + '\n').focus()
})
</script>
# 11. 提交评论
def submit_comment(request):
# 11.1 判断提交请求
if request.is_ajax():
# 11.2 判断用户是否登入
if request.user.is_authenticated():
# 11.3 获取提交的数据
post_obj = request.POST
article_id = post_obj.get('article_id')
comment = post_obj.get('comment')
parent_id = post_obj.get('parent_id')
print(article_id, comment)
# 11.4 将文章评论数写进文章表 将文章评论评论内容添加到评论表
models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
# 获取文章对象 前端需要评论数据
article_obj = models.Article.objects.filter(pk=article_id).first()
models.Comment.objects.create(user=request.user, article=article_obj, content=comment, parent_id=parent_id)
# 组织返回的信息
back_dict = {'code': 200, 'right_msg': '评论成功'}
# 用户没有登入 提示用户登入
else:
back_dict = {'code': '400', 'error_msg': '<a href="/login/">请先登入在评论</a>'}
return JsonResponse(back_dict)
else:
return HttpResponse('非法访问')
# 全端传null的值后端获取到是空字符串 ''
print(parent_id, type(parent_id)) # (空的) <class 'str'>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4pevzSlH-1649082690806)(https://s2.loli.net/2022/04/02/Emg7GxoijZlkR15.gif)]
子评论中@用户 不是评论的内容不因该写进数据中, 将这部分进行切除.
// 给提交按钮绑定点击事件
$('#id_btn').on('click', function () {
// 获取大段文本款内的数据
let comment = $("[name='comment']").val()
// 判断是否为子评论
if (parent_id) {
// 是子评论, 将@username 与 \n 切除
// 获取\n后一位字符的索引 需要+1
let IndexNum = comment.indexOf('\n') + 1
// 切除操作
comment = comment.slice(IndexNum) // 将索引值IndexNum之前的都切除, 保留之后的内容
}
...
21.7 子评论的渲染
格式:
@用户
信息
<div class="panel-body">
<!--评论的信息-->
<div>
<!--判断是否为子评论 判断parent_id的值-->
{% if obj.parent_id %}
<div>
<!--展示根评论的用户名-->
<p><span style="color: #aaff00">@{{ obj.parent.user.username }}</span></p>
</div>
{% endif %}
{{ obj.content }}
</div>
</div>
obj.parent.user.username
评论的数据对象.自关联的外键, 拿到根评论的数据对象.外键.username拿到根评论的用户名.
21.8 重置子评论全局变量
第一次写是子评论
第二次写是根评论
这时会发现都是子评论, 因为parent_id获取到值之后就一直有值,
不重置为null的话, 获取到了值, 之后永远都是子评论.
在回调函数的末尾重置parent_id为null
再次测试, 先写一个子评论, 在写一个根评论.
正常
文章展示代码
{% extends 'base.html' %}
{% block css %}
<style>
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 125px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/img/upup.jpg) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.diggnum {
font-size: 14px;
color: #075db3;
}
.buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url(/static/img/downdown.jpg) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.burynum {
font-size: 14px;
color: #075db3;
}
.clear {
clear: both;
}
.diggword {
margin-top: 5px;
margin-left: 0;
font-size: 12px;
color: #808080;
}
</style>
{% endblock %}
{% block centent %}
<div class="col-md-9">
<!--文章展示-->
<div>
<h1 class="text-center">{{ article_obj.title }}</h1>
<p>{{ article_obj.content|safe }}</p>
</div>
<!--点赞点踩区域-->
<div class="clearfix">
<div id="div_digg">
<!--点赞-->
<div class="diggit action ">
<span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
</div>
<!--点踩-->
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
</div>
<!--清除浮动-->
<div class="clear"></div>
<div class="diggword" id="digg_tips"><span id="msg" style="color: red"></span></div>
</div>
</div>
<!--评论展示-->
<div id="id_exhibit">
<!--标题面板-->
{% for obj in comment_obj %}
<div class="panel panel-default">
<!--评论的其他信息 几楼, 评论时间 评论者 回复a标签-->
<div class="panel-heading">
<p>
<span>{{ forloop.counter }}楼</span>
<span>{{ obj.comment_time|date:'Y-m-d h:i:s' }}</span>
<!--评论表通过外键获取用户名 -->
<span>{{ obj.user.username }}</span>
<!-- 评论表通过user外键 获取到用户名, -->
<span class="pull-right"><a class="reply" username="{{ obj.user.username }}"
parent_id="{{ obj.pk }}">回复</a></span>
</p>
</div>
<div class="panel-body">
<!--评论的信息-->
<div>
<!--判断是否为子评论 判断parent_id的值-->
{% if obj.parent_id %}
<div>
<!--展示根评论的用户名-->
<p><span style="color: #aaff00">@{{ obj.parent.user.username }}</span></p>
</div>
{% endif %}
{{ obj.content }}
</div>
</div>
</div>
{% endfor %}
</div>
<!--评论功能-->
<div>
<p><span class="glyphicon glyphicon-comment">发表评论</span></p>
<textarea name="comment" id="id_comment" cols="45" rows="10"></textarea>
<p>
<button id="id_btn">提交</button>
<span id="id_error"></span>
</p>
</div>
</div>
{% endblock %}
{% block js %}
<script>
// 给点赞点踩绑div标签添加一个 同一个属性action 然后绑定点击事件
$(".action").on('click', function () {
// 判断按下的按键是否具备diggit属性, 如果是就说明按下的按键是点赞, 否则就点踩
let is_Up = $(this).hasClass('diggit')
// console.log(is_Up, typeof is_Up) // true 'boolean'
// 发送ajax请求
$.ajax({
// 数据提交地址
url: '/up_or_down/',
// 请求方式
type: 'post',
// 提交的数据
data: {
// csrf检验, 值是一个input标签, 需要转为字符串格式 使用''包裹
"csrfmiddlewaretoken": '{{ csrf_token }}',
// 文章的主值
'article_id': '{{ article_obj.pk }}',
// 点赞还是点踩
'is_Up': is_Up
},
// 回调函数
success: function (args) {
// 判断响应状态码
if (args.code === 200) {
// 展示返回信息
$('#msg').text(args.right_msg)
// 刷新计数
$('#digg_count').text(args.up_num)
$('#bury_count').text(args.down_num)
} else {
$('#msg').html(args.error_msg)
}
}
})
})
// 设置一个全局的变量 初始值值为 null
var parent_id = null
// 给提交按钮绑定点击事件
$('#id_btn').on('click', function () {
// 获取大段文本款内的数据
let comment = $("[name='comment']").val()
// 判断是否为子评论
if (parent_id) {
// 是子评论, 将@username 与 \n 切除
// 获取\n后一位字符的索引 需要+1
let IndexNum = comment.indexOf('\n') + 1
// 切除操作
comment = comment.slice(IndexNum) // 将索引值IndexNum之前的都切除, 保留之后的内容
}
// 提交数据
$.ajax({
// 提交地址
url: '/submit_comment/',
// 提交方式
type: 'post',
// 提交的数据
data: {
// 那个用户给哪篇文章评论了什么 (用户在auth中)
// 文章的id
'article_id': '{{ article_obj.pk }}',
// 评论的内容
'comment': comment,
// 根评论的id, parent_id 要么为Null 要么有值, 如果有值则是一个子评论
'parent_id': parent_id,
// csrf
"csrfmiddlewaretoken": '{{ csrf_token }}',
},
success: function (args) {
// 判断响应状态
if (args.code === 200) {
// 评论成功, 将评论款内信息清空
$('#id_comment').val('')
// 将评论的内容临时展示到评论区
// 获取当前用户名
let username = '{{ request.user.username }}'
// 制作一个模板
let tem = `
<div class="panel panel-default">
<div class="panel-heading">
<p>
<span class='glyphicon glyphicon-comment'> ${username}</span>
</p>
</div>
<div class="panel-body">
<div>
${comment}
</div>
</div>
</div>
`
// 将模板添加进展示区
$('#id_exhibit').append(tem)
}else{
// 错误的提示信息 只有一种错误用户没有登入
$('#id_error').html(args.error_msg)
}
// 重置 parent_id的值
parent_id = null
}
})
})
// 给回复所在标签绑定事件
$('.reply').on('click', function () {
// 获取评论根评论的用户名
let username = $(this).attr('username')
// 获取根评论的主键id 修改全局变量parent_id的值
parent_id = $(this).attr('parent_id')
// 拼接@用户名 + 换行 + 聚集focus()
$('#id_comment').val('@' + username + '\n').focus()
})
</script>
{% endblock %}