Day 20 BBS项目05 评论功能+后台管理

Day 06 BBS项目05 评论功能+后台管理

一、根评论功能

前端

        <div class="text-center" role="alert"></div>  # 这里是一个警示框 发表评论成功与否 显示消息
        {% if request.user.is_authenticated %}
            <div class="media" style="margin-top: 15px;padding: 5px 5px 0 5px;">  # 这个是评论框
                <img src="/media/{
     
     { request.user.avatar }}" class="mr-3" alt="..."
                     style="width: 40px;height: 40px">
                <div class="media-body row">
                    <div class="col-10">
                        <textarea class="form-control" id="exampleFormControlTextarea1" rows="3"
                                  style="background-color: #ededed"></textarea>
                    </div>
                    <div class="col-2">
                        <button id="comment" class="btn btn-primary" style="width: 90%;height: 100%;">发表评论</button>
                    </div>

                </div>
            </div>
        {% else %}
            <p>你还没有登录,请去 <a href="/signin/">登录/</a><a href="/signup/">注册</a></p>
        {% endif %}
        <div id="new">  # 新的评论内容在这里展示
            <p>
        </div>  # 这里展示所有评论
        {% for commit in commits %}
            <hr>
            <div class="media-body row" style="font-size: 14px;color: #707070;padding: 2px">
                <div class="col-1 user-face"><a href="/{
     
     { commit.user.username }}">
                    <img src="/media/{
     
     { commit.user.avatar }}" alt="" class="rounded-circle thumbnail"></a></div>
                <div class="col">
                    <div class="user"><a
                            href="/{
     
     { commit.user.username }}">{
   
   { forloop.counter }}楼 {
   
   { commit.user.username }}</a>
                    </div>
                    <p class="text" style="margin-top: 2px;color: black">{
   
   { commit.content }}</p>
                    <div class="info"><span class="time">2020-10-31 11:33</span>
                        <span style="right: 100px;position: absolute" class="reply" pk="{
     
     { commit.pk }}"
                              username="{
     
     { commit.user.username }}">回复</span>
                    </div>
                </div>
            </div>
        {% endfor %}

前端页面显示为这个

image-20201104215100273

现在开始js代码

	parent_id = ''    # 先设置为空 在点击回复的时候 赋值
	$('#comment').click(function () {
    
      
        let content = $('div>textarea').val()  # 获取评论的内容
        $.ajax({
    
    
            url: '/comment',
            method: 'post',
            data: {
    
    
                content: content,  # 评论内容
                article_id:{
    
    {
    
    article.pk}},  # 文章id
                parent: parent_id,  # 父评论
                csrfmiddlewaretoken: '{
    
    { csrf_token }}'
            },
            success: (result) => {
    
    
        	# 给我们的警示框 添加样式 并赋予内容
                $($('div[role="alert"]')).addClass('alert alert-danger').html(result.msg);
                setTimeout(() => {
    
    
                    $($('div.alert')).removeClass('alert alert-danger').html(''),
                        $('div>textarea').val('')
                }, 1200);  # 1.2 秒后 警示框消失,并且 评论内容也清空
                parent_id = ''  # 评论完成后 父评论为空 
                let username = result.username
                let avatar = result.avatar
                if (result.code === 100) {
    
    
                    let str = ``;  # 这里es6 字符串格式化 相当于 python中的 f{
    
    }asdas" 或者format
                    # ${
    
    xxx} 作为可变内容
                    str = `<hr>
                        <div class="media-body row" style="font-size: 14px;color: #707070;padding: 2px;box-shadow: 0 1px 1px 1px rgba(162, 162, 162, 0.1);">
                        <div class="col-1 user-face"><a href="/${
      
      username}">
                        <img src="/media/${
      
      avatar}" alt="" class="rounded-circle thumbnail"></a></div>
                        <div class="col">
                        <div class="user"><ahref="/${
      
      username}">${
      
      username}</a>
                        </div>
                        <p class="text" style="margin-top: 2px;color: black">${
      
      content}</p>
                        <div class="info"><span class="time">刚刚</span><span
                                class="like "></span><span style="right: 100px;position: absolute" id="reply"
                                                           class="${
      
      username}">回复</span>
                        </div>
                    </div>
                </div>`
                    $('#new').append(str)  # 评论成功 在评论列表下显示 自己的 最新评论
                } else if (result.code === 102) {
    
    
                    str = `<hr>
                        <div class="media-body row" style="font-size: 14px;color: #707070;padding: 2px;box-shadow: 0 1px 1px 1px rgba(162, 162, 162, 0.1);">
                        <div class="col-1 user-face"><a href="/${
      
      username}">
                        <img src="/media/${
      
      avatar}" alt="" class="rounded-circle thumbnail"></a></div>
                        <div class="col">
                        <div class="user"><ahref="/${
      
      username}">${
      
      username}</a>
                        </div>
                        <p class="text" style="margin-top: 2px;color: black">@<a href="/${
      
      username}">${
      
      username}</a>${
      
      content}</p>
                        <div class="info"><span class="time">刚刚</span><span  # 时间刚刚
                                class="like "></span><span style="right: 100px;position: absolute" id="reply"
                                                           class="${
      
      username}">回复</span>
                        </div>
                    </div>
                </div>`
                }

            }
        })

    })
  # 这里是 回复 也就是子评论内容
    $('.reply').click(function () {
    
    
        const parent_name = $(this).attr('username')  # 拿到父评论者
        parent_id = $(this).attr('pk')  # 这里给我们的 父id 赋值 从此有了值 不再为空
        $('div>textarea').val('@' + parent_name + '\n').focus()  # 评论框 内容 +换行
    })

