bbs项目(部分讲解)

2023-02-18,,

文章评论业务完善

提交评论
评论框里面的内容会清空 然后页面会有一个临时评论样式出现 页面刷新才会出现评论楼样式 研究子评论特性
每个评论右侧都应该有回复按钮 点击就可以填写子评论
点击回复按钮具体动作:评论框中自动添加@+评论的人名并换行 聚焦
如何区分不同的回复按钮所对应的用户名
利用标签可以自定义属性直接携带对应的评论用户名即可 提交根评论和子评论点击的是同一个按钮 两者的区别与联系是什么
其实根评论和子评论的唯一区别就是是否有父评论的主键值
如何区分不同的回复按钮所对应的评论主键值
利用标签可以自定义属性直接携带对应的评论主键值即可 点击回复按钮发送子评论 页面不刷新的情况下 后续的评论全部成了子评论
原因是全局变量parentId没有清空导致的 每次提交评论都应该清空一下 针对子评论内中的@用户名换行 理论上不属于用户评论的内容 不应该记录到数据库
前端可以剔除 也可以在后端剔除 针对子评论的渲染 应该动态判断是否是子评论 如果是应该加上评论的目标用户名

注意:针对评论的渲染也可以分页 也可以做根评论与子评论的集合操作(分类)

后台管理

base.html

    <div class="container-fluid">
<div class="row">
<div class="col-md-2">
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
aria-expanded="false" aria-controls="collapseOne" class="collapsed">
博客后台
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse" role="tabpanel"
aria-labelledby="headingOne" aria-expanded="false" style="height: 0px;">
<div class="panel-body">
<a href="/add/">新建随笔</a>
</div>
<div class="panel-body">
<a href="">草稿箱</a>
</div>
<div class="panel-body">
<a href="">回收站</a>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingTwo">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion"
href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
分类
</a>
</h4>
</div>
<div id="collapseTwo" class="panel-collapse collapse" role="tabpanel"
aria-labelledby="headingTwo" aria-expanded="false" style="height: 0px;">
<div class="panel-body">
<a href="">新增分类</a>
</div>
<div class="panel-body">
<a href="">分类列表<</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-10">
<div class="is-show">
<h4 class="is-show">文章展示</h4>
<ul class="nav nav-tabs">
<li role="presentation" class="active"><a href="#">文章</a></li>
<li role="presentation"><a href="#">新闻</a></li>
<li role="presentation"><a href="#">标签</a></li>
</ul> <div class="tab-content">
<div role="tabpanel" class="tab-pane fade in active" id="home">
{% block crticle %} {% endblock %}
</div> </div>
</div> {% block add %} {% endblock %}
</div> </div>
</div>

index.html

{% extends 'backend/base.html' %}
{% block title %}
后台管理
{% endblock %} {% block crticle %}
<div class="bs-example" data-example-id="hoverable-table">
<table class="table table-hover">
<thead>
<tr>
<th>编号</th>
<th>标题</th>
<th>发布时间</th>
<th>评论数</th>
<th>操作</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for article in article_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td><a href="/{{ article.blog.userinfo.username }}/articles/{{ article.id }}">{{ article.title }}/</a></td>
<td>{{ article.create_time|date:'Y-m-d H:i' }}</td>
<td>{{ article.comment_num }}</td>
<td><a href="/delete/?pk={{ article.id }}">删除</a></td>
<td><a href="/alter_article/?pk={{ article.id }}">修改</a></td>
</tr>
{% endfor %} </tbody>
</table>
</div>
{% endblock %}

新建文章

前端模板

{% extends 'backend/base.html' %}

