提交评论
评论框里面的内容会清空 然后页面会有一个临时评论样式出现 页面刷新才会出现评论楼样式
研究子评论特性
每个评论右侧都应该有回复按钮 点击就可以填写子评论
点击回复按钮具体动作:评论框中自动添加@+评论的人名并换行 聚焦
如何区分不同的回复按钮所对应的用户名
利用标签可以自定义属性直接携带对应的评论用户名即可
提交根评论和子评论点击的是同一个按钮 两者的区别与联系是什么
其实根评论和子评论的唯一区别就是是否有父评论的主键值
如何区分不同的回复按钮所对应的评论主键值
利用标签可以自定义属性直接携带对应的评论主键值即可
点击回复按钮发送子评论 页面不刷新的情况下 后续的评论全部成了子评论
原因是全局变量parentId没有清空导致的 每次提交评论都应该清空一下
针对子评论内中的@用户名换行 理论上不属于用户评论的内容 不应该记录到数据库
前端可以剔除 也可以在后端剔除
针对子评论的渲染 应该动态判断是否是子评论 如果是应该加上评论的目标用户名
注意:针对评论的渲染也可以分页 也可以做根评论与子评论的集合操作(分类)
后台管理
base.html
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<div class="panel-group" >
<div class="panel panel-default">
<div class="panel-heading" role="tab" >
<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
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" >
<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
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" >
{% 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->
<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" >
</div>
<div class="form-group">
<label for="add-content">内容</label>
<div>
<textarea name="content" ></textarea>
</div>
</div>
<div class="form-group">
<label for="add-classify">分类</label>
<select class="form-control" name="category" >
{% 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" 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" >
</label>
<input type="file" >
<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" >
</div>
<div class="form-group">
<label for="pwd2">新密码</label>
<input type="password" >
</div>
<div class="form-group">
<label for="pwd3">确认密码</label>
<input type="password" >
</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" >
</div>
<div class="form-group">
<label for="add-content">内容</label>
<div>
<textarea name="content" >{{ article.content }}</textarea>
</div>
</div>
<div class="form-group">
<label for="add-classify">分类</label>
<select class="form-control" name="category" >
{% 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" 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}')