zoukankan      html  css  js  c++  java
  • 1211 BBS后台管理文章添加

    昨日内容回顾

    侧边栏inclusion_tag

    inclusion_tag的响应

    • 可以将页面上的某个区域的内容坐火车哪个模块的形式
    • 调用的时候传入参数,即可渲染出对应的页面放到调用的位置

    使用

    • 当页面上的某一块内容在多个页面上都需要别使用到
    • 并且这块内容是需要传参才能渲染出来,那么你可以考虑使用
    • inclusion_tag来帮你简化代码

    自定义inclusion_tag,标签,过滤器

    1. 在应用下新建一个名字必须叫template_tag文件夹

    2. 在该文件夹内新建任意名称的py文件

    3. 在py文件内,固定先写下面的两句代码

      
      

    文章的点赞点踩

    前端

    1. 点赞点踩前端样式拷贝
    除了拷贝html代码之外
    还需要将配套的css代码也拷贝过来
    如果css代码中引用了图片 为了防止图片防盗链 应该下载到本地
    
    1. 点击提交点赞点踩

      如何区分用户点赞还是点踩
      给点赞点踩标签统一加了一个相同的类名
      给该类名绑定点击事件
      
      利用this指点的是当前被操作对象本身再加上判断某个标签是否有某个属性
      能够得出用户点的赞还是踩(hasClass())
      
    2. 向后端发送ajax请求

    • 点赞点踩
    • csrf
    • 文章id
    1. 回调函数

      展示提示信息
          成功
              提示
              将对于的数字加一
                  一定要转数值类型再相加
          失败
              提示
      

    后端

    校验规则

    • 校验当前请求是ajax请求

      `request.is_ajax()`
      
    • 校验当前用户是否登录

      `request.user.is_authenticated()`
      
    • 校验的当前用户是否点的是自己的文章

      根据文章id获取文章对象,利用文在哪个对象查询改文章的作者
      与当前登录的用户进行比对
      request.user == article_obj.blog.userinfo
      
    • 校验当前用户是否已经给当前文章点过了

      只需要去点赞点踩表中查询用户是当前用户并且文章id是前端传来的id
      
    • 操作数据库

      两个地方需要修改
      点赞点踩表
      文章表中的普通字段
      

    文章的评论功能

    先考虑根评论 再考虑子评论
    

    1.前端

    渲染评论框

    区分当前用户是否登录
    没有登录的情况下,只展示登录注册
    鞥路的情况下才展示评论框
    
    • 给提交的评论按钮绑定点击事件

      发送ajax请求

      评论内容
      文章id
      csrf
      

      回调函数

      将评论框中内容清空
      

      临时渲染

      利用模板字符串
      利用DOM操作将临时创建的模板字符串添加到正常的文档流中
      

      刷新渲染

      手动添加html代码
      
    • 子评论

      研究点击回复按钮发生的事情

      1.将当前想要评论的评论人的姓名
      2.将拼接号的内容添加到评论框中
      3.评论框自动聚焦
      	focus()
      

      提交根评论与提交子评论点击的是相同的按钮

      根评论与子评论唯一的区别仅仅在于是否有parentid赋值

      朝后端发送

      后端parentid无论是否传值都不会影响存储
      所以你的提交代码写一份就可以
      

    需要避免的问题

    提交子评论之后一定要将全局变量名充值为空
    在存储数据的时候,要对数据进行切割操作
    在渲染评论楼的时候,针对子评论需要多渲染一个根评论人的名字
    	comment.parent.user.username
    

    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="true" aria-controls="collapseOne">
              Collapsible Group Item #1
            </a>
          </h4>
        </div>
        <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
          <div class="panel-body">
            Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
          </div>
        </div>
      </div>
    </div>
    

    右侧使用标签栏

    <div>
    
      <!-- Nav tabs -->
      <ul class="nav nav-tabs" role="tablist">
        <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">Home</a></li>
        <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
        <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">Messages</a></li>
        <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Settings</a></li>
      </ul>
    
      <!-- Tab panes -->
      <div class="tab-content">
        <div role="tabpanel" class="tab-pane active" id="home">...</div>
        <div role="tabpanel" class="tab-pane" id="profile">...</div>
        <div role="tabpanel" class="tab-pane" id="messages">...</div>
        <div role="tabpanel" class="tab-pane" id="settings">...</div>
      </div>
    
    </div>
    

    在右侧的侧边栏设置模板,用来继承整体显示

                    <div class="tab-content">
                        <div role="tabpanel" class="tab-pane active" id="home">
                            {% block article %}
                            文章页面
                            {% endblock %}
                        </div>
                        <div role="tabpanel" class="tab-pane" id="profile">
                            随笔页面
                        </div>
                        <div role="tabpanel" class="tab-pane" id="messages">3</div>
                        <div role="tabpanel" class="tab-pane" id="settings">4</div>
                    </div>
    

    文件模板继承

    {% extends 'backend/backend_base.html' %}
    
    
    {#文章页面展示#}
    {% block article %}
        <table class="table table-striped 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><a href="/{{ request.user.user.username }}/article/{{ article.pk }}">{{ article.title }}</a></td>
                    <td>{{ article.create_time|date:'Y-m-d' }}</td>
                    <td>{{ article.comment_num }}</td>
                    <td>{{ article.up_num }}</td>
                    <td><a href="#">编辑</a></td>
                    <td><a href="#">删除</a></td>
                </tr>
            {% endfor %}
    
        </tbody>
        </table>
    {% endblock %}
    

    使用分页器

    class Pagination(object):
        def __init__(self,current_page,all_count,per_page_num=2,pager_count=11):
            """
            封装分页相关数据
            :param current_page: 当前页
            :param all_count:    数据库中的数据总条数
            :param per_page_num: 每页显示的数据条数
            :param pager_count:  最多显示的页码个数
            
            用法:
            queryset = model.objects.all()
            page_obj = Pagination(current_page,all_count)
            page_data = queryset[page_obj.start:page_obj.end]
            获取数据用page_data而不再使用原始的queryset
            获取前端分页样式用page_obj.page_html
            """
            try:
                current_page = int(current_page)
            except Exception as e:
                current_page = 1
    
            if current_page <1:
                current_page = 1
    
            self.current_page = current_page
    
            self.all_count = all_count
            self.per_page_num = per_page_num
    
    
            # 总页码
            all_pager, tmp = divmod(all_count, per_page_num)
            if tmp:
                all_pager += 1
            self.all_pager = all_pager
    
            self.pager_count = pager_count
            self.pager_count_half = int((pager_count - 1) / 2)
    
        @property
        def start(self):
            return (self.current_page - 1) * self.per_page_num
    
        @property
        def end(self):
            return self.current_page * self.per_page_num
    
        def page_html(self):
            # 如果总页码 < 11个:
            if self.all_pager <= self.pager_count:
                pager_start = 1
                pager_end = self.all_pager + 1
            # 总页码  > 11
            else:
                # 当前页如果<=页面上最多显示11/2个页码
                if self.current_page <= self.pager_count_half:
                    pager_start = 1
                    pager_end = self.pager_count + 1
    
                # 当前页大于5
                else:
                    # 页码翻到最后
                    if (self.current_page + self.pager_count_half) > self.all_pager:
                        pager_end = self.all_pager + 1
                        pager_start = self.all_pager - self.pager_count + 1
                    else:
                        pager_start = self.current_page - self.pager_count_half
                        pager_end = self.current_page + self.pager_count_half + 1
    
            page_html_list = []
            # 添加前面的nav和ul标签
            page_html_list.append('''
                        <nav aria-label='Page navigation>'
                        <ul class='pagination'>
                    ''')
            first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
            page_html_list.append(first_page)
    
            if self.current_page <= 1:
                prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
            else:
                prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
    
            page_html_list.append(prev_page)
    
            for i in range(pager_start, pager_end):
                if i == self.current_page:
                    temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
                else:
                    temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
                page_html_list.append(temp)
    
            if self.current_page >= self.all_pager:
                next_page = '<li class="disabled"><a href="#">下一页</a></li>'
            else:
                next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
            page_html_list.append(next_page)
    
            last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
            page_html_list.append(last_page)
            # 尾部添加标签
            page_html_list.append('''
                                               </nav>
                                               </ul>
                                           ''')
            return ''.join(page_html_list)
    

    文章的发布

    页面的搭建

    编辑器的选用kindeditor编辑器

    kindeditor编辑器

    网站

    使用

    下载

    部署

    修改

    html添加以下代码
    
    
    <script charset="utf-8" src="/editor/lang/zh-CN.js"></script>
    <script>
            KindEditor.ready(function(K) {
                    window.editor = K.create('#editor_id');
            });
    </script>
    

    初始参数

    输入框的长宽设置

        <script>
            KindEditor.ready(function (K) {
                window.editor = K.create('#id_comment',{
                     '100%',
                    height:'450px',
                    resizeType:0
                });
            });
        </script>
    

    items

    配置编辑器的工具栏,其中”/”表示换行,”|”表示分隔符。

    • 数据类型: Array
    • 默认值:
    [
            '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'
    ]
    
    source HTML代码
    preview 预览
    undo 后退
    redo 前进
    cut 剪切
    copy 复制
    paste 粘贴
    plainpaste 粘贴为无格式文本
    wordpaste 从Word粘贴
    selectall 全选
    justifyleft 左对齐
    justifycenter 居中
    justifyright 右对齐
    justifyfull 两端对齐
    insertorderedlist 编号
    insertunorderedlist 项目符号
    indent 增加缩进
    outdent 减少缩进
    subscript 下标
    superscript 上标
    formatblock 段落
    fontname 字体
    fontsize 文字大小
    forecolor 文字颜色
    hilitecolor 文字背景
    bold 粗体
    italic 斜体
    underline 下划线
    strikethrough 删除线
    removeformat 删除格式
    image 图片
    flash Flash
    media 视音频
    table 表格
    hr 插入横线
    emoticons 插入表情
    link 超级链接
    unlink 取消超级链接
    fullscreen 全屏显示
    about 关于
    print 打印
    code 插入程序代码
    map Google地图
    baidumap 百度地图
    lineheight 行距
    clearhtml 清理HTML代码
    pagebreak 插入分页符
    quickformat 一键排版
    insertfile 插入文件
    template 插入模板
    anchor 插入锚点

    上传文章里图片文件

    开设url地址设置

    uploadJson:'地址'

    extraFileUploadParams 参数设置

    csrf验证

    上传图片、Flash、视音频、文件时,支持添加别的参数一并传到服务器。

    • 数据类型: Array
    • 默认值: {}
    KindEditor.ready(function(K) {
            K.create('#id', {
                    extraFileUploadParams : {
                            item_id : 1000,
                            category_id : 1
                    }
            });
    });
    

    post提交数据

    返回格式(json)

    成功时
    	{
    		"error":0,
    		"url":文件路径
    	}
    失败时
    	{
    		"error":1,
    		"message":"错误信息"
    	}
    

    防止XSS攻击

    文章的简介如何获取

    • 直接将用户输入的原生的script标签直接删除
    • 将用户输入的原生script标签直接转义(

      包括

      )
    如何从html代码中筛选出纯文本内容
    如何从html代码中删除script标签
    

    使用beautifulsoup

    进行筛选

    from bs4 import BeautifulSoup
    @login_required
    def add_article(request):
        tag_list = models.Tag.objects.filter(blog=request.user.blog)
        category_list = models.Category.objects.filter(blog=request.user.blog)
    
        # 判断用户输入的文章,并获取
        if request.method == 'POST':
            title = request.POST.get('title')
            content = request.POST.get('content')
            tags_list = request.POST.getlist('tags')
            category_id = request.POST.get('category')
            # 先生成一个模块对象
            soup = BeautifulSoup(content,'html.parser')
            # soup.text # 获取纯文本
            tags = soup.find_all()         # 获取所有的标签文本
            for tag in tags:
                if tag.name == 'script':
                    tag.decompose()    #  删除script标签
            # 文章简介,截取文章的前150字符
            desc = soup.text[0:150]
            # 操作数据库
            article_obj = models.Article.objects.create(title=title,content=str(soup),desc=desc,category_id=category_id,blog=request.user.blog)
            # 去文章与标签的第三张表中手动录入数据,利用bulk_create
            obj_list = []
            for tag_id in tags_list:
                obj_list.append(models.Article2Tag(article=article_obj,tag_id=tag_id))
            models.Article2Tag.objects.bulk_create(obj_list)
            # 重定向
            return redirect('/backend/')
        return render(request,'backend/add_article.html',locals())
    
    

    代码

    后台管理

    {% extends 'backend/backend_base.html' %}
    
    
    {#文章页面展示#}
    {% block article %}
        <table class="table table-striped table-hover">
        <thead>
        <tr>
            <th>标题</th>
            <th>发布时间</th>
            <th>评论数</th>
            <th>点赞数</th>
            <th>操作</th>
            <th>操作</th>
        </tr>
        </thead>
    
        <tbody>
            {% for article in page_queryset %}
                <tr>
                    <td><a href="/{{ request.user.username }}/article/{{ article.pk }}">{{ article.title }}</a></td>
                    <td>{{ article.create_time|date:'Y-m-d' }}</td>
                    <td>{{ article.comment_num }}</td>
                    <td>{{ article.up_num }}</td>
                    <td><a href="#">编辑</a></td>
                    <td><a href="#">删除</a></td>
                </tr>
            {% endfor %}
        </tbody>
        </table>
        <div class="text-right">{{ page_obj.page_html|safe }}</div>
    {% endblock %}
    

    添加文章

    add_article.py

    {% extends 'backend/backend_base.html' %}
    
    
    {% block article %}
        <h3>添加文章</h3>
    
        <form action="" method="post">
            {% csrf_token %}
            <p>标题</p>
            <p><input type="text" name="title" class="form-control"></p>
            <p>内容使用kind编辑器</p>
            <p>
                <textarea name="content" id="id_comment" cols="30" rows="10" class="form-control"></textarea>
            </p>
            <p>标签</p>
            <p>
                {#            渲染标签#}
                {% for tag in tag_list %}
                    <input type="checkbox" name="tags" value="tag.pk">&nbsp;&nbsp;{{ tag.name }}&nbsp;&nbsp;&nbsp;&nbsp;
                {% endfor %}
            </p>
    
            <p>分类</p>
            <p>
                {#            渲染分类#}
                {% for category in category_list %}
                    <input type="radio" name="category" value="category.pk">&nbsp;&nbsp;{{ category.name }}&nbsp;&nbsp;
                    &nbsp;&nbsp;
                {% endfor %}
            </p>
            <input type="submit" class="btn btn-success">
        </form>
    
    
        {#kindeditor编辑器的部署#}
        <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
        <script>
            KindEditor.ready(function (K) {
                window.editor = K.create('#id_comment',{
                     '100%',
                    height:'450px',
                    resizeType:0,
                    uploadJson:'/upload_img/',
                    extraFileUploadParams : {
                            csrfmiddlewaretoken:'{{ csrf_token }}'
                    }
                });
            });
        </script>
    {% endblock %}
    

    更改头像

    set_avatar

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户更改头像</title>
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
        {% load static %}
        <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
        <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
    
    </head>
    <body>
    
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <h3>原头像:</h3>
        <img src="/media/{{ request.user.avatar }}" alt="">
    
     <div class="form-group">
                        <label for="mdd">新头像
                            <img src="/static/img/default.jpg" alt="" width="100px" style="margin-left: 10px" id="img">
                        </label>
    {#                    将input上传文件的框隐藏#}
                        <input type="file" name="avatar" id="mdd" style="display: none;">
                    </div>
        <input type="submit" class="btn btn-primary">
    
    </form>
    
        <script>
                $('#mdd').on('change',function () {
            {#内置对象FileReader,完成文件的读取操作#}
             let MyFileReader = new FileReader();
             // 获取用户上传的文件对象
            let fileObj = $(this)[0].files[0];
            // 让文件阅读器读取文件
            MyFileReader.readAsDataURL(fileObj);  // 异步io操作
            // 等待文件io操作读取完成再执行下面的代码
            MyFileReader.onload = function () {
                // 将读取之后的内容替换到img标签src属性中
                $('#img').attr('src',MyFileReader.result)
            }
        });
    
        </script>
    </body>
    </html>
    

    后台管理

    from app01.utils.mypage import Pagination
    # 后台管理
    @login_required
    def backend(request):
        article_list = models.Article.objects.filter(blog=request.user.blog)
        # 分页器的使用
        page_obj = Pagination(current_page=request.GET.get('page',1),all_count=article_list.count())
        page_queryset = article_list[page_obj.start:page_obj.end]
    
        return render(request,'backend/backend.html',locals())
    
    
    from bs4 import BeautifulSoup
    @login_required
    def add_article(request):
        tag_list = models.Tag.objects.filter(blog=request.user.blog)
        category_list = models.Category.objects.filter(blog=request.user.blog)
    
        # 判断用户输入的文章,并获取
        if request.method == 'POST':
            title = request.POST.get('title')
            content = request.POST.get('content')
            tags_list = request.POST.getlist('tags')
            category_id = request.POST.get('category')
            # 先生成一个模块对象
            soup = BeautifulSoup(content,'html.parser')
            # soup.text # 获取纯文本
            tags = soup.find_all()         # 获取所有的标签文本
            for tag in tags:
                if tag.name == 'script':
                    tag.decompose()    #  删除script标签
            # 文章简介,截取文章的前150字符
            desc = soup.text[0:150]
            # 操作数据库
            article_obj = models.Article.objects.create(title=title,content=str(soup),desc=desc,category_id=category_id,blog=request.user.blog)
            # 去文章与标签的第三张表中手动录入数据,利用bulk_create
            obj_list = []
            for tag_id in tags_list:
                obj_list.append(models.Article2Tag(article=article_obj,tag_id=tag_id))
            models.Article2Tag.objects.bulk_create(obj_list)
            # 重定向
            return redirect('/backend/')
        return render(request,'backend/add_article.html',locals())
    
    
    import os
    from BBS import settings
    # 文章内上传文件的函数
    def upload_img(request):
        back_dic = {'error':0}
        if request.method == 'POST':
        # print(request.FILES)   # 打印键值对
            # 获取用户上传的图片,然后保存在本地
            file_obj = request.FILES.get('imgFile')
            # 手动拼接存储的文件路径
            file_path = os.path.join(settings.BASE_DIR,'media','article_img')
            if not os.path.isdir(file_path):
                os.mkdir(file_path)
            # 保存文件
            img_path = os.path.join(file_path,file_obj.name)
            with open(img_path,'wb') as f:
                for l in file_obj:
                    f.write(l)
            # 成功返回0,back_dic
            _url = '/media/article_img/%s'%file_obj.name
            back_dic['url'] = _url
    
            return JsonResponse(back_dic)
    
    
    
    @login_required
    def set_avatar(request):
        # 展示用户之前的头像,用户上传新的头像
        if request.method == 'POST':
            file_obj = request.FILES.get('avatar')
            # 方式一,不会自动拼接路径
            models.Userinfo.objects.filter(pk=request.user.pk).update(avatar=file_obj)
            # 方式二
            request.user.avatar = file_obj
            request.user.save()
            return redirect('/home/')
        return render(request,'set_avatar.html',locals())
    
  • 相关阅读:
    基础
    条件语句/变量和基本数据类型
    编程语言介绍
    asp.net中log4net使用方法
    web布到服务器上出错
    《转》IEnumerable、IEnumerator两个接口的认识
    异步ADO.NET
    Session的使用
    AJAX参数及各种HTTP状态值
    简易的抓取别人网站内容
  • 原文地址:https://www.cnblogs.com/fwzzz/p/12037139.html
Copyright © 2011-2022 走看看