bbs项目(部分讲解)
阅读原文时间:2023年07月09日阅读:1

文章评论业务完善

提交评论
    评论框里面的内容会清空 然后页面会有一个临时评论样式出现 页面刷新才会出现评论楼样式

研究子评论特性
    每个评论右侧都应该有回复按钮 点击就可以填写子评论
    点击回复按钮具体动作:评论框中自动添加@+评论的人名并换行 聚焦
        如何区分不同的回复按钮所对应的用户名
              利用标签可以自定义属性直接携带对应的评论用户名即可

提交根评论和子评论点击的是同一个按钮 两者的区别与联系是什么
    其实根评论和子评论的唯一区别就是是否有父评论的主键值
        如何区分不同的回复按钮所对应的评论主键值
               利用标签可以自定义属性直接携带对应的评论主键值即可

点击回复按钮发送子评论 页面不刷新的情况下 后续的评论全部成了子评论
    原因是全局变量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清除即可

  1. 页面简易搭建

  2. 文章内容区富文本编辑器的使用

    课下可以自行查找更多的富文本编辑器使用

  3. 添加文章需要注意的问题

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

    文章内容不允许编辑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}')