bbs项目(部分讲解)
文章详情和点赞点踩
左侧列表组使用inclusion_tag
实现
# 自定义标签
1. 在应用下创建templatetags包,必须是templatetags
2. 在templatetags中新建一个new_tag.py文件,py文件名随意。
from django import template
from blog.models import Classify, Tag, Article, UserInfo
from django.db.models import Count
from django.db.models.functions import TruncMonth
register = template.Library() # 生成一个Library对象 名字必须叫register
# 装饰函数
@register.inclusion_tag(filename='left.html', name='left') # 返回html片段,第一个参数是html文件
def left(name):
# user 当前根据用户名查到的用户,需要传入用户名,一定会有user
user = UserInfo.objects.filter(username=name).first()
# 需要标签名和统计标签内文章数
classify_res = Classify.objects.all().filter(blog=user.blog).values('id').annotate(
c=Count('article__id')).values_list('id', 'name', 'c')
tag_res = Tag.objects.all().filter(blog=user.blog).values('id').annotate(c=Count('article__id')).values_list(
'id', 'name', 'c')
date_res = Article.objects.all().filter(blog=user.blog).annotate(year_month=TruncMonth('create_time')).values(
'year_month').annotate(c=Count('id')).values_list('year_month', 'c')
return {'classify_res': classify_res, 'tag_res': tag_res, 'date_res': date_res, 'user':user} # 字典中的数据可以在left中使用
left.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="list-group">
<a href="#" class="list-group-item active">
我的标签
</a>
{% for foo in tag_res %}
<div class="list-group">
<a href="/{{ user.username }}/tag/{{ foo.0 }}.html"
class="list-group-item"><span>{{ foo.1 }}</span>
<span>({{ foo.2 }})</span></a>
</div>
{% endfor %}
</div>
<div class="list-group">
<a href="#" class="list-group-item active">
我的分类
</a>
{% for foo in classify_res %}
<div class="list-group">
<a href="/{{ user.username }}/classify/{{ foo.0 }}.html"
class="list-group-item"><span>{{ foo.1 }}</span>
<span>({{ foo.2 }})</span></a>
</div>
{% endfor %}
</div>
<div class="list-group">
<a href="#" class="list-group-item active">
随笔分类
</a>
{% for foo in date_res %}
<div class="list-group">
<a href="/{{ user.username }}/archive/{{ foo.0|date:'Ym' }}.html"
class="list-group-item"><span>{{ foo.0|date:'Y年m月' }}</span>
<span>({{ foo.1 }})</span></a>
</div>
{% endfor %}
</div>
</body>
</html>
base.html
在
base
中的左侧栅格使用inclusion_tag
需要先将自定义标签load过来,在使用标签并传入参数
<div class="col-md-2">
{% load new_tag %}
{% left name %}
</div>
-
渲染
site.html
页面时,返回的locals()
,所以可以用到site
函数的所有变量,site
函数的name是它的形参,是点击首页博主用户名跳转过来的,name
参数就是用文章取到的博主用户名,所以base可以用到name属性 -
将那么属性传到
new_tag
文件中的left
函数中,执行该函数。进行标签等数据的过滤,然后返回参数供left.html
文件使用,left.html
文件渲染完后,贴在base.html
的相应位置。views.py
中的
注意:添加templatetags
模块后 需要重启服务器 才可以使用标签
点赞点踩样式
- 直接拷贝博客园样式即可 主要除了
html
还有css
- 针对路由匹配
含有动态匹配的路由很多时候可能会出现顶替的情况
这个时候我们可以将简单的路由放前面 复杂的放后面 甚至修改匹配策略
点击首页文章和个人站点中的文章跳转到文章详情页面去
path('<str:name>/articles/<int:article_id>', views.article_detail),
views.py
def article_detail(request, name, article_id):
# 文章博主
user = UserInfo.objects.filter(username=name).first()
# 文章
article = Article.objects.filter(id=article_id).first()
if user and article:
return render(request, 'article.html', context={'user': user, 'article': article, 'name': name})
return render(request, 'error.html')
article.html
{% extends 'base.html' %}
{% block title %}
{{ article.title }}
{% endblock %}
{% block link %}
<link rel="stylesheet" href="/static/css/up.css">
{% endblock %}
{% block handle %}
<div class="my_nav">
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<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>
<a class="navbar-brand" href="#">{{ 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">
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<button type="button" class="btn btn-danger navbar-btn">管理</button>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div>
{% endblock %}
{% block crticle %}
<div>
<h3>{{ article.title }}</h3>
</div>
<div>
{{ article.content }}
</div>
<!--点赞点踩样式 直接copy-->
<div id="div_digg" class="pull-right">
<div class="diggit is_up">
<span class="diggnum" id="digg_count">{{ article.up_num }}</span>
</div>
<div class="buryit is_up">
<span class="burynum" id="bury_count">{{ article.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips">
</div>
</div>
{% endblock %}
/static/css/up.css
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/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/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;
}
点赞点踩前端js
<script>
// 将点赞点踩设置成一个点击事件
$('.is_up').click(function () {
let is_up = ($(this).hasClass('diggit'))//根据类属性来判断是点赞还是点踩
$.ajax({
url: '/is_up/', //处理点赞点踩的接口
type: 'post',
// 需要传谁给哪篇文章点赞还是点踩了 谁点赞可以不传 只要后端登陆了就可以查到
data: {
article_id:{{ article.id }},
is_up: is_up,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function (data) {
if (data.code == 100) {
// 如果成功,点赞数+1
$('#digg_count').html({{ article.up_num }} +1)
} else if (data.code == 103) {
// 如果失败,点踩数+1
$('#bury_count').html({{ article.down_num }} +1)
}
// 每次打印提示信息
$('.diggword').html(data.msg)
}
})
})
</script>
设置路由
# is_up 处理点赞相关路由
path('is_up/', views.is_up),
点赞点踩后端
def is_up(request):
article_id = request.POST.get('article_id')
is_up = json.loads(request.POST.get('is_up')) # 直接取出来是字符串 需要转成bool值
res = {'code': 100, 'msg': '点赞成功了'}
# 1. 判断当前用户是否登录
if not request.user.is_authenticated: # 只要用户登录就是当前用户 没有登陆就是匿名用户
res['code'] = 101
res['msg'] = '没有登录点击跳转<a href="/login/">登录</a>'
return JsonResponse(res)
# 2. 判断当前用户是否已经给这篇文章点过赞或踩了
if UpAndDown.objects.filter(user=request.user, article_id=article_id).first():
res['code'] = 102
res['msg'] = '已经点赞或点踩了'
return JsonResponse(res)
# 3. 用户是点赞还是点踩 存入点赞点踩表和文章表 并将点赞点踩数返回bbs
# 开启事务
with transaction.atomic():
UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=is_up)
if is_up:
# 文章表点赞数加1
Article.objects.filter(id=article_id).update(up_num=F('up_num') + 1)
else:
Article.objects.filter(id=article_id).update(down_num=F('down_num') + 1)
res['code'] = 103
res['msg'] = '点踩成功了'
return JsonResponse(res)
评论前端页面
<div class="comment-show">
<div style="margin-top: 60px">
<b>评论列表</b>
</div>
<ul class="list-group comment-ajax">
{% for foo in comment %}
<li class="list-group-item">
<div>
<span># {{ forloop.counter }} 楼</span> <span
style="margin-left: 20px">{{ foo.create_time|date:'Y-m-d H:i' }}</span>
<a href="/{{ foo.user.username }}/"><span
style="margin-left: 20px">{{ foo.user.username }}</span></a>
<div class="fa-pull-right">
<a class="reply" parent_id="{{ foo.article.id }}" username="{{ foo.user.username }}">回复</a>
</div>
</div>
{% if foo.parent_id %}
<p style="margin-top: 10px">@ {{ foo.parent.user.username }}</p>
<p>{{ foo.content|safe }}</p>
{% else %}
<p style="margin-top: 10px">{{ foo.content }}</p>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
<div>
<a href="">刷新页面</a>
</div>
<div style="margin-top: 60px">
<i class="fa fa-commenting-o" aria-hidden="true"></i>
<b>发表评论</b>
</div>
{% if request.user.is_authenticated %} <!--判断用户是否登录-->
<div>
<label for="content"></label>
<textarea name="" id="content" cols="170" rows="10"></textarea>
</div>
<div class="pull-right">
<button class="btn btn-info" id="comment" style="margin-bottom: 50px">提交评论</button>
</div>
{% else %}
<div>
<i class="fa fa-commenting-o" aria-hidden="true"></i> <span style="margin-left: 10px">登录后才能发表评论,立即 <a
href="/login/">登录</a> 或者 <a
href="/">逛逛</a> 首页</span>
</div>
{% endif %}
js代码
</script>
// 评论按钮点击事件
var parent_id = ''
$('#comment').click(function () {
// 取出评价内容 包括子评论和跟评论
var content = $('#content').val()
// 判断 如果是子评论要删除 @ 名字 换行
if (parent_id) {
console.log(content)
var i = content.indexOf('\n')//取到换行的索引
content = content.slice(i)//从索引位置往后切
}
$.ajax({
url: '/comment/',
type: 'post',
data: {// 谁给哪篇文章评论了什么 父评论的id
parent_id: parent_id,
article_id: {{ article.id }},
content: content,
csrfmiddlewaretoken: '{{ csrf_token }}' // 坑!!! 一定要加引号
},
success: function (data) {
console.log(data)
if (data.code == 100) {
$('#content').val('') //评论成功将评论区文字清空
var cur_name = data.people // 当前评论人
var content = data.content // 评论内容
var s = '' // 将评论拼接到评论列表中
if (data.comment_name) {//如果是子评论
var comment_name = data.comment_name
s = `
<li class="list-group-item" style="margin-top: 20px">
<i class="fa fa-commenting" aria-hidden="true"></i>
<b><span>${cur_name}:</span></b>
<div><span>@${comment_name}</span></div>
<div><span>${content}</span></div>
</li>`
} else {
s = `
<li class="list-group-item" style="margin-top: 20px">
<i class="fa fa-commenting" aria-hidden="true"></i>
<b><span>${cur_name}:</span></b>
<div>
<span>${content}</span>
</div>
</li>`
}
}
$('.comment-ajax').append(s)//追加到评论组的最后边 ajax提交跟评论和子评论
}
})
})
// 回复事件
$('.reply').click(function () {
parent_id = $(this).attr('parent_id')
console.log(parent_id)
var name = $(this).attr('username')
// 将 @ 名字 换行 加到输入框中
$('#content').val(`@${name}\n`).focus()//光标聚焦
})
</script>
评论后端
def comment(request):
res = {'code': 100, 'msg': '评论成功'}
if request.user.is_authenticated:
article_id = request.POST.get('article_id')
content = request.POST.get('content')
parent_id = request.POST.get('parent_id')
# 保存评论
# 开启事务
with transaction.atomic():
res_comment = Comment.objects.create(content=content, user=request.user, article_id=article_id,
parent_id=parent_id)
# 文章表中评论数加1
Article.objects.filter(id=article_id).update(comment_num=F('comment_num') + 1)
# 评论成功发送邮件
# 使用多线程
article_title = Article.objects.filter(pk=article_id).first().title
send = Article.objects.filter(pk=article_id).first().blog.userinfo.email
t = Thread(target=send_mail,
args=(f'[博客评论通知]Re:{article_title}', content, settings.EMAIL_HOST_USER, [send]))
t.start()
# send_mail(f'[博客评论通知]Re:{article_title}', content, settings.EMAIL_HOST_USER, ['xuxiaoxu152@163.com']) # subject, message, from_email, recipient_list,
# 返回给前端当前评论人 和评论内容
res['people'] = request.user.username
res['content'] = content
if parent_id: # 如果这是一条子评论 将他评论的这条评论的博主名返回
res['comment_name'] = res_comment.parent.user.username
return JsonResponse(res)
res['code'] = 101
res['msg'] = '未登录 不能评论'
return JsonResponse(res)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)