第三篇:BBS侧边栏inclusion_tag、点赞和评论
第三篇:BBS侧边栏inclusion_tag、点赞和评论
目录
一、侧边栏inclusion_tag
1、url冲突
由于url方法第一个参数是正则表达式,所有当路由特别多的时候,可能会出现被顶替的情况,针对这种情况有两种解决方式。【找到绝望,才找到bug,我贼】
1.修改正则表达式
2.调整url方法的位置
比如:
2、模板的继承
由于文章详情页和个人站点基本一致,所以我们可以使用模板的继承。将个人站点页进行修改,统一继承base.html
中的样式即可。
3、侧边栏inclusion_tag
我们继承之后,会发现一个问题,就是在个人站点页面的分类、标签等内容可以正常显示,而在文章主页中的内容却不能正常显示,这是为什么?我们观看代码。
# 1 查询当前个人站点下所有的分类及分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list(
'name', 'count_num', 'pk')
"""
1、先过滤出个人站点下的所有分类
2、将个人站点过滤出的分类进行分组
3、统计出分组之后分类下的文章数【反向查询(直接表名小写+__, 就可以到达有外键的表)】
4、拿出分类名和分类下的文章数
"""
# print(category_list) # <QuerySet [('yangyi的分类一', 2), ('yangyi的分类二', 1), ('yangyi的分类三', 1)]>
# 2 查询当前个人站点下所有的标签及标签下的文章数
tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('tag',
'count_num',
'pk')
# print(tag_list) # <QuerySet [('yangyi的标签一', 1), ('yangyi的标签二', 2), ('yangyi的标签三', 1)]>
# 3 查询当前个人站点下按照年月统计所有的文章
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(count_num=Count('pk')).values_list('month', 'count_num')
# print(date_list) # <QuerySet [(datetime.date(2020, 6, 1), 1), (datetime.date(2021, 5, 1), 1), (datetime.date(2021, 2, 1), 1), (datetime.date(2021, 7, 1), 1)]>
就是因为使用了模板的继承之后,我们发现文章页面并不能提供给base页面中的侧边栏中需要的参数,那么我们该如何解决这个问题呢?
# 第一种方式:直接在文章详情函数中赋值即可 【简单粗暴,但是代码重复】
# 第二种方式:将侧边栏做成inclusion_tag
侧边栏的渲染需要传输数据才能渲染,并且该侧边栏在很多页面都需要使用
"""
步骤:
1.在应用下创建一个名字必须叫templatetags文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内先固定写两行代码
from django import template
register = template.Library()
# 自定义过滤器
# 自定义标签
# 自定义inclusion_tag
"""
代码如下所示。
"""将侧边栏做成inclusion_tag"""
from django import template
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
register = template.Library()
@register.inclusion_tag('left_menu.html')
def left_menu(username):
# blog = models.Blog.objects.filter(userinfo__username=username).first()
user_obj = models.UserInfo.objects.filter(username=username).first()
blog = user_obj.blog
# 1 查询当前个人站点下所有的分类及分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list(
'name', 'count_num', 'pk')
"""
1、先过滤出个人站点下的所有分类
2、将个人站点过滤出的分类进行分组
3、统计出分组之后分类下的文章数【反向查询(直接表名小写+__, 就可以到达有外键的表)】
4、拿出分类名和分类下的文章数
"""
# print(category_list) # <QuerySet [('yangyi的分类一', 2), ('yangyi的分类二', 1), ('yangyi的分类三', 1)]>
# 2 查询当前个人站点下所有的标签及标签下的文章数
tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('tag',
'count_num',
'pk')
# print(tag_list) # <QuerySet [('yangyi的标签一', 1), ('yangyi的标签二', 2), ('yangyi的标签三', 1)]>
# 3 查询当前个人站点下按照年月统计所有的文章
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(count_num=Count('pk')).values_list('month', 'count_num')
# print(date_list) # <QuerySet [(datetime.date(2020, 6, 1), 1), (datetime.date(2021, 5, 1), 1), (datetime.date(2021, 2, 1), 1), (datetime.date(2021, 7, 1), 1)]>
return locals()
使用方式,在base.html
中直接加载就可以使用。
"""base.py""" # 之后,只要页面能够传username就可以加载侧边栏
<div class="col-md-3">
{% load mytag %}
{% left_menu username %}
</div>
同理,我们也可以将个人站点的样式,做成inclusion_tag的形式,而且还保证了个人站点的各个页面的样式都是一致的【以前的普通导入,可能会导致样式不渲染(如果不传递blog的话)】,代码如下。
"""mytag.py"""
from django import template
register = template.Library()
# 个人站点显示同样颜色
@register.inclusion_tag('user_theme.html')
def user_theme(username):
blog = models.Blog.objects.filter(userinfo__username=username).first()
return locals()
"""user_theme.html"""
<link rel="stylesheet" href="/media/css/{{ blog.site_theme }}">
"""base.html"""
{% load mytag %}
{% user_theme username %}
{#<link rel="stylesheet" href="/media/css/{{ blog.site_theme }}">#}
二、点赞点踩功能
1、文章素材准备
浏览器上你看到的花里胡哨的页面,内部都是HTML(前端)代码
为了显示效果,我们可以拷贝别人的html代码进行演示 >>> copy outerhtml
使用admin后天管理进行添加,继承base.py的article_detail.py中进行直接渲染
"""
{#文章标签和内容开始#}
<h1>{{ article_obj.title }}</h1>
<div>{{ article_obj.content|safe }}</div>
"""
2、点赞点踩图标展示
我们下载博客园的图标,渲染到自己的页面中。【由于有图片防盗链的问题,所以将图片直接下载到本地,static文件夹中】
<!--html代码-->
{#点赞点踩开始#}
<div id="div_digg" class="clearfix">
<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" style="color: red;"></div>
</div>
{#点赞点踩结束#}
<!--css代码-->
{% 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.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
#div_digg .diggnum {
line-height: 1.5em !important;
}
.buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url('/static/img/downdown.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.clear {
clear: both;
}
element.style {
color: red;
}
</style>
{% endblock %}
渲染的样式如下。
3、点赞点踩功能实现
我们分为三个部分,前端点赞点踩按钮的渲染、js代码、后端代码
- article_detail.html 按钮渲染
{#点赞点踩开始#}
<div id="div_digg" class="clearfix">
<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" style="color: red;"></div>
</div>
{#点赞点踩结束#}
- article_detail.html js代码
{#点赞点踩js代码#}
// 给action块绑定点击事件
$('.action').click(function () {
// 定义一个变量记录是否点赞
let isUp = $(this).hasClass('diggit');
// 回调函数拿不到 $(this)了,必须在这里定义一下
let $div = $(this);
// alert(isUp) // boolean
// 发送ajax请求
$.ajax({
url: '/up_or_down/',
type: 'post',
data: {
'article_id': '{{ article_id }}',
'isUp': isUp,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (args) {
// 如果点赞点踩成功
if(args.code === 1000) {
// 临时的修改数据,因为ajax请求,不会直接刷新以前的数据,必须重新刷新页面,才会修改数据
let old_num = $div.children().text();
let new_num = Number(old_num) + 1; // 将字符串转化成数字进行相加
$div.children().text(new_num) // 为什么不能立马渲染出来呢?
$('#digg_tips').html(args.msg)
}else{
// 添加没有成功相关的消息
$('#digg_tips').html(args.msg)
}
}
})
})
- views.py
# 点赞点踩
def up_or_down(request):
"""
1 判断用户是否登录,
1.1 用户不能给自己点赞
1.2 点赞之后不能进行修改
2 操作数据库
"""
# 如果是ajax请求
if request.is_ajax():
if request.method == 'POST':
# ajax返回字典
back_dic = {'code': 1000, 'msg': ''}
# 获取到前端传过来的数据
article_id = request.POST.get('article_id')
is_up = request.POST.get('isUp')
is_up = json.loads(is_up) # 反序列化
# print(article_id, is_up) # 1 true
print(request.user)
# 如果用户登录【正向判断】不能这样判断用户是否登录,即便没有登陆,request.user依然是一个AnonymousUser
if request.user.is_authenticated():
# 如果用户给自己点赞
if request.user == models.UserInfo.objects.filter(blog__article__pk=article_id).first():
back_dic['code'] = 3000
back_dic['msg'] = '不能给自己点赞'
else:
# 这里还要判断用户之前是否点击
is_click = models.UpAndDown.objects.filter(user=request.user, article_id=article_id).first()
# 如果用户之前点击过
if not is_click:
# 如果用户点了赞
if is_up:
# 修改数据库数据【哪个用户给哪篇文章点了什么】
models.UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=is_up)
# 注意还要给文章表 同步更新
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
# 修改back_dic中的值
back_dic['msg'] = '点赞成功'
else:
# 修改数据库数据【哪个用户给哪篇文章点了什么】
models.UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=is_up)
# 注意还要给文章表 同步更新
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
# 修改back_dic中的值
back_dic['msg'] = '点踩成功'
else:
back_dic['code'] = 4000
back_dic['msg'] = '你之前已经点过了'
else:
print('用户没有登录')
back_dic['code'] = 2000
back_dic['msg'] = '请先<a href="/login/">登录</a>'
# 返回ajax数据
return JsonResponse(back_dic)
return HttpResponse('点赞点踩')
最终,可以实现一个点赞点踩功能,效果如下。
我给leichao的文章点赞。
如果没有登陆,进行点赞。还会直接跳转到登录界面。
4、点赞点踩总结
"""前端界面"""
1.拷贝博客园点赞点踩前端样式
html代码 + css代码
2.如何判断用户到底点击了哪个图标?
恰巧页面上只有两个图标,所以给两个图标标签添加一个公共的样式类,
然后给这个样式类绑定点击事件,再利用this指代的就是当前被操作对象,
利用hasClass判断是否有某个特定的类属性,从而判断出到底是两个图标中的哪一个
3.书写ajax代码朝后端提交数据
4.后端逻辑书写完毕之后,前端针对点赞点踩动作实现需要动态展示提示信息
5.前端点赞点踩数字自增1,【需要注意数据类型的问题】 Number(old_num) + 1
6.用户没有登陆,需要展示没有登陆提示,并且登陆可以点击跳转
html() |safe mark_safe()
"""后端逻辑"""
1.先判断用户是否登陆
request.user.authenticated()
2.再判断当前文字是否是当前用户自己写的
通过文章主键值获取文章对象,之后利用orm查询获取文章对象对应的用户对象与request.user比对
3.判断当前用户是否已经给当前文章点了
利用article_obj文章对象和request.user用户对象去点赞点踩表中筛选,数据如果有数据则点过,没有则没点
4.操作数据库,需要注意要同时操作两张表
# 前端发送过来的是否点赞是一个字符串 需要你自己转成布尔值或者用字符串判断
is_up = json.loads(is_up) F模块
"""
总结:在书写较为复杂的业务逻辑的时候,可以先按照一条线书写下去
之后再去弥补其他线路情况。
"""
三、根评论子评论功能
1、根评论
我们可以实现下面的效果,比如。
根评论有两步渲染方式
1.DOM临时渲染
2.页面刷新render渲染
我们给雷超的文章进行评论,效果如下。
我们点击提交按钮。
然后,我们刷新界面,才是真正的评论楼效果。
2、子评论
子评论方式
1.点击回复自动聚焦到评论框
2.将回复按钮所在的那一行评论人的姓名
@username
3.评论框内部自动换行
"""根评论子评论都是点击一个按钮朝后端提交数据的,即依靠parent_id进行辨识"""
我们换leichao进行登录,效果如下。
输入内容,显示效果如下。
刷新页面。
3、根评论子评论功能实现
那么该代码如何实现呢?
和上面一样,我们也是分为三个部分,前端渲染,js代码,后端代码。
- article_detail.html 前端渲染
{#评论楼渲染开始#}
<br>
<br>
<br>
<br>
<div class="row">
<div class="col-md-8">
<ul class="list-group">
{#循环每一篇文章的评论#}
{% for comment_obj in article_obj.comment_set.all %}
{#如果是子评论#}
{% if comment_obj.parent_id %}
<li class="list-group-item">
<span>#{{ forloop.counter }}楼 {{ comment_obj.comment_time|date:'Y-m-d h:i:s' }} {{ comment_obj.user.username }} 回复 {{ comment_obj.parent.user.username }}</span>
<span class="pull-right reply" username="{{ article_obj.blog.userinfo.username }}" comment_id="{{ comment_obj.pk }}"><a>回复</a></span>
<div style="padding-left: 20px">
<p>@ {{ comment_obj.parent.user.username }}</p>
{{ comment_obj.content }}
</div>
</li>
{% else %}
<li class="list-group-item">
<span>#{{ forloop.counter }}楼 {{ comment_obj.comment_time|date:'Y-m-d h:i:s' }} {{ comment_obj.user.username }}</span>
<span class="pull-right reply" username="{{ article_obj.blog.userinfo.username }}" comment_id="{{ comment_obj.pk }}"><a>回复</a></span>
<div style="padding-left: 20px">{{ comment_obj.content }}</div>
</li>
{% endif %}
{% endfor %}
{# <li class="list-group-item" id="id_tem_comment">#}
{# <span></span>#}
{# <div></div>#}
{# </li>#}
</ul>
</div>
</div>
{#评论楼渲染结束#}
{#评论功能开始#}
{% if request.user.is_authenticated %}
<div>
<p><span class="glyphicon glyphicon-comment"></span>发表评论</p>
<div>
<textarea name="comment" id="id_comment" cols="60" rows="10" ></textarea>
</div>
<button class="btn btn-primary" id="id_submit">提交评论</button>
<span style="color: red" id="errors"></span>
</div>
{% else %}
<li><a href="{% url 'register' %}">注册</a></li>
<li><a href="{% url 'login' %}">登陆</a></li>
{% endif %}
{#评论功能结束#}
- article_detail.html js代码实现
// 设置一个全局的parentID字段
let parentId = null;
{#评论功能js代码#}
$('#id_submit').click(function (){
// 像后端发送ajax请求[先不考虑子评论, 考虑 哪个用户给哪篇文章评论了哪些内容]
let conTent = $('#id_comment').val();
// 判断该评论是否有父评论[只有子评论才会走这条路]
if(parentId){
// 找到\n对应的索引 然后利用切片 但是前片顾头不顾尾 所以索引+1
let indexNum = conTent.indexOf('\n') + 1;
conTent = conTent.slice(indexNum); // 将indexNum之前的所有数据切除 只保留后面的部分
}
// 发送ajax请求
$.ajax({
url: '/comment/',
type: 'post',
data: {
'article_id': '{{ article_id }}',
'comment': conTent,
// 如果parentId没有值,那么就是null,后端存储null没有任何关系
'parent_id': parentId,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (args) {
if(args.code === 1000){
/*
// 动态将评论的内容,添加到页面中
$('#id_tem_comment').children().first().text('{{ request.user.username }}').next().text(conTent)
// 不管评论是否正确,评论完毕,之后内容都会消失
$('#id_comment').val('');
*/
// 也可以采用下面的方法
let userName = '{{ request.user.username }}';
let template = `
<li class="list-group-item" id="id_tem_comment">
<span>${userName}</span>
<div>${conTent}</div>
</li>
`;
// 将生成好的标签添加到ul标签内
$('.list-group').append(template);
// 不管评论是否正确,评论完毕,之后内容都会消失
$('#id_comment').val('');
// 重置parentId
parentId = null;
}
}
})
})
{#回复评论功能#}
$('.reply').click(function () {
// 1.将焦点放到textarea中,并将@添加到被回复名前面,跳转到下一行
let username = $(this).attr('username');
$('#id_comment').focus().val('@ ' + username + '\n');
// 拿到当前评论的父评论id
parentId = $(this).attr('comment_id');
})
- views.py
from django.db import transaction
# 评论功能
def comment(request):
if request.is_ajax():
if request.method == 'POST':
# 定义一个返回字典
back_dic = {'code': 1000, 'msg': ''}
# 拿到传过来的数据
article_id = request.POST.get('article_id')
comment_content = request.POST.get('comment')
parent_id = request.POST.get('parent_id')
print(comment_content)
# 后端也判断用户是否登录
if request.user.is_authenticated():
# 开启事务
with transaction.atomic():
# 直接将评论存到数据库中
models.Comment.objects.create(user=request.user, article_id=article_id, content=comment_content, parent_id=parent_id)
# 文章表中添加评论数+1
models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num')+1)
back_dic['msg'] = '评论成功'
else:
back_dic['code'] = 2000
back_dic['code'] = '评论失败'
return JsonResponse(back_dic)
return HttpResponse('评论功能')
4、评论实现总结
"""先写根评论"""
1.书写前端获取用户评论的标签
可能点赞点踩有浮动带来的影响 clearfix
2.点击评论按钮发送ajax请求
3.后端针对评论单独开设url处理,后端逻辑其实非常的简单
4.针对根评论涉及到前端的两种渲染方式
4.1 DOM操作临时渲染评论楼,需要用到模版字符串
4.2 页面刷新永久(render)渲染
后端直接获取当前文章对应的所有评论,传递给html页面即可,
前端利用for循环参考博客园评论楼样式渲染评论
4.3 评论框里面的内容需要清空
"""再考虑子评论"""
从回复按钮入手,点击回复按钮发生了哪些事
1.评论框自动聚焦 .focus()
2.评论框里面自动添加对应评论的评论人姓名 @username\n
# 思考:
1.根评论和子评论点的是同一个按钮
2.根评论和子评论的区别
其实之前的ajax代码只需要添加一个父评论id即可
3.点击回复按钮之后,我们应该获取到根评论对应的用户名和主键值
针对主键值,多个函数都需要用,所以用全局变量的形式存储
4.针对子评论内容,需要切割出不是用户写的,@username\n
5.后端parent字段本来就可以为空,所以传不传值都可以直接存储数据
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人