{% block link %}
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
{% endblock %} {% block title %}
添加文章
{% endblock %} {% block add %}
<div class="text-center" style="background: #2aabd2">
<h3>添加随笔</h3>
</div>
<form action="" method="post">
{% csrf_token %}
<div class="form-group">
<label for="add-title">标题</label>
<input type="text" id="add-title" name="title" class="form-control">
</div>
<div class="form-group">
<label for="add-content">内容</label>
<div>
<textarea name="content" id="editor_id" cols="300" rows="20"></textarea>
</div> </div> <div class="form-group">
<label for="add-classify">分类</label>
<select class="form-control" name="category" id="add-classify">
{% for classify in classify_list %}
<option value="{{ classify.id }}">{{ classify.name }}</option>
{% endfor %}
</select>
</div> <div class="form-group">
<label for="add-tag">标签</label>
<select class="form-control" name="tag" id="add-tag" multiple>
{% for tag in tag_list %}
<option value="{{ tag.id }}">{{ tag.name }}</option>
{% endfor %}
</select>
</div>
<button class="btn btn-success form-control">上传文章</button>
</form> {% endblock %} {% block js %}
// 使用富文本编辑器
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
width: '100%',
height: '300px',
resizeType: '1',
// 上传图片相关
uploadJson: '/put_img/',
//filePostName: 'myfile', //默认imgFile
//extraFileUploadParams: {
// 'csrfmiddlewaretoken': '{{ csrf_token }}'
// } 后端没有取消校验 需要传csrf
});
});
</script>
{% endblock %}
def add(request):
if request.method == 'GET':
tag_list = Tag.objects.filter(blog=request.user.blog)
classify_list = Classify.objects.filter(blog=request.user.blog)
return render(request, 'backend/add.html', context={'tag_list': tag_list, 'classify_list': classify_list})
title = request.POST.get('title')
content = request.POST.get('content')
# BeautifulSoup第一个参数是html内容,第二个参数:使用的解析器
bs = BeautifulSoup(content, features='html.parser')
# 截取html文本,将空格和换行替换成空,并截取70个字符
desc = bs.text.replace(' ', '').replace('\n', '')[:70] + '...'
# 剔除script标签
script_list = bs.findAll('script')
for i in script_list:
i.decompose() # 将每个script标签删除
classify = request.POST.get('category')
tag = request.POST.getlist('tag') # 这是多对多的
res = Article.objects.create(title=title, content=str(bs), desc=desc, classify_id=classify, blog=request.user.blog)
# 多对多添加外键关系
res.tag.add(*tag)
return redirect('/backend/')

富文本编辑器图片处理,查看官方文档

# 文章图片处理
# 需要处理csrf 可已经用掉这个接口的csrf @csrf_exempt # 免除校验
def put_img(request):
img = request.FILES.get('imgFile')
path = os.path.join(settings.MEDIA_ROOT, 'upload', img.name)
with open(path, 'wb') as f:
for i in img:
f.write(i)
return JsonResponse({
"error": 0,
"url": f"http://127.0.0.1:8000/media/upload/{img.name}"
})

处理xss攻击

xss跨站脚本,在内容中存script脚本,前端渲染时使用了safe,如果存在script脚本,就会执行。解决方案。富文本编辑器在输入代码块时会自动将尖括号转换成对应的字符,只需在后端将恶意的script清除即可

    页面简易搭建
    文章内容区富文本编辑器的使用

    课下可以自行查找更多的富文本编辑器使用
    添加文章需要注意的问题

    文章简介不应该有标签存在

    文章内容不允许编辑script脚本(XSS攻击)

涉及到html相关内容的处理 可以借助于爬虫相关模块

bs4

需要使用beautifulsoup4模块

-pip3 install beautifulsoup4
-删除script标签
soup = BeautifulSoup(content, 'html.parser')
script_list=soup.findAll('script') # 搜索到html中所有的script标签
for script in script_list:
script.decompose() # 把搜到的script标签一个个删除

首页用户信息展示

用户登陆后展示用户名和和管理选项按钮

<!--首页用户信息展示 未登录显示登录和注册-->
{% if request.user.is_authenticated %}
<li><a href="{{ request.user.username }}">{{ request.user.username }}</a></li>
<li class="dropdown">
<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="/set_pwd/">修改密码</a></li>
<li><a href="/backend/">后台管理</a></li>
<li><a href="/alter_icon/">修改头像</a></li>
<li role="separator" class="divider"></li>
<li><a href="/login_out/">退出登录</a></li>
</ul>
</li>
{% else %}
<a href="/login/">登录</a>
<a href="/register/">注册</a>
{% endif %}

退出后台

# 退出登录
def login_out(request):
logout(request) # request.session.flush() 清除掉session和cookie
return redirect('/')

修改头像

{% extends 'backend/base.html' %}

{% block title %}
修改头像
{% endblock %} {% block add %}
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div style="margin-top: 100px">
<h3 style="color: darkslateblue">修改头像</h3>
<label for="icon">
<img src="/media/{{ icon }}" alt="" width="100px" height="100px" id="img">
</label>
<input type="file" id="icon" style="display: none" name="icon">
<button class="btn btn-success">确认修改</button>
</div>
</form> {% endblock %} {% block js %}
<script>
$('.is-show').toggle() // 头像动态显示 给文件标签绑定一个变化事件
$('#icon').change(function () {
var reader = new FileReader() // 获取文件内容
var file = $('#icon')[0].files[0] reader.readAsDataURL(file) reader.onload = (function () {
$('#img').attr('src', reader.result)
}) })
</script>
{% endblock %}
# 修改头像
def alter_icon(request):
if request.method == "GET":
# 需要当前用户头像
icon = request.user.icon
return render(request, 'backend/alter_icon.html', context={'icon': icon})
icon = request.FILES.get('icon')
request.user.icon = icon
request.user.save()
return redirect('/')

修改密码

{% extends 'backend/base.html' %}

