BBS项目day05 后台系统功能(首页、文章列表展示、添加文章、上传文件)

一、后台功能的初始配置

1. urls.py路由分发

    re_path('app02/', include('app02.urls')),

2.app02/urls.py

from django.urls import path, re_path, include
from app02 import views

urlpatterns = [
    path('home/', views.home),
    path('article_list/', views.article_list),
    path('add_article/', views.add_article),
    path('upload_image/', views.upload_image),
]

二、后台功能之首页

1.首页前端

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>

    {% load static %}
    <script src="{% static 'js/jquery.min.js' %}">script>
    <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
    <script src="{% static 'bootstrap/js/bootstrap.min.js' %}">script>
    <script src="{% static 'layer/layer.js' %}">script>

head>
<body>

{# 导航条开始 #}
<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 navigationspan>
                <span class="icon-bar">span>
                <span class="icon-bar">span>
                <span class="icon-bar">span>
            button>
            <a class="navbar-brand" href="#">BBS博客园后台系统a>
        div>

        
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">文章 <span class="sr-only">(current)span>a>li>
                <li><a href="#">分类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="#">Actiona>li>
                        <li><a href="#">Another actiona>li>
                        <li><a href="#">Something else herea>li>
                        <li role="separator" class="divider">li>
                        <li><a href="#">Separated linka>li>
                        <li role="separator" class="divider">li>
                        <li><a href="#">One more separated linka>li>
                    ul>
                li>
            ul>
            <form class="navbar-form navbar-left">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search">
                div>
                <button type="submit" class="btn btn-default">搜索button>
            form>
            <ul class="nav navbar-nav navbar-right">
                {% if request.session.username %}
                    <li style="line-height: 50px;">
                        
                        {# <img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" style="width: 100px;" alt="..."> #}
                        <img src="/media/{{ cur_avatar }}" style="width: 50px; height: 36px;" class="onImg" alt="">
                    li>
                    <li><a href="#">{{ request.session.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="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码a>li>
                            <li><a href="#">更改头像a>li>
                            <li><a href="/logout/">退出登录a>li>
                            <li><a href="#">后台管理a>li>
                        ul>
                    li>
                {% else %}
                    <li><a href="/login/">登录a>li>
                    <li><a href="/register/">注册a>li>
                {% endif %}
            ul>
            
            <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
                <div class="modal-dialog modal-lg" role="document">
                    <div class="modal-content">
                        <div class="row">
                            <h1 class="text-center">修改密码h1>
                            <div class="col-md-8 col-md-offset-2">
                                <div class="form-group">
                                    用户名:<input type="text" readonly value="{{ request.session.username }}"
                                               class="form-control">
                                div>
                                <div class="form-group">
                                    原密码:<input type="password" id="old_password" class="form-control" msg="原密码必须输入">
                                div>
                                <div class="form-group">
                                    新密码:<input type="password" id="new_password" class="form-control" msg="原密码必须输入">
                                div>
                                <div class="form-group">
                                    确认密码:<input type="password" id="re_password" class="form-control" msg="原密码必须输入">
                                div>
                                <div class="form-group">
                                    <input type="button" value="修改密码" class="btn btn-primary btn-block btn_password">
                                div>
                            div>
                        div>
                    div>
                div>
            div>
        div>
    div>
nav>
{# 导航条结束 #}

<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            <div class="list-group">
                <a href="#" class="list-group-item active">
                    首页
                a>
                <a href="/app02/article_list/" class="list-group-item">文章列表a>
                <a href="#" class="list-group-item">分类类别a>
                <a href="#" class="list-group-item">标签列表a>
                <a href="#" class="list-group-item">更多a>
            div>
        div>

        <div class="col-md-9">
            <div class="panel panel-info">
                <div class="panel-heading">我自一口真气足div>
                <div class="panel-body">
                    {% block content %}
                        <div class="jumbotron">
                            <h1>最牛叉的博客平台h1>
                            <p>无招胜有招p>
                            <p><a class="btn btn-primary btn-lg" href="#" role="button">更过风景a>p>
                        div>
                        <div class="row">
                            <div class="col-sm-6 col-md-4">
                                <div class="thumbnail">
                                    <img src="https://img2.baidu.com/it/u=3323311628,2330835932&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1692464400&t=3cf590e7ea88465e48ef5170f7c70884"
                                         alt="...">
                                    <div class="caption">
                                        <h3>Thumbnail labelh3>
                                        <p>清风拂山岗p>
                                        <p><a href="#" class="btn btn-primary" role="button">Buttona> <a href="#"
                                                                                                           class="btn btn-default"
                                                                                                           role="button">如来神掌a>
                                        p>
                                    div>
                                div>
                            div>
                            <div class="col-sm-6 col-md-4">
                                <div class="thumbnail">
                                    <img src="https://img2.baidu.com/it/u=3323311628,2330835932&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1692464400&t=3cf590e7ea88465e48ef5170f7c70884"
                                         alt="...">
                                    <div class="caption">
                                        <h3>Thumbnail labelh3>
                                        <p>清风拂山岗p>
                                        <p><a href="#" class="btn btn-primary" role="button">Buttona> <a href="#"
                                                                                                           class="btn btn-default"
                                                                                                           role="button">如来神掌a>
                                        p>
                                    div>
                                div>
                            div>
                            <div class="col-sm-6 col-md-4">
                                <div class="thumbnail">
                                    <img src="https://img2.baidu.com/it/u=3323311628,2330835932&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1692464400&t=3cf590e7ea88465e48ef5170f7c70884"
                                         alt="...">
                                    <div class="caption">
                                        <h3>Thumbnail labelh3>
                                        <p>清风拂山岗p>
                                        <p><a href="#" class="btn btn-primary" role="button">Buttona> <a href="#"
                                                                                                           class="btn btn-default"
                                                                                                           role="button">如来神掌a>
                                        p>
                                    div>
                                div>
                            div>
                        div>
                    {% endblock %}
                div>
            div>
        div>
    div>
div>

{% block js %}

{% endblock %}

body>
html>

2.首页后端

def home(request):
    return render(request, 'backend/home.html', locals())

三、后台功能之文章列表展示

1.文章列表展示前端

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

{% block content %}
    <h3 class="text-center">文章列表</h3>
    <a href="/app02/add_article" class="btn btn-success" style="margin-bottom: 10px">添加文章</a>
    <table class="table table-bordered table-hover table-striped">
        <thead>
        <tr>
            <th>标题</th>
            <th>点赞数</th>
            <th>点踩数</th>
            <th>评论数</th>
            <th>更多</th>
        </tr>
        </thead>
        <tbody>
            {% for articles in articles_list %}
                <tr>
                    <td><a href="/{{ request.session.username }}/{{ articles.pk }}" target="_blank">{{ articles.title }}</a></td>
                    <td>{{ articles.up_num }}</td>
                    <td>{{ articles.down_num }}</td>
                    <td>{{ articles.comment_num }}</td>
                    <td>
                        <a href="" class="btn btn-success">修改</a>
                        <a href="" class="btn btn-danger">删除</a>
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

{% endblock %}

2.文章列表后端

def article_list(request):
    # 查询出所有的文章列表
    articles_list = models.Article.objects.all()

    return render(request, 'backend/article_list.html', locals())

三、后台功能之添加文章

1.添加文章前端

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

{% block content %}
    <h3 class="text-center">添加文章</h3>
    <form action="">
        <div class="form-group">文章标题:
            <input type="text" id="title" class="form-control">
        </div>
        <div class="form-group">文章分类:
            <select name="" id="cate" class="form-control">
                {% for category in category_list %}
                    <option value="{{ category.pk }}">{{ category.name }}</option>
                {% endfor %}
            </select>
        </div>
        <div class="form-group">
            <p>文章标签:</p>
            {% for tags in tags_list %}
                {{ tags.name }}&nbsp;
                <input type="checkbox" id="title" name="tags" value="{{ tags.pk }}" style="margin-right: 10px;">
            {% endfor %}
        </div>
        <div class="form-group">文章内容:
            <textarea id="editor_id" name="content" style="width:100%;height:400px;"></textarea>
        </div>
        <div class="form-group">
            <!-- type="submit" 会自动提交表单 -->
            <!-- <button></button> 会自动提交表单,注意:当写在外面的时候才不会自动提交form表单 -->
            {# <input type="submit" value="提交" id="title" class="btn btn-success btn-block"> #}
            <!-- type="button" 不会自动提交表单 -->
            <input type="button" value="提交" id="title" class="btn btn-success btn-block btn_article">
        </div>
    </form>

{% endblock %}

{% block js %}
    {% load static %}
    <script charset="utf-8" src="{% static 'kindeditor/kindeditor-all.js' %}"></script>
    <script charset="utf-8" src="{% static 'kindeditor/lang/zh-CN.js' %}"></script>
    <script>
        // 绑定点击事件
        $(".btn_article").click(function () {
            // 同步数据后可以直接取得textarea的value
            editor.sync();
            // 获取数据

            // 标题
            let title = $("#title").val();

            // 分类
            let cate = $("#cate").val();

            // 文章标签是复选框,所以通过属性选择器获取值,
            // 由于是复选框的缘故,可能会有多个值的情况出现,所以应该循环取值
            // 注意:获取复选框时,不要加上 .val(),循环取值的再加上
            let tags = $("input[name='tags']:checkbox");

            let tags_arr = []
            $.each(tags, function (index, value) {
                tags_arr.push($(this).val());
            });
            console.log(tags_arr); // [1,2,3,4]
            // 由于是数组,所以传递到后台的数据不是字符串,要先转为字符串
            // 在下面的Ajax的 data 中传值也要传递字符串
            var tags_str = tags_arr.join(',');
            console.log(tags_str);

            // 内容
            {#html = document.getElementById('editor_id').value; // 原生API#}
            {#html = K('#editor_id').val(); // KindEditor Node API#}
            let content = $("#editor_id").val(); // jQuery
            console.log(content);

            // 发起Ajax请求
            $.ajax({
                url: '',
                type: 'post',
                data: {
                    title: title,
                    cate: cate,
                    tags: tags_str,
                    content: content,
                    csrfmiddlewaretoken: '{{ csrf_token }}'
                },
                success: function (res) {
                    if (code === 200) {
                        layer.msg(res.msg, {}, function () {
                            location.href = '/app02/article_list/';
                        });
                    } else {
                        layer.msg(res.msg, {});
                    }
                }
            });
        });
    </script>
{% endblock %}


2.添加文章后端

def add_article(request):
    # 文本编辑器官网 http://www.kindsoft.net/down.php

    user_obj = models.UserInfo.objects.filter(pk=request.session.get('id')).first()
    if not user_obj:
        return redirect('/login/')

    blog = user_obj.blog

    # 查询所有的分类
    category_list = models.Category.objects.all()

    # 查询所有的标签列表
    tags_list = models.Tag.objects.all()

    back_dict = {'code': 200, 'msg': '添加成功', 'data': []}
    # 1.获取前段传递过来的数据
    if request.method == 'POST':
        title = request.POST.get('title')
        cate_id = request.POST.get('cate_id')
        content = request.POST.get('content')
        tags = request.POST.get('tags')  # 1, 2
        # tags原本是一个列表,但是前端传值时进行了转换,有列表转为了字符串
        # 在此,要先转位列表去使用
        tags_list = tags.split(',')  # [1, 2]

        # 2.参数验证
        if not title:
            back_dict['code'] = 1500
            back_dict['msg'] = '标题必须有'
            return JsonResponse(back_dict)
        if not cate_id:
            back_dict['code'] = 1501
            back_dict['msg'] = '分类必须选择'
            return JsonResponse(back_dict)
        if not tags:
            back_dict['code'] = 1502
            back_dict['msg'] = '标签必须选择'
            return JsonResponse(back_dict)
        if not content:
            back_dict['code'] = 1503
            back_dict['msg'] = '内容不能为空'
            return JsonResponse(back_dict)

        '''
            1.摘要截取的问题
            2.xss攻击的问题----->原理:有了script标签------>把提交过来的内容过滤出script标签,然后做删除
                解决方法:
                1.使用正则匹配 script,匹配到之后,做删除,这个方法很麻烦,不推荐
                2.利用第三方模块来处理:bs4模块
                pip install bs4
                BeautifulSoup它是用在爬虫里面,它能够筛选数据,清晰html数据
                    BeautifulSoup('', 'html.parser')
                    使用lxml的话需要安装 pip install lxml
                    BeautifulSoup('', 'lxml')
        '''
        # 解决xss攻击的问题
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(content, 'html.parser')
        # print(soup.find_all('a'))  # 写的a那么久就筛选a
        # print(soup.find_all('script'))

        # print(soup.text)  # 打印文本内容

        for tag in soup.find_all():
            # print(tag.name)
            if tag.name == 'script':
                # 删除script
                tag.decompose()

        # 文章摘要直接从内容中截取 100字
        desc = soup.text[:100]

        # 3.数据入库,需要操作文章表,文章、标签的第三张表
        article_obj = models.Article.objects.create(title=title, content=str(soup), desc=desc, category_id=cate_id,
                                                    blog=blog)

        # 操作标签的第三张表,数据是多对多,选择批量插入数据
        article_tag_list = []
        for i in tags_list:
            article_tag_obj = models.Article2Tag(article_id=article_obj.pk, tag_id=i)
            article_tag_list.append(article_tag_obj)
        models.Article2Tag.objects.bulk_create(article_tag_list)

        return JsonResponse(back_dict)

    return render(request, 'backend/add_article.html', locals())

四、后台功能之上传文件

1.添加文章前端

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

{% block content %}
    <h3 class="text-center">添加文章</h3>
    <form action="">
        <div class="form-group">文章标题:
            <input type="text" id="title" class="form-control">
        </div>
        <div class="form-group">文章分类:
            <select name="" id="cate" class="form-control">
                {% for category in category_list %}
                    <option value="{{ category.pk }}">{{ category.name }}</option>
                {% endfor %}
            </select>
        </div>
        <div class="form-group">
            <p>文章标签:</p>
            {% for tags in tags_list %}
                {{ tags.name }}&nbsp;
                <input type="checkbox" id="title" name="tags" value="{{ tags.pk }}" style="margin-right: 10px;">
            {% endfor %}
        </div>
        <div class="form-group">文章内容:
            <textarea id="editor_id" name="content" style="width:100%;height:400px;"></textarea>
        </div>
        <div class="form-group">
            <!-- type="submit" 会自动提交表单 -->
            <!-- <button></button> 会自动提交表单,注意:当写在外面的时候才不会自动提交form表单 -->
            {# <input type="submit" value="提交" id="title" class="btn btn-success btn-block"> #}
            <!-- type="button" 不会自动提交表单 -->
            <input type="button" value="提交" id="title" class="btn btn-success btn-block btn_article">
        </div>
    </form>

{% endblock %}

{% block js %}
    {% load static %}
    <script charset="utf-8" src="{% static 'kindeditor/kindeditor-all.js' %}"></script>
    <script charset="utf-8" src="{% static 'kindeditor/lang/zh-CN.js' %}"></script>
    <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#editor_id', {
                height: '300px',
                items: [
                    'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste',
                    'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright',
                    'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript',
                    'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/',
                    'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold',
                    'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image', 'multiimage',
                    'flash', 'media', 'insertfile', 'table', 'hr', 'emoticons', 'baidumap', 'pagebreak',
                    'anchor', 'link', 'unlink', '|', 'about'
                ],
                resizeType: 0,
                colorTable: [
                    ['#E53333', '#E56600', '#FF9900', '#64451D', '#DFC5A4', '#FFE500'],
                    ['#009900', '#006600', '#99BB00', '#B8D100', '#60D978', '#00D5FF'],
                    ['#337FE5', '#003399', '#4C33E5', '#9933E5', '#CC33E5', '#EE33EE'],
                    ['#FFFFFF', '#CCCCCC', '#999999', '#666666', '#333333', '#000000'],
                ],
                uploadJson: '/upload_image/',
                extraFileUploadParams: {
                    csrfmiddlewaretoken: '{{ csrf_token }}'
                },
            });

        });

        // 绑定点击事件
        $(".btn_article").click(function () {
            // 同步数据后可以直接取得textarea的value
            editor.sync();
            // 获取数据

            // 标题
            let title = $("#title").val();

            // 分类
            let cate = $("#cate").val();

            // 文章标签是复选框,所以通过属性选择器获取值,
            // 由于是复选框的缘故,可能会有多个值的情况出现,所以应该循环取值
            // 注意:获取复选框时,不要加上 .val(),循环取值的再加上
            let tags = $("input[name='tags']:checkbox");

            let tags_arr = []
            $.each(tags, function (index, value) {
                tags_arr.push($(this).val());
            });
            console.log(tags_arr); // [1,2,3,4]
            // 由于是数组,所以传递到后台的数据不是字符串,要先转为字符串
            // 在下面的Ajax的 data 中传值也要传递字符串
            var tags_str = tags_arr.join(',');
            console.log(tags_str);

            // 内容
            {#html = document.getElementById('editor_id').value; // 原生API#}
            {#html = K('#editor_id').val(); // KindEditor Node API#}
            let content = $("#editor_id").val(); // jQuery
            console.log(content);

            // 发起Ajax请求
            $.ajax({
                url: '',
                type: 'post',
                data: {
                    title: title,
                    cate: cate,
                    tags: tags_str,
                    content: content,
                    csrfmiddlewaretoken: '{{ csrf_token }}'
                },
                success: function (res) {
                    if (code === 200) {
                        layer.msg(res.msg, {}, function () {
                            location.href = '/app02/article_list/';
                        });
                    } else {
                        layer.msg(res.msg, {});
                    }
                }
            });
        });
    </script>
{% endblock %}


2.添加文章后端

def upload_image(request):
    '''
    文件返回格式:
        //成功时
        {
                "error" : 0,
                "url" : "http://www.example.com/path/to/file.ext"
        }
        //失败时
        {
                "error" : 1,
                "message" : "错误信息"
        }
    :param request:
    :return:
    '''
    if request.method == 'POST':
        print(request.FILES)
        file_obj = request.FILES.get('imgFile')

        # 拼接上传的路径
        import os
        from django.conf import settings
        BASE_DIE = os.path.join(settings.BASE_DIR, 'media', 'article_img')
        file_name = os.path.join(BASE_DIE, file_obj.name)

        import uuid
        new_str = str(uuid.uuid4())
        new_uuid = new_str.replace('-', '')
        new_file_name = new_uuid + '.' + file_obj.name.rsplit('.')[-1]

        with open(new_file_name, 'wb') as f:
            for line in file_obj:
                f.write(line)

    # return HttpResponse('ok')
    return JsonResponse(
        {
            "error": 0,
            # "url": "/media/article_img/%s" % file_obj.name
            "url": "/media/article_img/%s" % new_file_name
        }
    )

你可能感兴趣的:(python01,django,django,python)