灵虚御风
醉饮千觞不知愁,忘川来生空余恨!

导航

 
BBS项目:
    day1
    数据库表设计
    注册功能
    登录功能
    主页搭建
    day2
    admin后台管理
    用户头像展示
    个人主页搭建
    侧边栏渲染
        inclusion_tag
    文章详情页
    day3
    点赞点踩功能
    用户评论
    day4
    后台管理
        当前用户所有的文章展示
            添加功能
            编辑
            删除
        处理xss攻击 文章简介的获取
        上传图片操作
        用户头像修改
BBS知识点划分
公司开发项目的流程
    1.需求分析
        客户提需求但是并不是完全按照客户需求来
        产品经理和架构师+开发组组长
        去之前架构师和开发组组长 会提前先预想一套方案 
        有意识的引导客户朝着自己已经想好的解决方案上去提需求
    2.项目设计
        框架的使用 语言的使用 数据库的使用(主库+缓存数据库) 功能的划分 框架的设计...
        报价(项目的周期,项目所使用的技术复杂度,参与开发的人员个数(一个开发人员一天算1000~2000))
        先交给产品经理  交给公司财务审批  老板签字确认......
    3.分组开发
        将一个大的项目拆分成多个小的模块 交由不同的组 或者不同的开发人员进行开发
        组长分发给下面的组员(朝设计好的框架内填写代码)
    4.测试
        自己写测试脚本测试(看公司规定)
        千万不要犯一些显而易见的bug 如果在测试部门发现了显而易见的bug会扣绩效
        测试部分测试(必须的)
    5.交付上线
        公司的运维人员或者是对方公司的运维人员        

数据库表设计
    用户表(利用auth_user那张表 自己额外再扩展几个字段)
        phone
        avatar
        create_time
        
        blog  一对一个人站点表
         
    个人站点表
        site_name 
        site_title
        site_theme
    
    标签表
        name
        
        blog   一对多个人站点
    
    分类表
        name
        
        blog   一对多个人站点
    
    文章表
        title
        desc
        content
        create_time
        
        blog        一对多个人站点
        tag            多对多标签
        category    一对多分类
        
        # 数据库设计优化(******)
        comment_num   普通字段
        up_num         普通字段
        down_num     普通字段
        
        
        
        
    
    点赞点踩表
    
        user            一对多用户表
        article         一对多文章表   
        is_up            0/1
        
        
        user            article      is_up
        1                1                1
        1                2                1
        1                3                1
        2                1                1
        
    评论表
        user            一对多用户表        
        article            一对多文章表  
        comment                
        create_time        
        parent           一对多评论表(自关联)    父评论的id  如果有值说明你是子评论  如果没有值说明你是父评论

注册功能

登陆功能
    图片相关功能的模块
        pip3 install pillow

主页搭建
readme1
项目开发流程
    项目需求
    项目设计
    分组开发
    测试
    交付上线

数据库表设计
    总共7张表
        用户表 Userinfo
        个人站点表 blog
        文章标签表 category
        文章分类表 tag
        文章表 article
        点赞点踩表 upanddown
        评论表 comment        自关联

注册
    1.forms组件
        用户名
        密码
        确认密码
        邮箱
    2.利用forms组件渲染前端页面
        不借助于form表单提交 利用ajax提交

    3.手动搭建获取用户头像的input框
        1.点击图片弹出选择框 利用label标签与input有绑定的关系的特性
        2.再利用文件阅读器 实现图片的实时展示

    4.后端创建用户的时候 你可以利用**{}的形式 帮你节省代码
        1.在获取用户头像的时候 你一定要做一步判断

    5.数据校验不通过  你要将不过的信息 展示到对应的input框下
        1.forms组件渲染的input的框的id值有固定的格式 id_字段名
        2.前端for循环字段 手动拼接处id值
        3.给span添加报错信息  给所在的div加has-error

    6.用户再次点击input,将报错信息个has-error移除



登陆
    1.图片验证码
        1.必会的  一定要自己能够写出产生随机验证码的代码
        2.img标签的src属性 可以写具体的图片路径,还可以写url,接收二进制数据直接再转换成图片展示出来
        3.借助于pillow模块生成图片验证码必备的对象
        4.后端校验图片验证码是否正确  然后再判断用户输入的用户名和密码是否正确


首页搭建
    自己完成
        注销和修改密码的功能
readme1回顾总结
1.admin后台管理
    admin后台管理 是一个能够帮助你快速的实现注册了的模型表数据的增删改查

    使用:
        在应用中找到admin.py文件  然后注册 你想要操作的默写表即可
        使用超级管理员账户 即可登录后台进行数据的管理
        from django.contrib import admin
        from app01 import models
        # Register your models here.
        admin.site.register(models.UserInfo)
        """
        models.py 创造admin 展示 中文转义
            class Meta:
                # verbose_name = '用户表' # admin显示 用户表s
                verbose_name_plural = '用户表' # admin显示 用户表

            # 表关联名称的打印,方便admin使用
            def __str__(self):
                return self.title
        """
    窥探admin内部源码
        http://127.0.0.1:8000/admin/app01/userinfo/  展示数据
        http://127.0.0.1:8000/admin/app01/userinfo/add/  添加数据
        http://127.0.0.1:8000/admin/app01/userinfo/2/change/  编辑数据
        http://127.0.0.1:8000/admin/app01/userinfo/2/delete/  删除数据

        http://127.0.0.1:8000/admin/app01/article/  展示数据
        http://127.0.0.1:8000/admin/app01/article/add/  添加数据
        http://127.0.0.1:8000/admin/app01/article/2/change/  编辑数据
        http://127.0.0.1:8000/admin/app01/article/2/delete/  删除数据




    django admin会自动给注册了的模型表生成起码四条url(增删改查)
    # 路由分发的本质
    url(r'^index1/',([
        url(r'^index_1/',([],None,None)),
        url(r'^index_2/',([],None,None)),
        url(r'^index_3/',([],None,None)),
                     ],None,None)),
2.用户头像展示(media配置)
    media配置 可以暴露给用户任意的后端资源

    网站所使用的静态文件默认都是放在static文件夹下
    用户上传的静态文件 也应该放在一个固定的文件夹下
        我们目前实现的是用户上传的头像 固定放在avatar文件夹下了
        我们应该创建一个类似于static文件夹  里面在根据文件的不同创建不同的文件夹存储数据

    # 规定 用户上传的所有的静态文件 全部放到media文件夹下
    MEDIA_ROOT = os.path.join(BASE_DIR,'media')


    from django.views.static import serve
    # 手动暴露后端文件夹资源 http://127.0.0.1:8000/media/avatar/default.jpg
    url(r'^media/(?P<path>.*)',serve,{"document_root":settings.MEDIA_ROOT}),




3.个人主页搭建
    404页面 直接拷贝博客园404页面即可
    但是会发现图片加载不出来    是因为 人家设置了   图片防盗链
    什么是图片防盗链?
        实现原理:校验当前请求的url是否是我本网站的
        如果是我正常给资源,如果不是 直接拒绝
        Referer: http://127.0.0.1:8000/fsdafasfd/

        Referer请求头 表示了你这个网页上一次是从哪里来的
        爬虫
    解决方式
        1.爬虫直接爬取所有图片 保存到自己的数据库
        2.如果图片需求量不大的情况下 你可以采用人工智能方式 手动下载到本地
4.侧边栏渲染


    inclusion_tag

    日期归档
    id      content    create_time        month
    1        111            2019-1-1         2019-1
    2        322            2019-2-11         2019-2
    3        222            2019-1-21         2019-1
    4        555            2019-1-22         2019-1


        -官方提供
        from django.db.models.functions import TruncMonth
        models.Article.objects
        .annotate(month=TruncMonth('create_time'))  # Truncate to month and add to select list
        .values('month')  # Group By month
        .annotate(c=Count('id'))  # Select the count of the grouping
        .values('month', 'c')  # (might be redundant, haven't tested) select month and count


5.侧边栏功能





6.文章详情页
    前端页面搭建







7.点赞点踩功能
8.用户评论



9.后台管理
readme2
1.django admin后台管理
    能够简单快捷的帮你管理注册了的模型表数据的增删改查
    如果想使用admin后台管理 必须要是超级用户
    如何创建超级用户
        createsuperuser
    使用步骤
        1.先去对于的应用下的admin.py文件中注册你想要操作的模型表
            admin.site.register(models.UserInfo)
    页面上
        按照应用的不同 展示每一个应用下注册了的模型表

    在我们的bbs项目中 你在使用admin的时候 一定要将表关系绑定完全
    尤其是用户跟个人站点 千万不要忘记绑定
        1.文章表
        2.个人站点
        3.文章分类
        4.用户表绑定个人站点
        5.文章和标签表