后端

# urls.py
url('^comment$', views.comment),


# views.py
def comment(request):
    result = {
    
    'code': 101, 'msg': '评论失败!'}
    print(request.POST)
    if request.is_ajax():  # 看看是不是ajax请求
        content = request.POST.get('content')
        article_pk = request.POST.get('article_id')
        parent = request.POST.get('parent')
        # 存入事件 评论保存 文章评论数+1
        with transaction.atomic():
            commit = models.Commit.objects.create(user=request.user, article_id=article_pk,
                                                  content=content)
            models.Article.objects.filter(pk=article_pk).update(commit_num=F('commit_num') + 1)
            # 如果有parent_id 实行与父评论关联
            if parent:
                commit.commit_id = models.Commit.objects.get(pk=parent)
                commit.save()
        result['code'] = 100
        result['msg'] = '评论成功!'
        result['username'] = commit.article.blog.user_info.username
        result['content'] = content
        # 这里只要是一个路径就可以了
        result['avatar'] = str(request.user.avatar)
        
        print(result)
    return JsonResponse(result)

我们来看看效果

image-20201104221217026

二、后台管理页面搭建

1.back_base.html+修改头像+修改密码(模态框)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="//at.alicdn.com/t/font_2145723_p6g61z1ymh8.css">
    <!-- CSS only -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
          integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <!-- JS, Popper.js, and jQuery -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js"
            integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
            crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"
            integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
            crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"
            integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
            crossorigin="anonymous"></script>


    <title>创作中心</title>
</head>

<body>
<style>
    label {
    
    
        margin-left: 10px;
    }

    html body {
    
    
        background-color: #f4f4f4;
    }