{% block title %}
修改密码
{% endblock %} {% block add %}
<form action="" method="post">
{% csrf_token %}
<div class="form-group">
<label for="pwd1">原密码</label>
<input type="password" id="pwd1" name="old_password" class="form-control">
</div>
<div class="form-group">
<label for="pwd2">新密码</label>
<input type="password" id="pwd2" name="new_password" class="form-control">
</div>
<div class="form-group">
<label for="pwd3">确认密码</label>
<input type="password" id="pwd3" name="re_password" class="form-control">
</div>
<button class="form-control btn-success">提交</button> <span style="color: red">{{ error }}</span>
</form>
{% endblock %}
def set_pwd(request):
if request.method == 'GET':
return render(request, 'backend/set_pwd.html')
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
re_password = request.POST.get('re_password')
if request.user.check_password(old_password):
if new_password == re_password:
request.user.set_password(new_password)
request.user.save()
# 退出当前登录 跳转至登录
login_out(request)
return redirect(to='/login/')
return render(request, 'backend/set_pwd.html', context={'error': '两次密码不一致'})
return render(request, 'backend/set_pwd.html', context={'error': '原密码不一致'})

修改文章

{% extends 'backend/base.html' %}

{% block link %}
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
{% endblock %} {% block title %}
修改文章
{% endblock %} {% block add %}
<div class="text-center" style="background: #2aabd2">
<h3>修改文章</h3>
</div>
<form action="" method="post">
{% csrf_token %}
<div class="form-group">
<label for="add-title">标题</label>
<input type="text" id="add-title" name="title" class="form-control" value="{{ article.title }}">
</div>
<div class="form-group">
<label for="add-content">内容</label>
<div>
<textarea name="content" id="editor_id" cols="300" rows="20">{{ article.content }}</textarea>
</div> </div> <div class="form-group">
<label for="add-classify">分类</label>
<select class="form-control" name="category" id="add-classify">
{% for classify in classify_list %}
{% if classify == article.classify %}
<option value="{{ classify.id }}" selected>{{ classify.name }}</option>
{% else %}
<option value="{{ classify.id }}">{{ classify.name }}</option>
{% endif %} {% endfor %}
</select>
</div> <div class="form-group">
<label for="add-tag">标签</label>
<select class="form-control" name="tag" id="add-tag" multiple>
{% for tag in tag_list %}
{% if tag in tag_list %}
<option value="{{ tag.id }}" selected>{{ tag.name }}</option>
{% else %}
<option value="{{ tag.id }}">{{ tag.name }}</option>
{% endif %} {% endfor %}
</select>
</div>
<button class="btn btn-success form-control">上传文章</button>
</form> {% endblock %} {% block js %}
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
width: '100%',
height: '300px',
resizeType: '1',
// 上传图片相关
uploadJson: '/put_img/',
//filePostName: 'myfile', //默认imgFile
//extraFileUploadParams: {
// 'csrfmiddlewaretoken': '{{ csrf_token }}'
// } 后端没有取消校验 需要传csrf
});
});
</script>
{% endblock %}
def alter_article(request):
pk = request.GET.get('pk')
# 需要当前文章 当前用户的分类和标签
if request.method == 'GET':
article = Article.objects.filter(pk=pk).first()
classify_list = Classify.objects.filter(blog=request.user.blog)
tag_list = Tag.objects.filter(blog=request.user.blog)
return render(request, 'backend/alter_article.html',
context={'article': article, 'classify_list': classify_list, 'tag_list': tag_list})
# post请求 修改文章
title = request.POST.get('title')
content = request.POST.get('content')
# BeautifulSoup第一个参数是html内容,第二个参数:使用的解析器
bs = BeautifulSoup(content, features='html.parser')
# 截取html文本,将空格和换行替换成空,并截取70个字符
desc = bs.text.replace(' ', '').replace('\n', '')[:70] + '...'
# 剔除script标签
script_list = bs.findAll('script')
for i in script_list:
i.decompose() # 将每个script标签删除
classify = request.POST.get('category')
tag = request.POST.getlist('tag') # 这是多对多的
article = Article.objects.filter(pk=request.GET.get('pk')) # 必须是一个queryset
# 还需要将该文章的评论点赞点踩一起更新
up_num = Article.objects.filter(pk=pk).first().up_num
down_num = Article.objects.filter(pk=pk).first().down_num
comment_num = Article.objects.filter(pk=pk).first().comment_num
with transaction.atomic():
article.update(title=title, desc=desc, classify_id=classify, content=str(bs), blog=request.user.blog,
up_num=up_num, down_num=down_num, comment_num=comment_num)
article.first().save()
# 多对多关系添加
article.first().tag.set(tag)
return redirect(f'/{request.user.username}/articles/{pk}')

bbs项目(部分讲解)的相关教程结束。

《bbs项目(部分讲解).doc》

下载本文的Word格式文档,以方便收藏与打印。