图片防盗链
    根据refer请求头来判定是否是本网站发过来的请求



2.首页展示
    导航条用户相关功能   注销   修改密码
    1.拷贝样式
    2.用户头像展示

        网站使用的静态文件一般都是放在static文件夹下(第三方,css,js,img,font)
        用户上传的静态文件也应该放在某一个专门的文件夹下(css,js,img,font)
        1.初次设计的时候 只考虑到了用户的头像放到了avatar文件夹下
        2.升级为  用户上传的任意的静态文件都能够分门别类的在某一个文件夹下
            settings.py中配置
                MEDIA_ROOT = os.path.join(BASE_DIR,'media')
                # 配置完成后用户上传的静态文件都会自动存入该文件夹下(该文件夹内还会自动创建对于的文件夹)
            用户在浏览器中能够敲url访问到对应的资源都是因为后端开设该url的访问接口
        3.media路由配置
            from django.views.static import serve
            from app01 import settings

            urls = [
                url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})
            ]
            # 注意该配置可以支持你暴露后端任意文件夹资源
3.个人站点页面搭建
    1.页面布局是 左右布局
    2.左侧是侧边栏
        文章分类
        文章标签
        日期归档
    3.右侧就是当前该用户所写的所有的文章
    4.侧边栏的前端页面渲染
        # 1 查询当前用户每一个分类下的文章数
        # 2 查询当前用户每一个标签下的文章数
        # 3 按照年月对文章进行分组并统计个数
            参考官方提供的方式  直接拷贝代码 完成了需求
        将上述查询结果传递到前端完成侧边栏前端样式展示
    5.侧边栏筛选功能
        1.参考博客园 设计筛选功能的url
            username/category/1
            username/tag/1
            username/archive/2019-10
            # 利用正则 将三条url整合成一条
        2.筛选功能无外乎就是对已经查询出来的数据再进行一层过滤筛选
4.模板的继承 自定义inclusion_tag
    1 在应用内新建一个名字必须交templatetags文件夹
    2 在该文件夹内新建一个任意名称的py文件
    3 在该py文件固定先写下面两行代码
        from  django import template

        register = template.Library()
5.文章详情页
readme2回顾总结
        点赞点踩
            1.校验用户是否登录
            2.校验当前用户点赞点踩的这篇是否是自己的
            3.校验当前用户是否已经对这篇文章进行过点赞点踩

        前端如何区分用户是点了赞 还是点了踩???

        1.前端需要注意的地方
            数字加减







        文章的评论
            根评论
                render渲染
                DOM渲染
            子评论
                1.每一个评论框都有一个回复按钮
                    点击回复按钮到底发生了几件事
                        获取像要评论的评论人的用户名

                        评论框自动聚焦

                        自动换行





国庆作业:
点赞点踩 作业
    1.新增取消功能
        """
            1.需要判断用户是否点过了 只有点过了你才应该渲染取消按钮
            2.给点赞点踩表新增一个is_delete字段 用来表示用户删除该条记录
        """




    2.如果用户点了  明确的提示用户已经点了赞还是踩

1.先掌握老师上课将的bbs版本
2.基于该版本 实现多种功能 模态框的形式展示
readme3
点赞点踩
    1.前端页面点赞点踩样式拷贝
        1.html和css代码 两者都需要拷贝
        2.点赞点踩的图片设置了防盗链的 要想永久建议下载到本地
        3.前端页面如何区分用户是点赞还是点踩
            给赞和踩的图标加了一个公共的样式类
            给这个样式类绑定了一个点击事件
            再利用this指代的就是当前被操作对象
            只需要通过判断当前被点击对象是否有某个独立的样式类
        4.发送ajax请求
            参数只需要两个 文章的主键  点赞或点踩
        5.后端逻辑
            1.后端接收到的用户点赞点踩参数是一个前端js的布尔值字符串
                你可以直接利用json.loads转成后端的布尔值类型
            2.验证当前请求是否是ajax请求
                request.is_ajax()
            3.验证当前用户是否登录
                request.user.is_authenticated()
            4.验证当前文章是否是当前用户自己写的
                根据前端传过来的文章主键查询出文章对象
                根据文章对象查询出作者与request.user当前登录用户做比对
            5.验证当前文章是否已经被当前用户点过赞或踩了
                根据文章主键和当前登录用户对象去点赞点踩表中筛选数据
                一旦有数据  说明该用户已经给当前文章点过赞或者踩
            6.操作数据库
                一旦要注意在操作点赞点踩表的时候 一旦要去文章表中将
                点赞或点踩普通字段同步修改
        6.前端展示提示信息
            点赞或点踩成功需要将前端页面对应的数字在原来的基础上加一
            注意在加的时候一定要转换成数值类型相加
            Number()    类似后端 int()



评论
    1.前端获取用户评论样式搭建
        应该做到只有登录的用户才能看到评论框
    2.前端利用ajax发送评论请求
        当前文章的主键
        评论的内容
    3.后端
        1.校验当前请求是否是ajax请求
            获取主键 评论内容
        2.操作数据库
            利用事务完成两个地方数据的同步
                1.文章表里面的评论普通字段
                2.评论表记录
        3.前端展示评论楼信息
            后端获取当前文章所有的评论传递到前端
    4.评论渲染
        当用户点击提交按钮 除了渲染之外 你还应该将textarea框内容清空
        1.DOM临时渲染
            1.获取当前评论人的用户名和评论内容
            2.利用es6新语法 模板字符串 完成字符串的替换
            3.查找ul标签 将创建的li标签添加到ul标签内
        2.render永久渲染
            在渲染页面的时候  for循环文章所有的评论一一渲染即可
    5.子评论的功能
        子评论和根评论的唯一区别仅仅在于parent_id字段是否有值
        突破口:点击回复按钮发送了几件事
        1.获取当前登录用户想评论的那条评论人的用户名 拼接成 @用户名\r\n
            利用标签可以支持任意多个自定义属性 给回复按钮标签 添加username={{comment.user.username}}

        2.将拼接完成的内容的添加到textarea框中
        3.textarea框自动聚焦
        4.无论是提交根评论还是子评论都是点击的一个按钮
            1.提交子评论和父评论唯一的差距就在于你是否传了parent_id
            2.数据库中parent_id是可以为空的 也就意味你在创建数据的时候 如果传空也不会有影响
        5.无论是根评论 子评论我都可以提交一个parent_id无论它是否有值
            后端只需要接收parent_id然后在create方法中直接添加即可  至此后端代码一行都不需要再修改了
        6.在点击回复按钮的时候 除了获取评论人的用户名之外 还应该回去当前评论数据的主键值
            还需要给回复按钮 加一个自定义数据  pk = {{comment.pk}}
            点击回复按钮能够获取到评论主键值  什么时候用的?
            当你在点击提交评论按钮的时候才会用到评论主键值(一个函数需要引用林外一个函数中的某个名字)
            应该在全局设置一个专门存储评论主键值的字段parent_id = null;默认等于None
            点击完回复按钮之后才会对全局的parent_id进行修改
        7.提交子评论内容中含有@用户名\r\n 这一段内容并不是用户自己写的 应该去除
            前端处理
                获取\n所在的索引值   然后你自己思考了一下 切片取值 是顾头不顾尾 所以应该给索引加1
                利用slice切片操作(从0开始到索引结束的内容全部去除 保留剩下部分)
        8.你会发现当你提交了一次子评论之后 页面不刷新的情况 永远无法提交根评论了  因为全局的parent_id字段一直有值
            你应该在每一次提交完成后  清空parent_id字段

        9.评论渲染的时候
            如何渲染出子评论
            利用orm表查询
            {{ comment.parent.user.username }}
readme3回顾总结
    所有的文件夹内部都可以再根据功能的细化 再细化成多个文件夹

    后台管理
        当前用户所有的文章展示
            添加功能
            编辑
            删除
        处理xss攻击  文章简介的获取
            1.文章简介的获取
                截取150个中文字符
            2.防止用户写script脚本
                1.获取用户输入的所有的script标签直接删除
                2.给script转义
        模块介绍
            beautifulsoup4
            简称 BS4

            pip3 install beautifulsoup4

            #  1 先生成一个BeautifulSoup对象
            soup = BeautifulSoup(content,'html.parser')
            for tag in soup.find_all():
                # 针对script标签 应该直接删除
                # print(tag.name)  # 获取当前html页面所有的标签
                if tag.name == 'script':
                    tag.decompose()  # 将符合条件的标签删除

            # 文章简介应该是150个文本内容
            desc = soup.text[0:150]


        上传图片操作


        用户头像修改



1.数据库的设计  (7张表 一对一对一 一对多对多 其余的都是一对多)
2.forms组件完成注册功能
    注册功能前端错误信息渲染 需要你自己找出每一个input id值的规律
3.登陆功能
    图片验证码