</style>

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <a class="navbar-brand" href="#">首页</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav" style="width: 100%">
            <li class="nav-item active" style="width: 20%">
                <a class="nav-link" href="#"
                   style="background-color: #006edc;width:100px;height: 38px;border-radius: 10px">
                    <i class="iconfont icon-shipin"> I See</i>
                    <span class="sr-only">(current)</span></a>
            </li>
            <li class="nav-item" style="width:70%;">
                <form class="form-inline my-2 my-lg-0">
                    <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"
                           style="width: 80%">
                    <button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
                </form>
            </li>

            <li class="nav-item dropdown" style="width: 10%">
                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
                   data-toggle="dropdown"
                   aria-haspopup="true" aria-expanded="false">
                    54088
                </a>
                <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                    <a class="dropdown-item" href="/{
    
    { user.username }}">个人中心</a>
                    <a class="dropdown-item" href="/{
    
    { user.username }}/create_article">创作中心</a>
                    <button type="button" class="dropdown-item" data-toggle="modal" data-target="#set_password_modal">
                        修改密码
                    </button>
                    <div class="dropdown-divider"></div>
                    <button class="dropdown-item" id="sign-out">退出</button>
                </div>
            </li>

        </ul>
    </div>
</nav>

<div class="row">
    <div class="col-3 text-center" style="margin-top: 30px;margin-left: 20px">
        <div class="accordion" id="accordionExample">
            <div class="card" style="border-radius: 5px">
                <div class="card-header  bg-primary">
                    <a href="/{
    
    { user.username }}/create_article" class="btn btn-block text-center"><i
                            class="iconfont icon-chuanshu_shangchuan"></i>
                        投 稿</a>
                </div>
            </div>
            <div class="card">
                <div class="card-header" id="headingTwo">
                    <button class="btn btn-block text-center" type="button" data-toggle="collapse"
                            data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
                        操 作
                    </button>
                </div>
                <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
                    <div class="card-body">
                        <p>
                            <button class="btn btn-block btn-primary text-center" data-toggle="modal"
                                    data-target="#set_avatar_modal">修 改 头 像
                            </button>
                        </p>
                        <p>
                            <button class="btn btn-block btn-primary text-center" data-toggle="modal"
                                    data-target="#set_password_modal">
                                修改密码
                            </button>
                        </p>
                    </div>
                </div>
            </div>
            <div class="card">
                <div class="card-header" id="headingTwo">
                    <button class="btn btn-block text-center" type="button" data-toggle="collapse"
                            data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
                        分 类
                    </button>
                </div>
                <div id="collapseThree" class="collapse" aria-labelledby="headingThree" data-parent="#accordionExample">
                    <div class="card-body">
                        <p class="bg-primary">
                            <a href="/{
    
    { user.username }}/back/article" class="btn btn-block text-center">文 章</a>
                        </p>
                        <p class="bg-primary">
                            <a href="/{
    
    { user.username }}/back/category" class="btn btn-block text-center">分 类</a>
                        </p>
                        <p class="bg-primary">
                            <a href="/{
    
    { user.username }}/back/tag" class="btn btn-block text-center">标 签</a>
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="col-8">
        {
    
    % block content %}
        {
    
    % endblock %}
    </div>
</div>


<div class="modal fade" id="set_password_modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
     aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content col-10">
            <div class="modal-header">
                <h5 class="modal-title" id="exampleModalLabel">修改密码</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="form-group col">
                <form action="">{
    
    % csrf_token %}</form>

                <i class="iconfont icon-suo"></i><label for="exampleInputPassword1">Password</label>
                <input type="password" class="form-control" name="password">
                <p class="text-right text-danger" style="font-size: 12px;height: 12px"></p>

            </div>
            <div class="form-group col">
                <i class="iconfont icon-suo"></i><label for="exampleInputPassword1">RePassword</label>
                <input type="password" class="form-control" name="re_password">
                <p class="text-right text-danger" style="font-size: 12px;height: 12px"></p>

            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
                <button type="button" class="btn btn-primary" id="set_password">确认</button>
            </div>
        </div>
    </div>
