BBS项目-点赞点踩功能
1 添加文章内容
先登录admin后台,给文章详情页加入文章内容。
浏览器上你看到的花里胡哨的页面,内部都是HTML(前端)代码。那我们的文章内容应该写什么???直接拷贝博客园中文章的html代码,copy outerhtml
添加完文章内容后,在article_detail.html 页面,要进行转义,前端才能显示出文章内容
{% extends 'base.html' %}
{% block content %}
<h1>{{ article_obj.title }}</h1>
<div class="content">
{{ article_obj.content|safe}}
</div>
{% endblock %}
2 拷贝点赞点踩样式
每篇文章详情下都有点踩点踩功能,因此我们要在详情页加上。
2.1 拷贝前端点赞点踩图标
article_detail.html
{% block content %}
<h1>{{ article_obj.title }}</h1>
<div class="content">
{{ article_obj.content|safe }}
</div>
{# 点赞点踩开始 #}
<div>
<div id="div_digg">
<div class="diggit" onclick="votePost(17047871,'Digg')">
<span class="diggnum" id="digg_count">3</span>
</div>
<div class="buryit" onclick="votePost(17047871,'Bury')">
<span class="burynum" id="bury_count">0</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips">
</div>
</div>
</div>
{# 点赞点踩结束 #}
{% endblock %}
2.1 拷贝前端点赞点踩css
仅仅拷贝了html 并没有点踩点踩的样式,我们还需要拷贝css代码
注意 digg 这个div 标签嵌套的几个标签的样式全部都要拷贝。
在base.html 模板页面,开一个css 的block
<head>
<meta charset="UTF-8">
{% load static %}
<script src="{% static 'jQuery-3.6.0.js' %}"></script>
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/mysetup.js' %}"></script>
{% block css %}
{% endblock %}
<title>Title</title>
</head>
在 article_detail.html 页面,就能写自己的css样式,把拷贝的点赞点踩css样式放到 block 块中的style标签中。
{% 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.cnblogs.com/images/upup.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.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;
}
.clear {
clear: both;
}
.diggword {
margin-top: 5px;
margin-left: 0;
font-size: 12px;
color: #808080;
}
</style>
{% endblock %}
由于有图片防盗链的问题,所以将图片直接下载到本地,url 改为本存放地址
.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;
}
.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;
}
3 点赞点踩逻辑
3.1 前端如何区分用户是点了赞还是点了踩
方式一:给点赞和点踩标签各自绑定一个事件,但两个标签对应的代码基本一样,仅仅是是否点赞点踩这一个参数不一样,写两个点击事件发送ajax请求,代码冗余。
方式二:给两个标签绑定一个事件,如下:
{# 点赞点踩开始 #}
<div>
<div id="div_digg">
<div class="diggit action" >
<span class="diggnum" id="digg_count">0</span>
</div>
<div class="buryit action" >
<span class="burynum" id="bury_count">0</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips">
</div>
</div>
</div>
{# 点赞点踩结束 #}
{% endblock %}
{% block js %} //在base.html开了一个js的block块,在 article_detail.html 页面,就能写自己的js代码
<script>
$('.action').click(function () {
alert($(this).hasClass('diggit'))
})
</script>
{% endblock %}
给两个标签都添加了action类,给所有的action类绑定点击事件, $(this)指代当前标签对象,点击的谁就代表谁,通过hasClass获取标签属性,如果获取到diggit属性,返回true,说明是点赞;反之,返回false,说明是点踩。
3.2 ajax请求
点击标签朝后端发送ajax请求,由于点赞点踩内部有一定的业务逻辑,所以后端单独开设视图函数处理。
点赞点踩的 url 放在个人站点与文章详情的url前面,因为这几个url有正则匹配,防止被个人站点或文章详情 url 截获。
path('up_or_down/', views.up_or_down),
re_path(r'^(?P<username>\w+)/$', views.site, name='site'),
re_path(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)', views.site),
re_path(r'^(?P<username>\w+)/article/(?P<article_id>\d+)', views.article_detail),
ajax朝点赞点踩视图发送请求,请求数据呢? 点赞点踩表要记录的是哪个用户
给哪篇文章
点了赞
还是点了踩
。点赞点踩视图的业务逻辑,首先是登录用户才能操作,后端能够获取到用户,因此ajax只用发送 文章id 和 点赞或是点踩(用布尔值表示)。
% block js %}
<script>
$('.action').click(function () {
let isUp = $(this).hasClass('diggit') //返回布尔值赋值给变量isUp
$.ajax({
url:'/up_or_down/',
type: 'post',
data: {
article_id: '{{ article_obj.pk }}', //文章详情页通过模板语法获取到文章id
is_up: isUp
},
success: function (args) {
alert(args)
}
})
})
</script>
{% endblock %}
3.3 后端视图函数逻辑
1.校验用户是否登陆
2.判断当前文章是否是当前用户自己写的(自己不能点自己的文章)
3.当前用户是否已经给当前文章点过了
4.操作数据库
先写出正确的业务逻辑:
def up_or_down(request):
# 点赞点踩视图只接收ajax请求
if request.is_ajax():
back_dic = {'code': 0, 'msg': ''}
# 1 校验用户是否登录
if request.user.is_authenticated:
article_id = request.POST.get('article_id')
is_up = json.loads(request.POST.get('is_up')) # 前端传过来的是json格式字符串(true/false),反序列化为布尔值
# 2 判断当前文章是否是用户自己写的
if models.Article.objects.filter(Q(pk=article_id), ~Q(blog__userinfo=request.user)):
# 3 判断当前用户是否已经给当前文章点过了;点赞点踩表里筛选,同时符合的数据说明当前这个用户给当前这个文章点过了
# 虚拟字段传对象,真实字段传被关联表的主键值
if not models.UpAndDown.objects.filter(user=request.user, article_id=article_id):
# 4 判断当前用户点了赞还是踩,从而决定给文章表的点赞点踩数普通字段加一
if is_up:
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1)
back_dic['msg'] = '点赞成功'
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num')+1)
back_dic['msg'] = '点踩成功'
# 5 操作点踩点踩表录入数据
models.UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=is_up)
return JsonResponse(back_dic)
第二个判断用了Q查询,筛选条件是文章 id 和 当前用户取反,如果能取到对象,说明当前文章不是当前用户自己写的。
或者直接根据文章id和当前用户取文章对象,再取反操作。
if not models.Article.objects.filter(pk=article_id, blog__userinfo=request.user):
或者根据文章id查询文章对象,根据文章对象查到作者,判断作者跟request.user是不是同一个人。
article_obj = models.Article.objects.filter(pk=article_id).first()
if not article_obj.blog.userinfo == request.user:
补全错误逻辑:
第一个判断用户登录的逻辑,返回错误信息,给“登录”套一个a标签,可以点击跳转到登录页面
def up_or_down(request):
if request.is_ajax():
back_dic = {'code': 0, 'msg': ''}
if request.user.is_authenticated:
article_id = request.POST.get('article_id')
is_up = json.loads(request.POST.get('is_up'))
if models.Article.objects.filter(Q(pk=article_id), ~Q(blog__userinfo=request.user)):
if not models.UpAndDown.objects.filter(user=request.user, article_id=article_id):
if is_up:
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1)
back_dic['msg'] = '点赞成功'
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num')+1)
back_dic['msg'] = '点踩成功'
models.UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=is_up)
else:
back_dic['code'] = 1
if models.UpAndDown.objects.filter(user=request.user, article_id=article_id, is_up=1):
back_dic['msg'] = '你已经点过赞了'
else:
back_dic['msg'] = '你已经点过踩了'
else:
back_dic['code'] = 1
back_dic['msg'] = '不能点赞或点踩自己的文章'
else:
back_dic['code'] = 1
back_dic['msg'] = '请先<a href="/login/">登录</a>'
return JsonResponse(back_dic)
3.4 前端信息提示
后端返回back_dic后,前端根据code判断,提示正确或错误的信息。
上图中,点赞点踩显示的数字是固定写死的,我们要实现点赞点踩过后,前端数字实时变动,即动态展示文章表中up_num和down_num两个字段。
ajax请求如下
{% block js %}
<script>
$('.action').click(function () {
let isUp = $(this).hasClass('diggit') //返回布尔值赋值给变量isUp
let $div = $(this) //把当前操作标签赋值给变量$div
$.ajax({
url:'/up_or_down/',
type: 'post',
data: {
article_id: '{{ article_obj.pk }}',
is_up: isUp
},
success: function (args) {
if (args.code===0){
$('#digg_tips').text(args.msg) //点赞或点踩成功,返回信息提示
let oldNum = $div.children().text(); //获取当前标签之前的点赞数或是点踩数
$div.children().text(Number(oldNum) + 1) //点赞点踩成功后把数字加1,oldNum是字符串,要先转成数字类型再加1
}else {
$('#digg_tips').html(args.msg) //返回错误信息提示,未登录的情况后端返回的a标签,因此用html显示
}
}
})
})
</script>
{% endblock %}
【推荐】国内首个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训练数据并当服务器共享给他人