4.首页展示
    django admin后台管理
     用户头像展示
        media配置

5.个人站点
    侧边栏展示
    侧边栏筛选功能
    inclusion_tag

6.文章详情页
    点赞点踩

    评论

7.后台管理
readme4

 

  

backend/

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

{% block article %}
    <h3>添加文章</h3>
    <p>标题</p>
    <form action="" method="post">
        {% csrf_token %}
        <p><input type="text" name="title" class="form-control" id="id_title"></p>
{#        kindeditor编辑器#}
        <p>内容(kindeditor编辑器,支持拖放/粘贴上传图片)</p>
        <p>
            <textarea name="content" id="id_content" cols="30" rows="10"></textarea>
        </p>
        <div class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title">文章标签</h3>
            </div>
            <div class="panel-body">
                {% for tag in tag_list %}
                    <input type="checkbox" name="tag" value="{{ tag.pk }}">{{ tag.name }}&nbsp;&nbsp;&nbsp;
                {% endfor %}
            </div>
        </div>
        <div class="panel panel-warning">
            <div class="panel-heading">
                <h3 class="panel-title">文章分类</h3>
            </div>
            <div class="panel-body">
                {% for category in category_list %}
                    <input type="radio" name="category" value="{{ category.pk }}">{{ category.name }}&nbsp;&nbsp;&nbsp;
                {% endfor %}
            </div>
        </div>
        <input type="submit" class="btn btn-danger" value="发布">
    </form>
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#id_content', {
                width: '100%',
                height: '450px',
                resizeType: 1,
                uploadJson : '/upload_img/',
                extraFileUploadParams : {
                        'csrfmiddlewaretoken':'{{ csrf_token }}'
                }
            });
        });
    </script>
{% endblock %}
backend/add_article.html
{% extends 'backend/base.html' %}

{% block article %}