</div>
<div class="modal fade" id="set_avatar_modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
     aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content col-10">
            <div class="modal-header">
                <p><h5 class="modal-title" id="exampleModalLabel">修改头像</h5></p>
            </div>
            <div class="modal-body">
                <label for="avatar" id="avatar_label"
                       style="
                           color: #006edc">选择头像
                    <img src="https://gitee.com/A1L19/i-see-project/raw/master/Avatar/%E5%A4%B4%E5%83%8F%20(1).jpg"
                         style="width: 80px;height:80px;border-radius: 50%;
                           background-size: 100%;background-repeat: no-repeat;
                           background-color: #f2f2f2;margin-left: 60px" id="avatar_img">
                </label>
                <input type="file" id="avatar" style="display: none">
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
                <button type="button" class="btn btn-primary" id="set_avatar">确认</button>
            </div>
        </div>
    </div>
</div>

{
    
    % block script %}
{
    
    % endblock %}

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>

    $('#avatar').change(function () {
    
    
        {
    
    #借助于文件阅读器#}
        var filereader = new FileReader()
        {
    
    #把图片读取到filereader中#}
        {
    
    #读取文件#}
        avtar = $('#avatar')[0].files[0]
        filereader.readAsDataURL(avtar)
        {
    
    #在读取文件时我们不能立马将文件写入到属性中,因为代码是立即执行的,写入时#}
        {
    
    #可能还没有读取完成,所以我们要在读取完成后写入#}
        filereader.onload = function () {
    
    
            $('#avatar_img').attr('src', filereader.result)

        }
    })
    {
    
    #修改密码操作#}
    $('#set_password').click(function () {
    
    
        $.ajax({
    
    
            url: '/set_password/',
            method: 'post',
            data: {
    
    
                'password': $('input[name="password"]').val(),
                're_password': $('input[name="re_password"]').val(),
                'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val()
            },
            success: (result) => {
    
    
                alert(result.msg)
                location.href = result.path
            }
        })
    })
    $('#set_avatar').click(function () {
    
    



        let formData = new FormData()
        formData.append('avatar', $('#avatar')[0].files[0])

        console.log($('#avatar')[0].files[0])
        $.ajax({
    
    
            url: '/set_avatar/',
            type:'post',
            processData: false,
            contentType: false,
            data: formData,
            success: (result) => {
    
    
                alert(result.msg)
            }
        })
    })

</script>
</body>
</html>

1.1修改头像+修改密码后端

# urls.py
    url(r'^set_password/', views.set_password),
    url(r'^set_avatar/', views.set_avatar),
    
# views.py
# 修改密码
@login_required(login_url='/signin/')
def set_password(request):
    result = {
    
    'code': 101, 'msg': '密码输入错误!', 'path': '/home/'}
    if request.method == 'POST':
        print(request.POST)
        password = request.POST.get('password')
        re_password = request.POST.get('re_password')
        if not request.user.is_authenticated():
            return JsonResponse(result)
        else:
            if request.user.check_password(password):
                request.user.set_password(re_password)
                request.user.save()
                result['code'] = 100
                result['msg'] = '密码修改成功!'
                result['path'] = '/signin/'
    return JsonResponse(result)

# 修改头像
from django.views.decorators.csrf import csrf_exempt
@login_required(login_url='/signin/')
@csrf_exempt
def set_avatar(request):
    user = request.user
    result = {
    
    'msg': '修改成功'}
    print(request.FILES.get('avatar'))
    if request.method == 'POST':
        new_avatar = request.FILES.get('avatar')
        user.avatar = new_avatar
        user.save()
        return JsonResponse(result)

2.后端+内容显示

前端

{% extends 'background/backstage_base.html' %}

{% block content %}

    <div class="col-6" style="margin-left: 50px">
        <div>
            {% if code == 101 %}
                <h3 style="margin-left: 80px">{
   
   { articles }}</h3>
            {% else %}
                {% for article in articles %}
                    <div class="media" style="margin-top: 15px">
                        <img src="/media/{
     
     { article.blog.user_info.avatar }}" class="mr-3" alt="..."
                             style="width: 40px;height: 40px">
                        <div class="media-body">
                            <h5 class="mt-0"><a
                                    href="/{
     
     { article.blog.user_info.username }}/article/{
     
     { article.id }}.html">{
   
   { article.title }}</a>
                            </h5>
                            {
   
   { article.introduction }}
                            <div>
                            <span style="font-size: 12px;height: 12px"><a
                                    href="/{
     
     { article.blog.user_info.username }}">{
   
   { article.blog.user_info.username }} </a>
                                   &nbsp;&nbsp;{
   
   { article.publish_time|date:'Y-m-d H-i-s' }} </span>
                            </div>
                        </div>
                    </div>
                {% endfor %}

            {% endif %}
        </div>
    </div>
{% endblock %}

image-20201104222810093

后端

# urls.py
url('^(?P<name>\w+)/back/(?P<type>category|tag|article)$', views.user_background),

# views.py
def user_background(request, name, **kwargs):
    user = models.User_info.objects.filter(username=name).first()
    print(kwargs)
    if user:
        # 默认显示作者全部文章
        articles = models.Article.objects.filter(blog=user.blog)
        query = kwargs.get('type', None)
        if user.blog:
            # 根据类别筛选出显示的文章
            if query == 'category':
                articles = articles.order_by('category')
            elif query == 'tag':
                articles = articles.order_by('tag')
        
            return render(request, 'background/user_background.html',
                          {
    
    'articles': articles, 'user': user})
        return render(request, 'background/user_background.html',
                      {
    
    'articles': '这个作者很懒什么都没留下!', 'user': user, 'code': 101})

三、富文本编辑器的使用

# 1 下载kindeditor:http://kindeditor.net/down.php
# 2 解压缩,把整个文件加放入static目录下
# 3 再add_article.html中
    <div class="form-group">
    <label for="">内容</label>
    <textarea name="content" id="editor_id" cols="80" rows="10" class="form-control">	 </textarea>
    </div>
# 3 js代码
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script>
    <script>
        KindEditor.ready(function (K) {
    
    
            window.editor = K.create('#editor_id', {
    
    
                width: '100%',
                height: '500px',
                resizeType: 1,


            });
        });

四、新增文章,处理xss攻击

# pip3 install beautifulsoup4
from bs4 import BeautifulSoup

@login_required(login_url='/signin/')
def create_article(request, name):
    print(request.POST)
    categorys = models.Category.objects.order_by('name')
    tags = models.Tag.objects.order_by('name')
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')
        category = request.POST.get('category')
        tag = request.POST.getlist('tag')
        soup = BeautifulSoup(content, 'html.parser')
        introduction = soup.text[0:90]  # 去掉所有标签后的文本
        # 把script标签干掉
        res_script = soup.find_all('script')
        for script in res_script:
            script.decompose()  # 删除script标签
        
        article = models.Article.objects.create(title=title, content=str(soup), introduction=introduction,
                                                blog=request.user.blog,
                                                category_id=category)
        article.tag.add(*tag)
        return redirect('/user_background/')
    return render(request, 'background/create_article.html', {
    
    'categorys': categorys, 'tags': tags})

五、富文本编辑器上传图片

前端代码

    <script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script>
    <script>
        KindEditor.ready(function (K) {
    
    
            window.editor = K.create('#content', {
    
    
                width: '100%',
                height: '500px',
                resizeType: 1,
                uploadJson: '/upload_img/',
                filePostName: 'myfile',
                //额外带的参数
                extraFileUploadParams: {
    
    
                    csrfmiddlewaretoken: '{
    
    { csrf_token }}',

                }
            });
        });
    </script>

后端

def upload_img(request):
    result = {
    
    'error': 0}
    try:
        file = request.FILES.get('myfile')
        path = os.path.join(settings.BASE_DIR, 'media', 'image', file.name)
        with open(path, 'wb')as f:
            for line in file:
                f.write(line)
        result['url'] = '/media/image/' + file.name
    except Exception:
        result['error'] = 1
        result['message'] = '文件上传失败!'
    return JsonResponse(result)

image-20201104222048278

猜你喜欢

转载自blog.csdn.net/A1L__/article/details/109501093