{#    接收当前用户所有的文章#}

{#标题    发布状态    评论数    阅读数    操作    操作#}
    <table class="table table-hover table-striped">
        <thead>
            <tr>
                <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.comment_num }}</td>
                    <td>{{ article.up_num }}</td>
                    <td><a href="">编辑</a></td>
                    <td><a href="">删除</a></td>
                </tr>

            {% endfor %}

        </tbody>
    </table>
    <div class="pull-right">
    {{ page_obj.page_html|safe }}
    </div>
{% endblock %}
backend/backend.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>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>
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <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 navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="/home/">{{ request.user.blog.site_title }}</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <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="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></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">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
              <li><a>{{ 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="/edit_avatar/">修改头像</a></li>
                <li><a href="#" data-target=".bs-example-modal-lg" data-toggle="modal">修改密码</a></li>
                <li><a href="/backend/">后台管理</a></li>
                <li role="separator" class="divider"></li>
                <li><a href="/logout/">注销</a></li>
              </ul>
            </li>
          {% else %}
            <li><a href="#">用户</a></li>

            <li><a href="/login/">登录</a></li>
                <li><a href="/register/">注册</a></li>

          {% endif %}
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<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 text-center">
                    <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
                      更多操作
                    </a>
                  </h4>
                </div>
                <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
                  <div class="panel-body text-center">
                      <a href="/add_article/">添加文章</a>
                  </div>
                </div>
                <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
                  <div class="panel-body text-center">
                    <a href="">添加随笔</a>
                  </div>
                </div>
                <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
                  <div class="panel-body text-center">
                    <a href="">草稿箱</a>
                  </div>
                </div>
                <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
                  <div class="panel-body text-center">
                    <a href="">其他</a>
                  </div>
                </div>
              </div>
            </div>

        </div>
        <div class="col-md-10">
            <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">文章</a></li>
            <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">随笔</a></li>
            <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">草稿</a></li>
            <li role="presentation"><a href="#file" aria-controls="file" role="tab" data-toggle="tab">文件</a></li>
            <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">设置</a></li>
          </ul>

          <!-- Tab panes -->
          <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">草稿页面</div>
            <div role="tabpanel" class="tab-pane" id="file">文件页面</div>
            <div role="tabpanel" class="tab-pane" id="settings">设置页面</div>
          </div>

        </div>


        </div>
    </div>
</div>
</body>
</html>
backend/base.html
{% extends 'base.html' %}
{% block css %}
    <style>
        #div_digg {
            float: right;
            margin-bottom: 10px;
            margin-right: 30px;
            font-size: 12px;
            width: 128px;
            text-align: center;
            margin-top: 10px;
        }

        .diggit {
            float: left;
            width: 46px;
            height: 52px;
            background: url(/static/img/up.gif) no-repeat;
            text-align: center;
            cursor: pointer;
            margin-top: 2px;
            padding-top: 5px;
        }

        .buryit {
            float: right;
            margin-left: 20px;
            width: 46px;
            height: 52px;
            background: url(/static/img/down.gif) no-repeat;
            text-align: center;
            cursor: pointer;
            margin-top: 2px;
            padding-top: 5px;
        }

        .clear {
            clear: both;
        }

        .diggword {
            margin-top: 5px;
            margin-left: 0;
            font-size: 12px;
            color: red;
        }
    </style>
{% endblock %}

{% block content %}
    <h2>{{ article_obj.title }}</h2>
    <p>{{ article_obj.content|safe }}</p>
    {#点赞点踩前端页面开始#}
    <div class="clearfix">
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips">
            </div>
        </div>
    </div>
    {#    点赞点踩踩前端页面结束#}
    {#    评论列表展示#}
    <ul class="list-group">
        {% for comment in comment_list %}
            <li class="list-group-item">
                <span>#{{ forloop.counter }}楼</span>
                <span>{{ comment.create_time|date:'Y-m-d' }}</span>
                <span><a href="/{{ comment.user.username }}/">{{ comment.user.username }}</a></span>
                <span class="pull-right reply" username="{{ comment.user.username }}" pk="{{ comment.pk }}"><a>回复</a></span>


                <div>
                    {% if comment.parent %}
                        <p>@{{ comment.parent.user.username }}</p>
                    {% endif %}
                    {{ comment.content }}
                </div>
            </li>
        {% endfor %}

    </ul>



    {#    评论框#}
    {% if request.user.is_authenticated %}



        <div>
            <p>发表评论</p>
            <p>
                昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
                          value="{{ request.user.username }}">
            </p>
            <p>评论内容:</p>
            <p><textarea name="" id="id_content" cols="60" rows="10"></textarea></p>
            <p><input type="button" class="btn btn-primary" value="提交评论" id="id_comment"></p>
        </div>
    {% endif %}
{% endblock %}
{% block js %}
    <script>
        $('.action').click(function () {
            // 更具被点击的标签是否有 比如说类属性diggit  如果有说明用户点的是赞  如果没有说明用户点的是踩
            var isUp = $(this).hasClass('diggit');
            var $spanEle = $(this).children();
            // 发送ajax请求
            $.ajax({
                url: '/up_or_down/',  // 朝后端专门处理点赞点踩逻辑的视图函数发请求
                type: 'post',
                data: {
                    'article_id':{{ article_obj.pk }},
                    'is_up': isUp,
                    'csrfmiddlewaretoken': '{{ csrf_token }}'
                },
                success: function (data) {
                    if (data.code == 100) {
                        // 如果点成功了 应该将对应的数字加1
                        $spanEle.text(Number($spanEle.text()) + 1);
                        $('.diggword').text(data.msg)
                    } else {
                        $('.diggword').html(data.msg)
                    }
                }
            })
        });


        // 提前定义一个存储评论id的变量
        var parentId = null;
        // 评论相关js代码
        $('#id_comment').click(function () {
            // 只有是子评论时候 你才应该回去评论内容 截取
            var content = $('#id_content').val();
            if (parentId){
                // 获取\n所对应的索引值 又由于切片是顾头不顾尾  所以应该在获取的\n索引基础之上再加1
                var nIndex = content.indexOf('\n') + 1;
                // 切片获取用户输入的内容
                content = content.slice(nIndex)  // slice会将索引前面的所有的内容切除
            }
            $.ajax({
                url: '/comment/',
                type: 'post',
                data: {
                    'article_id':{{ article_obj.pk }},
                    'content': content,
                    'csrfmiddlewaretoken': '{{ csrf_token }}',
                    'parentId':parentId
                },
                success: function (data) {
                    if (data.code == 100) {
                        // 获取当前用户评论的内容
                        var conTent = $('#id_content').val();
                        var username = '{{ request.user.username }}';
                        // 创建评论楼样式标签 添加到ul标签内
                        var temp = `
                         <li class="list-group-item">
                            <span><span class="glyphicon glyphicon-comment"></span><a href="/${username}/">${username}</a></span>
                            <p>${conTent}</p>
                        </li>
                        `;
                        // 找到url标签 把上面的内容添加到标签内即可
                        $('.list-group').append(temp);
                        // 将textarea框中的内容清空
                        $('#id_content').val('');
                        // 每次发送完毕之后 一定要将parentId清空
                        parentId = null;


                    }
                }
            })
        });
        // 回复按钮触发的事件
        $('.reply').click(function () {
            var targetUserName = $(this).attr('username');
            var commentId = $(this).attr('pk');
            var temp = '@' + targetUserName + '\r\n';
            // 找到评论框 将上面的内容塞进去
            $('#id_content').val(temp).focus();
            // 给全局变量赋值
            parentId = commentId;
        })

    </script>
{% endblock %}
article_detail.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>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>
    <link rel="stylesheet" href="/media/css/{{ user_obj.blog.site_theme}}">
    {% block css %}

    {% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <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 navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="/home/">{{ blog.site_title }}</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <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="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></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">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
              <li><a>{{ 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="/edit_avatar/">修改头像</a></li>
                <li><a href="#" data-target=".bs-example-modal-lg" data-toggle="modal">修改密码</a></li>
                <li><a href="/backend/">后台管理</a></li>
                <li role="separator" class="divider"></li>
                <li><a href="/logout/">注销</a></li>
              </ul>
            </li>
          {% else %}
            <li><a href="#">用户</a></li>

            <li><a href="/login/">登录</a></li>
                <li><a href="/register/">注册</a></li>

          {% endif %}
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            {% load mytag %}
            {% left_menu username %}
        </div>
        <div class="col-md-9">
        {% block content %}

        {% endblock %}

        </div>
    </div>
</div>


{% block js %}

{% endblock %}
</body>
</html>
base.html
{% extends 'base.html' %}

{% block content %}
<h2>修改头像</h2>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="file" name="myfile" class="form-control">
        <input type="submit" class="btn btn-primary">
    </form>
{% endblock %}
edit_avatar.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>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>
<h2>修改头像</h2>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="file" name="myfile" class="form-control">
        <input type="submit" class="btn btn-primary">
    </form>
</body>
</html>
edit_avatar1.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>404_页面不存在 - BBS</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>
    <style>
        body{
            margin:8% auto 0;
            max-width: 550px;
            min-height: 200px;
            padding: 10px;
            font-family: 'PingFang SC','Microsoft YaHei','Helevetica Neue','Helvetica','Arial',sans-serif;
            font-size: 14px;
        }
        p{
            color: #555;
            margin: 10px 10px;
        }
        img {
            border:0px;
        }
        .d{
            color: #404040;
        }
    </style>
</head>
<body>
    <a href='https://www.cnblogs.com/'><img src='/static/img/logo_small.gif' alt='cnblogs' /></a>
    <p><b>404.</b> 抱歉,您访问的资源不存在。</p>
    <p class='d'>请确认您输入的网址是否正确,如果问题持续存在,请发邮件至 contact&#64;cnblogs.com 与我们联系。</p>
    <p><a href='/home/'>返回网站首页</a></p>

</body>
</html>
errors.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>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>
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <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 navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">BBS</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <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="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></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">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
              <li><a>{{ 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="/edit_avatar/">修改头像</a></li>
                <li><a href="#" data-target=".bs-example-modal-lg" data-toggle="modal">修改密码</a></li>
                <li><a href="/backend/">后台管理</a></li>
                <li role="separator" class="divider"></li>
                <li><a href="/logout/">注销</a></li>
              </ul>
            </li>
          {% else %}
            <li><a href="#">用户</a></li>

            <li><a href="/login/">登录</a></li>
                <li><a href="/register/">注册</a></li>

          {% endif %}
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-primary">
              <div class="panel-heading">找工作,来来来!!!</div>
              <div class="panel-body">
                马到必定成功(别墅靠大海)
              </div>
            </div>

            <div class="panel panel-danger">
              <div class="panel-heading">
                <h3 class="panel-title">百万大奖</h3>
              </div>
              <div class="panel-body">
                赶紧拿起电话领走吧:110
              </div>
            </div>
            <div class="panel panel-warning">
              <div class="panel-heading">
                <h3 class="panel-title">在线直播</h3>
              </div>
              <div class="panel-body">
                赶快点击,性感荷官在线发牌
              </div>
            </div>
            <div class="panel panel-danger">
              <div class="panel-heading">
                <h3 class="panel-title">百万大奖</h3>
              </div>
              <div class="panel-body">
                赶紧拿起电话领走吧:110
              </div>
            </div>

        </div>
        <div class="col-md-8">
        <ul class="media-list">
        {% for article in page_queryset %}
          <li class="media">
            <h4 class="media-heading">
                <a href="/{{ article.blog.userinfo.username }}/article/{{ article.pk }}">{{ article.title }}</a>
            </h4>
            <div class="media-left">
              <a href="#">
                <img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" alt="..." height="60">
              </a>
            </div>
            <div class="media-body">
                {{ article.desc }}
            </div>
          {#   超级小小黑 发布于 2019-09-27 09:27 评论(0)阅读(64)#}
              <br>
            <div>
                <span><a href="/{{ article.blog.userinfo.username }}/">{{ article.blog.userinfo.username }}</a></span>
                <span>发布于&nbsp;&nbsp;{{ article.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
                <span>
                <span><span class="glyphicon glyphicon-comment"></span>评论数{{ article.comment_num }}&nbsp;&nbsp;</span>
                <span><span class="glyphicon glyphicon-thumbs-up">点赞数{{ article.up_num }}</span>
                </span>

            </div>
          <br>
          </li>
            <hr>
            {% endfor %}
        </ul>

            {{ page_obj.page_html|safe }}
        </div>
        <div class="col-md-2">

            <div class="panel panel-primary">
              <div class="panel-heading">找工作,来来来!!!</div>
              <div class="panel-body">
                马到必定成功(别墅靠大海)
              </div>
            </div>

            <div class="panel panel-danger">
              <div class="panel-heading">
                <h3 class="panel-title">百万大奖</h3>
              </div>
              <div class="panel-body">
                赶紧拿起电话领走吧:110
              </div>
            </div>
            <div class="panel panel-warning">
              <div class="panel-heading">
                <h3 class="panel-title">在线直播</h3>
              </div>
              <div class="panel-body">
                赶快点击,性感荷官在线发牌
              </div>
            </div>
            <div class="panel panel-danger">
              <div class="panel-heading">
                <h3 class="panel-title">百万大奖</h3>
              </div>
              <div class="panel-body">
                赶紧拿起电话领走吧:110
              </div>
            </div>

            <!-- Large modal -->

            {#修改密码模态框代码#}
            <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">
                            <div class="col-md-8 col-md-offset-2">
                                <h2 class="text-center">修改密码</h2>
                                <form>
                                    {% csrf_token %}
                                    <div class="form-group">
                                        <label for="id_username">用户名</label>
                                        <input type="text" name="username" value="{{ request.user.username }}" disabled id="id_username" class="form-control">
                                    </div>
                                    <div class="form-group">
                                        <label for="id_old_password">旧密码</label>
                                        <input type="password" name="old_password" id="id_old_password" class="form-control">
                                    </div>
                                    <div class="form-group">
                                        <label for="id_new_password">新密码</label>
                                        <input type="password" name="new_password" id="id_new_password" class="form-control">
                                    </div>
                                    <div class="form-group">
                                        <label for="id_confirm_password">确认密码</label>
                                        <input type="password" name="confirm_password" id="id_confirm_password" class="form-control">
                                    </div>
                                    <input type="button" class="btn btn-primary" id="id_submit" value='确认'>
                                    <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                                    <span class="errors" style="color: red"></span>
                                    <hr>
                    </form>
                            </div>
                        </div>
                    </div>
              </div>
            </div>
            {#修改密码模态框代码#}
        </div>
    </div>
</div>
<script>
   $('#id_submit').click(function () {
        $.ajax({
            url:"/set_password/",
            type:'post',
            data:{
                'old_password':$('#id_old_password').val(),
                'new_password':$('#id_new_password').val(),
                'confirm_password':$('#id_confirm_password').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}',
            },
            success:function (data) {
                if (data.code == 100){
                    location.href = data.url
                }else{
                    $('.errors').text(data.msg)
                }

            }


        })
    })
</script>
</body>
</html>
home.html
<div class="panel panel-primary">
              <div class="panel-heading">
                <h3 class="panel-title">文章分类</h3>
              </div>
              <div class="panel-body">
                {% for category in category_menu %}
                    <p><a href="/{{ username }}/category/{{ category.2 }}/">{{ category.0 }}({{ category.1 }})</a></p>
                {% endfor %}

              </div>
            </div>
            <div class="panel panel-danger">
              <div class="panel-heading">
                <h3 class="panel-title">文章标签</h3>
              </div>
              <div class="panel-body">
                  {% for tag in tag_menu %}
                    <p><a href="/{{ username }}/tag/{{ tag.2 }}/">{{ tag.0 }}({{ tag.1 }})</a></p>
                  {% endfor %}

              </div>
            </div>
            <div class="panel panel-warning">
              <div class="panel-heading">
                <h3 class="panel-title">日期归档</h3>
              </div>
              <div class="panel-body">
                  {% for date in date_menu %}
                        <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y年m月' }}({{ date.1 }})</a></p>
                  {% endfor %}

              </div>
            </div>
left_menu.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>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>
<div class="container">
    <div class="col-md-8 col-md-offset-2">
        <h2 class="text-center">登录</h2>
        <div class="form-group">
            <label for="id_username">用户名</label>
            <input type="text" name="username" class="form-control" id="id_username">
        </div>
        <div class="form-group">
            <label for="id_password">密码</label>
            <input type="password" name="password" class="form-control" id="id_password">
        </div>
        <div class="form-group">
            <label for="id_code">验证码</label>
            <div class="col-md-6">
                <input type="text" id="id_code" name="code" class="form-control">
            </div>
            <div class="col-md-6">
                <img src="/get_code/" alt="" height="35" width="360" id="id_img">
            </div>
            <button class="btn btn-success" id="id_submit">登录</button>
    <span class="errors" style="color:red"></span>
        </div>
    </div>
</div>
<script>
    $('#id_img').click(function () {
        // 获取src属性 在此基础之上 修改一下即可
        var oldSrc = $(this).attr('src');
        $(this).attr('src',oldSrc + '?')
    });
    $('#id_submit').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{
                'username':$('#id_username').val(),
                'password':$('#id_password').val(),
                'code':$('#id_code').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}'
            },
            success:function (data) {
                if(data.code==100){
                    location.href = data.url
                }else{
                    $('.errors').text(data.msg)
                }
            }
        })
    })
</script>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>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>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h2 class="text-center">注册</h2>
            <form action="" id="myform" novalidate>
                {% csrf_token %}
                {% for foo in form_obj %}
                    <div class="form-group">
                        {# 获取foo渲染的input框的id值#}
                        {#{{ foo.auto_id }}#}
                        <label for="{{ foo.auto_id }}">{{ foo.label }}</label>
                        {{ foo }}
                        <span class="errors pull-right" style="color: red"></span>
                    </div>
                {% endfor %}
                <div class="form-group">
                    <label for="myfile">头像
                        <img src="/static/img/default.jpg" alt="" height="80" style="margin-left: 20px" id="img">
                    </label>
                    <input type="file" name="avatar" id="myfile" style="display: none">
                </div>
                <input type="button" class="btn btn-primary pull-right" value="注册" id="id_submit">
            </form>
        </div>
    </div>
</div>
<script>
    $('#myfile').change(function () {
        // 获取用户上传的头像 然后替换到img标签中
        // 1 获取用户上传的文件对象
        var fileObj = $(this)[0].files[0];
        // 2.利用内置对象FileReader
        var fileReader = new FileReader();//文件阅读器
        // 3.将文件对象交由文件阅读器读取 文件内容
        fileReader.readAsDataURL(fileObj); // IO操作速度较慢
        // 4.找到img标签 修改src属性
        // 等待文件阅读器完全读取完文件数据之后 才做下面的操作 onload
        fileReader.onload = function () {
            $('#img').attr('src',fileReader.result)
        }

    });

    // 绑定点击事件
    $('#id_submit').click(function () {
        // 1. 产生内置对象formdata
        var formData = new FormData();
        // 2. 循环添加普通键值对
        {#console.log($('#myform').serializeArray());#}
        $.each($('#myform').serializeArray(), function (index,obj) {
            formData.append(obj.name,obj.value)
        });
        // 3. 手动添加文件
        formData.append('myfile',$('#myfile')[0].files[0]);
        // 4. 发送ajax请求
        $.ajax({
            url:'',
            type:'post',
            data:formData,

            // 传文件需要指定两个参数
            contentType:false,
            processData:false,

            success:function (data) {
                if (data.code==100){
                    location.href = data.url
                }else{
                    // 如果没有成功 说明用户输入的数据不合法 你需要展示错误信息
                    {#console.log(data.msg);#}
                    // 能够将对应的错误信息准确无误的渲染到对应的input下面的span标签中
                    // 手动拼接input的id值
                    $.each(data.msg,function (index,obj) {
                     {#console.log(index,obj)  #}
                        var targetId = '#id_'+index;
                        $(targetId).next().text(obj[0]).parent().addClass('has-error')

                    })
                }
            }

        })
    });
    $('input').focus(function () {
        $(this).next().text('').parent().removeClass('has-error')
    })
</script>
</body>
</html>
register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>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>
<div class="container">
    <div class="col-md-8 col-md-offset-2">
        <h2 class="text-center">修改密码</h2>
        <div class="form-group">
            <label for="id_username">用户名</label>
            <input type="text" name="username" class="form-control" id="id_username" value="{{ request.user.username }}" disabled>
        </div>
        <div class="form-group">
            <label for="id_password">旧密码</label>
            <input type="password" name="old_password" class="form-control" id="id_password">
        </div>
        <div class="form-group">
            <label for="id_new_password">新密码</label>
            <input type="password" name="new_password" class="form-control" id="id__new_password">
        </div>
        <div class="form-group">
            <label for="id_code">验证码</label>
            <div class="col-md-6">
                <input type="text" id="id_code" name="code" class="form-control">
            </div>
            <div class="col-md-6">
                <img src="/get_code/" alt="" height="35" width="360" id="id_img">
            </div>
            <button class="btn btn-success" id="id_submit">修改</button>
            <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
    <span class="errors" style="color:red"></span>
        </div>
    </div>
</div>
<script>
   $('#id_img').click(function () {
        // 获取src属性 在此基础之上 修改一下即可
        var oldSrc = $(this).attr('src');
        $(this).attr('src',oldSrc + '?')
    });
    $('#id_submit').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{
                'username':$('#id_username').val(),
                'old_password':$('#id_password').val(),
                'new_password':$('#id__new_password').val(),
                'code':$('#id_code').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}'
            },
            success:function (data) {
                if(data.code==100){
                    location.href = data.url
                }else{
                    $('.errors').text(data.msg)
                }
            }
        })
    })
</script>
</body>
</html>
set_password.html
{% extends 'base.html' %}

{% block content %}
<ul class="media-list">
    {% for article in page_queryset %}
        <li class="media">
                     <h4 class="media-heading"><a href="/{{ username }}/article/{{ article.pk }}">{{ article.title }}</a></h4>
                    <div class="media-left">
                      <a href="#">
                        <img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" alt="..." height="60">
                      </a>
                    </div>
                    <div class="media-body">
                      {{ article.desc }}
                    </div>
                  {#  posted @ 2018-05-23 20:28 武沛齐 阅读 (32237) 评论 (56) 编辑#}
                         <br>
                     <div class="pull-right">
                         <span>posted&nbsp;&nbsp;</span>
                         <span>{{ article.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
                         <span>{{ article.blog.userinfo.username }}&nbsp;&nbsp;</span>
                         <span><span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_num }})&nbsp;&nbsp;</span>
                         <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_num }})</span>
                         <span><a href="#">编辑</a></span>
                     </div>
                  </li>
                    <hr>
    {% endfor %}


</ul>
{% endblock %}
site.html

 

media  
-article_img     WIN_20190531_19_19_23_Pro.jpg
-avatar   default.jpg 
-css

a {
    color: pink;
}
---egon.css
a {
    color: red;
}
---jason.css
a {
    color: green;
}
---tank.css

static
-bootstrap-3.3.7-dist
-dist
-font
-img default.jpg
kindeditor


KindEditor是一套开源的HTML可视化编辑器,主要用于让用户在网站上获得所见即所得编辑效果,兼容IE、Firefox、Chrome、Safari、Opera等主流浏览器。

"""
Django settings for BBS project.

Generated by 'django-admin startproject' using Django 1.11.11.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'a$kx2wil58#!f*dknib@ove%ukv!n-36w8$4tz++6p*ng68c!4'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'BBS.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'BBS.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bbs_1',
        'USER':'root',
        'PASSWORD':'llx20190411',
        'HOST':'127.0.0.1',
        'PORT':3306
    }
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static')
]

AUTH_USER_MODEL = 'app01.UserInfo'

LOGIN_URL = '/login/'

# 规定 用户上传的所有的静态文件 全部放到media文件夹下
# MEDIA_ROOT = os.path.join(BASE_DIR,'media')
MEDIA_ROOT = os.path.join(BASE_DIR,'media')


# 暴露任意文件夹资源
MEDIA_ROOT1 = os.path.join(BASE_DIR,'app01')
settings.py
import pymysql
pymysql.install_as_MySQLdb()
__init__.py
from django.db import models

# Create your models here.
"""
用户表(利用auth_user那张表 自己额外再扩展几个字段)
        phone
        avatar 放图片的
        create_time
        blog  一对一个人站点表
"""
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    phone = models.BigIntegerField(null=True)
    # avatar存的是用户头像文件路径,用户上传的头像会自动保存到avatar文件夹下
    avatar = models.FileField(upload_to='avatar/',default='avatar/default.jpg')
    create_time = models.DateField(auto_now_add=True) # 记录创建时间

    blog = models.OneToOneField(to='Blog',null=True)

    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '用户表' # admin显示 用户表

    def __str__(self):
        return self.username


"""
blog  一对一个人站点表
         
    个人站点表
        site_name 
        site_title
        site_theme 网站主题
"""
class Blog(models.Model):
    site_name = models.CharField(max_length=32)
    site_title = models.CharField(max_length=32)
    site_theme = models.CharField(max_length=255)

    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '个人站点表' # admin显示 用户表
    def __str__(self):
        return self.site_name
"""
标签表
        name
        
        blog   一对多个人站点
"""
class Category(models.Model):
    name = models.CharField(max_length=32)
    blog = models.ForeignKey(to='Blog')

    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '标签表' # admin显示 用户表
    def __str__(self):
        return self.name
"""
分类表
        name
        
        blog   一对多个人站点
"""
class Tag(models.Model):
    name = models.CharField(max_length=32)
    blog = models.ForeignKey(to='Blog')

    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '分类表' # admin显示 用户表
    def __str__(self):
        return self.name
"""
文章表
        title
        desc
        content
        create_time
        
        blog        一对多个人站点
        tag            多对多标签
        category    一对多分类
        
        # 数据库设计优化(******)
        comment_num   普通字段
        up_num         普通字段
        down_num     普通字段
"""
class Article(models.Model):
    title = models.CharField(max_length=255)
    desc = models.CharField(max_length=255)
    content = models.TextField()  # 存大段文本
    create_time = models.DateField(auto_now_add=True)

    """数据优化"""
    # content 需要进行数据优化
    comment_num = models.IntegerField(default=0)
    up_num = models.IntegerField(default=0)
    down_num = models.IntegerField(default=0)


    """外键字段"""
    blog = models.ForeignKey(to='Blog',null=True)
    category = models.ForeignKey(to='Category',null=True) # 标签
    tag = models.ManyToManyField(to='Tag',through='Article2Tag',through_fields=('article','tag')) # 分类

    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '文章表' # admin显示 用户表
    def __str__(self):
        return self.title
# 文章和分类的半自动表
class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')

    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '文章与分类关联表' # admin显示 用户表

"""
   点赞点踩表
    
        user            一对多用户表
        article         一对多文章表   
        is_up            0/1
        
        
        user            article      is_up
        1                1                1
        1                2                1
        1                3                1
        2                1                1
        
"""
class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    is_up = models.BooleanField()  # 传布尔值 存0/1

    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '点赞点踩表' # admin显示 用户表

"""

   评论表
        user            一对多用户表        
        article            一对多文章表  
        comment                
        create_time        
        parent           一对多评论表(自关联)    父评论的id  如果有值说明你是子评论  如果没有值说明你是父评论

"""
class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    content = models.CharField(max_length=255)
    create_time = models.DateField(auto_now_add=True)
    parent = models.ForeignKey(to='self',null=True)

    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '评论表' # admin显示 用户表
models.py
from django.contrib import admin
from app01 import models
# Register your models here.
admin.site.register(models.UserInfo)
"""
models.py 创造admin 展示 中文转义
    class Meta:
        # verbose_name = '用户表' # admin显示 用户表s
        verbose_name_plural = '用户表' # admin显示 用户表
        
    # 表关联名称的打印,方便admin使用
    def __str__(self):
        return self.title
"""
admin.site.register(models.Blog)
admin.site.register(models.Tag)
admin.site.register(models.Category)
admin.site.register(models.Article)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
admin.py
from app01 import models

from django.db.models import Count
from django.db.models.functions import TruncMonth

"""自定义过滤器"""
"""
在你的模块文件中,你必须首先创建一个全局register变量,它是用来注册你自定义标签和过滤器的,
你需要在你的python文件的开始处,插入几下代码
"""
from django import template
register = template.Library()


@register.inclusion_tag('left_menu.html')
def left_menu(username):
    # 先获取用户用户名 查看是否存在
    user_obj = models.UserInfo.objects.filter(username=username).first()
    # 获取该用户的个人站点
    blog = user_obj.blog
    # 查询当前用户每一个分类下的文章数
    category_menu = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c',
                                                                                                       'pk')
    # print(category_menu)
    # 查询每一个分类下的文章数
    # res = models.Category.objects.annotate(c=Count('article')).values('name','c')
    # print(res)
    # 查询当前用户每一个标签下的文章数
    tag_menu = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c', 'pk')
    # print(tag_menu)
    date_menu = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
        'month').annotate(c=Count('pk')).values_list('month', 'c')
    # print(date_menu)
    return locals()
templatetags/mytag.py-app01

# templatetags/mytag.py 自定义过滤器

app01/myforms.py
from django import forms
from django.forms import widgets
from app01 import models
class MyRegForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label='用户名',
        error_messages={
            'max_length': '用户名最大八位',
            'min_length': '用户名最小三位',
            'required': '用户名不能为空'
        },
        widget=widgets.TextInput(attrs={
            'class':'form-control'
        })
    )
    password = forms.CharField(
        max_length=8,
        min_length=3,
        label='密码',
        error_messages={
            'max_length': '密码最大八位',
            'min_length': '密码最小三位',
            'required': '密码不能为空'
        },
        widget=widgets.PasswordInput(attrs={
            'class':'form-control'
        })
    )
    confirm_password = forms.CharField(
        max_length=8,
        min_length=3,
        label='确认密码',
        error_messages={
            'max_length': '确认密码最大八位',
            'min_length': '确认密码最小三位',
            'required': '确认密码不能为空'
        },
        widget=widgets.PasswordInput(attrs={
            'class': 'form-control'
        })
    )
    email = forms.EmailField(
        label='邮箱',
        error_messages={
            'required':'邮箱不能为空',
            'invalid':'邮箱格式错误'
        },
        widget=widgets.EmailInput(attrs={
            'class':'form-control'
        })
    )
    # 局部钩子 校验用户名是否已存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        is_user = models.UserInfo.objects.filter(username=username)
        if is_user:
            self.add_error('username','用户名已存在')
        return username

    # 全局钩子 校验密码是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password','两次密码不一致')
        return self.cleaned_data

 

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)
app01/utils/mypage.py(分页工具)

 models: 数据库优化

class Article(models.Model):
title = models.CharField(max_length=255)
desc = models.CharField(max_length=255)
content = models.TextField() # 存大段文本
create_time = models.DateField(auto_now_add=True)

# 数据库优化
comment_num = models.IntegerField(default=0)
up_num = models.IntegerField(default=0)
down_num = models.IntegerField(default=0)

"""外键字段"""
blog = models.ForeignKey(to='Blog', null=True)
category = models.ForeignKey(to='Category', null=True) # 标签
tag = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag')) # 分类

class Meta:
# verbose_name = '用户表' # admin显示 用户表s
verbose_name_plural = '文章表' # admin显示 用户表

def __str__(self):
return self.title

同步

python3 manage.py makemigrations

python3 manage.py migrate

"""BBS URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.conf.urls import url, include
    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.contrib import admin
from app01 import views
from django.views.static import serve
from BBS import settings
urlpatterns = [
    url(r'^admin/', admin.site.urls),

    # # 路由分发的本质
    # url(r'^index1/',([
    #     url(r'^index_1/',([],None,None)),
    #     url(r'^index_2/',([],None,None)),
    #     url(r'^index_3/',([],None,None)),
    #                  ],None,None)),

    url(r'^register',views.register),
    url(r'^login/',views.login),

    # 图片验证码相关路由
    url(r'^get_code/',views.get_code),
    url(r'^set_password/', views.set_password),
    url(r'^logout/', views.logout),
    
    # 主页
    url(r'^home/',views.home),
    # url(r'^session/',views.session_code),

    # 手动暴露后端文件夹资源
    url(r'^media/(?P<path>.*)',serve,{"document_root":settings.MEDIA_ROOT}),
    # 手动暴露后端文件资源的时候 一定要慎重
    url(r'^app01/(?P<path>.*)',serve,{"document_root":settings.MEDIA_ROOT1}),


    # 文章点赞,点踩功能
    url(r'^up_or_down/',views.up_or_down),
    # 文章评论功能
    url(r'^comment/',views.comment),


    # 后台管理
    url(r'^backend/',views.backend),
    # 后台添加文章
    url(r'^add_article/',views.add_article),
    # 文本编辑器上传功能
    url(r'^upload_img/',views.upload_img),

    # 修改用户头像
    url(r'edit_avatar/',views.edit_avatar),


    # 个人站点
    url(r'^(?P<username>\w+)/$',views.site),
    # 侧边栏筛选功能
    # url(r'^(?P<username>\w+)/category/(?P<param>\d+)/',views.site),
    # url(r'^(?P<username>\w+)/tag/(?P<param>\d+)/',views.site),
    # url(r'^(?P<username>\w+)/archive/(?P<param>.*)/',views.site),  # 2018-2s.site),
    # 合成一条的url

    url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/',views.site),  # \w+ 匹配字符

    # 文章详情页
    url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/',views.article_detail)
]
urls.py
from django.shortcuts import render,HttpResponse,reverse,redirect
from app01 import myforms,models
from django.http import JsonResponse
from django.contrib import auth
# Create your views here.
"""
注册功能
    1.forms组件
        用户名
        密码
        确认密码
        邮箱
    2.利用forms组件渲染前端页面
        不借助于form表单提交 利用ajax提交

    3.手动搭建获取用户头像的input框
        1.点击图片弹出选择框 利用label标签与input有绑定的关系的特性
        2.再利用文件阅读器 实现图片的实时展示

    4.后端创建用户的时候 你可以利用**{}的形式 帮你节省代码
        1.在获取用户头像的时候 你一定要做一步判断

    5.数据校验不通过  你要将不过的信息 展示到对应的input框下
        1.forms组件渲染的input的框的id值有固定的格式 id_字段名
        2.前端for循环字段 手动拼接处id值
        3.给span添加报错信息  给所在的div加has-error

    6.用户再次点击input,将报错信息个has-error移除

"""
def register(request):
    form_obj = myforms.MyRegForm()
    if request.method == 'POST':
        back_dic = {'code':100,'msg':''}
        # 校验用户信息是否合法
        form_obj = myforms.MyRegForm(request.POST)
        if form_obj.is_valid():
            clean_data = form_obj.cleaned_data # clean_data = {'username':'','password':'','confirm_password':'','email':''}
            clean_data.pop('confirm_password') # clean_data = {'username':'','password':'','email':''}
            # 手动获取用户头像
            user_file = request.FILES.get('myfile')
            if user_file: # 一定要判断用户是否传头像了 如果传了才往字典里面添加  没传不用添加 因为有默认
                clean_data['avatar'] = user_file
            # 创建数据
            models.UserInfo.objects.create_user(**clean_data)
            back_dic['msg'] = '注册成功'
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 101
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request,'register.html',locals())


"""
登陆功能
    图片相关功能的模块
        pip3 install pillow
    1.图片验证码
        1.必会的  一定要自己能够写出产生随机验证码的代码
        2.img标签的src属性 可以写具体的图片路径,还可以写url,接收二进制数据直接再转换成图片展示出来
        3.借助于pillow模块生成图片验证码必备的对象
        4.后端校验图片验证码是否正确  然后再判断用户输入的用户名和密码是否正确
"""
def login(request):
    if request.method == 'POST':
        back_dic = {'code':100,'msg':''}
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        # 1.先校验用户输入的验证码是否正确  忽略大小写校验 内部统一转大写或者小写比较
        if request.session.get('code').upper() == code.upper():
            # 2.数据库校验用户名和密码是否正确
            user_obj = auth.authenticate(username=username,password=password)
            if user_obj:
                # 3.保存用户登录状态
                auth.login(request,user_obj)
                back_dic['msg'] = '登录成功'
                back_dic['url'] = '/home/'
            else:
                back_dic['code'] = 101
                back_dic['msg'] = '用户名或密码错误'
        else:
            back_dic['code'] = 102
            back_dic['msg'] = '验证码错误'
        return JsonResponse(back_dic)
    return render(request,'login.html')



from PIL import Image,ImageDraw,ImageFont
"""
Image  生成图片的
ImageDraw  在图片上写东西的
ImageFont  控制字体样式的
"""
import random
from io import BytesIO,StringIO
"""
io是一个内存管理器模块
BytesIO  能够帮你存储数据 二级制格式
StringIO 能够帮你存储数据 字符串格式
"""

def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)

# 验证码相关
def get_code(request):
    # 推导步骤1:直接将本地已经存图片的以二进制方式读取发送
    # with open(r'D:\面试整理\重新开始\13.BBS(60-63)\BBS\avatar\222.jpg','rb')as f:
    #     data = f.read()
    # return HttpResponse(data)
    # 推导步骤2:能够产生任何多张的图片的方法
    # img_obj = Image.new('RGB',(35,360),'green')
    # img_obj = Image.new('RGB',(35,360),get_random())
    # # 先以文件的形式保存下来
    # with open('xxx','wb')as f:
    #     img_obj.save(f,'png')
    # # 然后再打开这个文件发送
    # with open('xxx', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)
    # 推导步骤3:需要找一个能够临时存储文件的地方  避免频繁文件读写操作
    # img_obj = Image.new('RGB', (35, 360), get_random())
    # io_obj = BytesIO()# 实例化产生一个内存管理对象  你可以把它当成文件句柄
    # img_obj.save(io_obj,'png')
    # return HttpResponse(io_obj.getvalue()) # 从内存对象中获取二级制的图片数据
    # 推导步骤4:在产生的图片上 写验证码
    img_obj = Image.new('RGB', (360, 35), get_random())
    img_draw = ImageDraw.Draw(img_obj) # 生成一个可以在图片上写字的画笔对象
    img_font = ImageFont.truetype('static/font/222.ttf',30) # 字体样式 及 大小
    io_obj = BytesIO()
    # # 图片验证码 一般都是有 数字  大写字母  小写字母
    # # 五位  每一位都可以 数字或大写字母或小写字母
    code = ''
    for i in range(5):
        upper_str = chr(random.randint(65,90))
        low_str = chr(random.randint(97,122))
        random_int = str(random.randint(0,9))
        # 随机取一个
        temp_code = random.choice([upper_str,low_str,random_int])
        # 朝图片上写
        img_draw.text((70+i*45,0),temp_code,get_random(),font=img_font)
        code += temp_code
    print(code)
    # # 将产生的随机验证码 存入session中 以便后续其他视图函数获取 校验验证码
    request.session['code'] = code
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())
"""
首页搭建
    自己完成
        注销和修改密码的功能
"""
from app01.utils.mypage import Pagination
def home(request):
    # 获取网站所有的文章,展示到前端
    article_list = models.Article.objects.all()
    # 如果文章数目比较多,你应该做分页处理
    current_page = request.GET.get("page", 1)
    all_count = article_list.count()
    page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=4)
    page_queryset = article_list[page_obj.start:page_obj.end]
    return render(request,'home.html',locals())

from django.contrib.auth.decorators import login_required
import time
@login_required# 路径写在settings
def logout(request):
    # request.session.flush()
    # 注销确认
    auth.logout(request)
    return render(request, 'home.html')

@login_required
def set_password(request):
    # if request.method == 'POST':
    #     back_dic = {'code': 100, 'msg': ''}
    #     old_password = request.POST.get('old_password')
    #     new_password = request.POST.get('new_password')
    #     code = request.POST.get('code')
    #     # 1.先校验用户输入的验证码是否正确  忽略大小写校验 内部统一转大写或者小写比较
    #     if request.session.get('code').upper() == code.upper():
    #         # 先判断原密码是否正确
    #         is_right = request.user.check_password(old_password) # 将获取的用户密码 自动加密 然后去数据库中对比当前用户的密码是否一致
    #         if is_right:
    #             print(is_right)
    #             # 修改密码
    #             request.user.set_password(new_password)
    #             request.user.save() # 修改密码的时候 一定要save保存 否则无法生效
    #             back_dic['msg'] = '修改成功'
    #             back_dic['url'] = '/home/'
    #         else:
    #             back_dic['code'] = 101
    #             back_dic['msg'] = '旧密码输入错误'
    #     else:
    #         back_dic['code'] = 102
    #         back_dic['msg'] = '验证码错误'
    #     return JsonResponse(back_dic)
    # return render(request, 'set_password.html')
    if request.is_ajax():
        back_dic = {'code': 100, 'msg': ''}
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        # 先判断原密码是否正确
        is_right = request.user.check_password(old_password) # 将获取的用户密码 自动加密 然后去数据库中对比当前用户的密码是否一致
        if is_right:
            if new_password == confirm_password:
                # 修改密码
                request.user.set_password(new_password)
                request.user.save() # 修改密码的时候 一定要save保存 否则无法生效
                back_dic['msg'] = '修改成功'
                back_dic['url'] = '/login/'
            else:
                back_dic['code'] = 102
                back_dic['msg'] = '两次密码不一致'
        else:
            back_dic['code'] = 101
            back_dic['msg'] = '旧密码输入错误'
        return JsonResponse(back_dic)
# def session_code(request):
#     res = request.session.get('code')
#     return HttpResponse(res)

from django.db.models import Count
from django.db.models.functions import TruncMonth
# 个人站点页
def site(request,username,**kwargs):
    # 先获取用户用户名 查看是否存在
    user_obj = models.UserInfo.objects.filter(username=username).first()
    if not user_obj:
        # 如果用户不存在 应该返回一个404页面
        return render(request,'errors.html')
    # 获取该用户的个人站点
    blog = user_obj.blog
    # 查询当前这个人的所有文章
    article_list = models.Article.objects.filter(blog=blog)
    """侧边栏筛选功能:就是对当前用户所有的文章 再进行一次筛选"""
    if kwargs:
        condition = kwargs.get('condition')  # tag/category/archive
        param = kwargs.get('param')  # 1/1/2019-1
        if condition == 'category':
            article_list = article_list.filter(category_id=param)
            print(article_list)
        elif condition == 'tag':
            article_list = article_list.filter(tag__id=param)
            print(article_list)
        else:
            year, month = param.split('-')
            article_list = article_list.filter(create_time__year=year, create_time__month=month)
    # 查询当前用户每一个分类下的文章数
    # category_menu = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c','pk')
    # # print(category_menu)
    # # 查询每一个分类下的文章数 annotate 分组
    # res = models.Category.objects.annotate(c=Count('article')).values('name','c')
    # # print(res)
    # # 查询当前用户每一个标签下的文章数
    # tag_menu = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name','c','pk')
    # # print(tag_menu)
    # date_menu = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month','c')
    # # print(date_menu)
    current_page = request.GET.get("page", 1)
    all_count = article_list.count()
    page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=4)
    page_queryset = article_list[page_obj.start:page_obj.end]
    return render(request,'site.html',locals())
# 文章页
def article_detail(request,username,article_id):
    # 先获取用户用户名 查看是否存在
    user_obj = models.UserInfo.objects.filter(username=username).first()
    if not user_obj:
        # 如果用户不存在 应该返回一个404页面
        return render(request, 'errors.html')
    blog = user_obj.blog
    # 根据文章id 查询出对应的文章 展示到前端 即可
    article_obj = models.Article.objects.filter(pk=article_id).first()
    comment_list = models.Comment.objects.filter(article=article_obj)
    return render(request,'article_detail.html',locals())

import json
from django.db.models import Count,F
from django.utils.safestring import mark_safe
# 仅仅只是处理ajax请求点赞点踩逻辑
# 点赞,点踩功能
def up_or_down(request):
    if request.is_ajax():
        back_dic = {'code':100,'msg':''}
        # 1 先校验用户是否登录
        if request.user.is_authenticated():
            # 获取必要的数据
            article_id = request.POST.get('article_id')
            is_up = request.POST.get('is_up')
            # 将字符串形式的js布尔值转换成后端python布尔值
            is_up = json.loads(is_up)
            # 2 校验当前文章是否是当前用户自己写的
            article_obj = models.Article.objects.filter(pk=article_id).first()
            if not article_obj.blog.userinfo == request.user:
                # 3 校验当前用户是否已经给当前文章点过赞或踩
                is_click = models.UpAndDown.objects.filter(article_id=article_id,user=request.user)
                if not is_click:
                    # # 4 操作数据库 记录数据  在记录的时候 需要做到文章表里的普通字段跟点赞点踩数据同步  并且区分是点赞还是点踩
                    if is_up:
                        # 如果是赞 先把文章表里面的普通点赞字段加1
                        """F查询的本质就是从数据库中获取某个字段的值"""
                        models.Article.objects.filter(pk=article_id).update(up_num = F('up_num') + 1)
                        back_dic['msg'] = '点赞成功'
                    else:
                        # 如果是踩 先把文章表里面的普通点踩字段加1
                        models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
                        back_dic['msg'] = '点踩成功'
                    # 操作点赞点踩表  存实际数据
                    models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)
                else:
                    back_dic['code'] = 101
                    back_dic['msg'] = '你已经点过了'
            else:
                back_dic['code'] = 102
                back_dic['msg'] = '你个臭不要脸的,不能点自己的!'
        else:
            back_dic['code'] = 103
            back_dic['msg'] = mark_safe('请先<a href="/login/>登录</a>"')
        return JsonResponse(back_dic)


"""
事务
"""

from django.db import transaction
# 文章评论功能
def comment(request):
    if request.is_ajax():
        back_dic = {'code': 100, 'msg': ''}
        # 前端虽然更具用户是否登录展示评论框 但是后端最好再校验一次用户是否登录
        if request.user.is_authenticated():
            content = request.POST.get('content')
            article_id = request.POST.get('article_id')
            parentId = request.POST.get('parentId')
            # 评论表  文章评论数普通字段 同步
            with transaction.atomic():
                models.Comment.objects.create(user=request.user,article_id=article_id,content=content,parent_id=parentId)
                models.Article.objects.filter(pk=article_id).update(comment_num = F('comment_num') + 1)
            back_dic['msg'] = '评论成功'
        else:
            back_dic['code'] = 101
            back_dic['msg'] = mark_safe('请先<a href="/login/">登录</a>')
        return JsonResponse(back_dic)


@login_required
def backend(request):
    article_list = models.Article.objects.filter(blog=request.user.blog)
    current_page = request.GET.get("page", 1)
    all_count = article_list.count()
    page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=4)
    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):
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')
        tags = request.POST.getlist('tag')
        category_id = request.POST.get('category')
        # 麻瓜式做法  直接对content窃取150
        #  1 先生成一个BeautifulSoup对象
        soup = BeautifulSoup(content,'html.parser')
        for tag in soup.find_all():
            title = request.POST.get('title')
            content = request.POST.get('content')
            tags = request.POST.getlist('tag')
            category_id = request.POST.get('category')
            # 麻瓜式做法  直接对content窃取150
            #  1 先生成一个BeautifulSoup对象
            soup = BeautifulSoup(content, 'html.parser')
            for tag in soup.find_all():
                # 针对script标签 应该直接删除
                # print(tag.name)  # 获取当前html页面所有的标签
                if tag.name == 'script':
                    tag.decompose() # 将符合条件的标签删除
            # 文章简介应该是150个文本内容
            desc = soup.text[0:150]
            # desc = content[0:150]
            article_obj = models.Article.objects.create(title=title,desc=desc,content=str(soup),category_id=category_id,blog=request.user.blog)
            # 一个个的添加
            b_list = []
            for tag_id in tags:
                b_list.append(models.Article2Tag(article=article_obj,tag_id=tag_id))
            models.Article2Tag.objects.bulk_create(b_list) # bulk_create 多条数据快速存
            return redirect('/backend/')

    tag_list = models.Tag.objects.filter(blog=request.user.blog)
    category_list = models.Category.objects.filter(blog=request.user.blog)
    return render(request,'backend/add_article.html',locals())


import os
from BBS import settings

@login_required
def upload_img(request):
    # 接收用户写文章传的所有的图片资源
    if request.method == 'POST':
        file_obj = request.FILES.get('imgFile')
        # 要将文件存入media文件下一个专门用来存储文章图片的文件夹(article_img)
        base_path = os.path.join(settings.BASE_DIR,'media','article_img')
        if not os.path.exists(base_path):
            os.mkdir(base_path)
        # 2 手动拼接文件的具体路径
        file_path = os.path.join(base_path,file_obj.name)
        # 3,文件操作
        with open(file_path,'wb') as f:
            for line in file_obj:
                f.write(line)
        """
        //成功时
            {
                    "error" : 0,
                    "url" : "http://www.example.com/path/to/file.ext"
            }
            //失败时
            {
                    "error" : 1,
                    "message" : "错误信息"
            }
        """

        back_dic = {
            'error':0,
            'url':'/media/article_img/%s'%file_obj.name
        }
        return JsonResponse(back_dic)
@login_required
def edit_avatar(request):
    if request.method == 'POST':
        file_obj = request.FILES.get('myfile')
        if file_obj:
            models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=file_obj)
            # queryset更新方法修改头像的时候 不会自动加avatar前缀
            # 你可以手动通过文件操作 写入文件  然后给avatar传一个手动拼接好的路径

            request.user.avatar = file_obj
            request.user.save()
        return redirect('/%s/'%request.user.username)
    # return render(request,'edit_avatar1.html')
    return render(request, 'edit_avatar.html')
views.py

 

posted on 2022-04-18 16:12  没有如果,只看将来  阅读(45)  评论(0编辑  收藏  举报