精华内容
下载资源
问答
  • Python全栈视频教程

    万人学习 2017-07-17 20:42:19
    Python全栈视频培训教程,帮助开发者从零到一入门python:1.深入理解python语法机制与底层原理,2.深入实战python各种案例 , 3.语音识别,图形界面,多线程爬虫,语音合成,游戏控制等等精彩案例。
  • 收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!Python Web全栈开发入门实战教程教程,真正零基础的教程,从零带实现一个Python Web的项目。

    Python Web全栈开发入门实战教程教程

       大家好,我叫亓官劼(qí guān jié ),这个《Python Web全栈开发入门实战教程教程》是一个零基础的实战教程,手把手带你开发一套系统,带你了解Python web全栈开发。写这篇文章的初衷就是想给想入门Python Web开发还没入门的小伙伴们一个详细的,真正零基础的一个开发教程,可以让小伙伴们在入门的途中少走很多弯路。
       在服务端端开发上,主流还是Java开发,Python的话虽说开发快速、便捷,但是在一些地方还是有缺陷的。但是Python Web有一个非常好的优点就是方便、快捷、易学、易用,可以让小伙伴们快速的入门Web开发。
      之前有不少小伙伴来问我,怎么开发Web系统啊?毕业论文的中期检查马上就要来了,我的毕业设计还一动没动,我完全不会Web系统的开发,我该怎么办?,虽说大学四年我们或多或少的都学过各种各样的语音,但是项目开发做的却很少,很多学校开了点项目开发的也,最后也都是上了点理论,开发?那是不可能开发的,最后还是一套试卷定成绩,期末背背就是高分,导致很多小伙伴们到了写毕设的时候,连个简简单单的web系统都不会开发。这篇文章也可以给小伙伴们一个参考吧,如果毕设的时候开发web系统的时候无从下手,可以来尝试一下Python Web,这篇文章将会真正零基础的,每步都进行一个详细的说明,带你完成一个小项目,实现各种各样的一个功能,给你提供一些思路。
      写这篇文章的另一个初衷就是,想给想入门Python Web开发的同学或者想学习Web开发的同学,提供一个练手的项目,一套完整的项目实现过程,避免刚入门时的茫然无措。我当时大二刚刚开始做Web开发时,由于学校里面做开发的比较少吧,自己一个人摸索,找网上的各种教程,在教程里他们总是会忽略一些细节,然后我实践运行的时候就是频频报错,一个小小的BUG就要折腾很久,还记得当时项目完成,部署到CentOS服务器上的时候,使用的Nginx+UWSGI进行部署,当时直接跑UWSGI可以跑,直接跑Nginx也可以,两个加起来就是一直错误,各种百度、谷歌,也找不到问题所在,最后折腾了半个月,在一篇很小的文章上看到了UWSGI的一个接口文件(安装的时候不是自动生成的,需要自己创建),然后一个很小的bug吧,折腾了半个月。所以这篇文章,也是想给刚入门的小白提供一个参考,一个少走弯路的方法吧!
    大家一起加油,时光荏苒,未来可期!


      大家好,我是亓官劼(qí guān jié ),在【亓官劼】公众号、CSDN、GitHub、B站、华为开发者论坛等平台分享一些技术博文,主要包括前端开发、python后端开发、小程序开发、数据结构与算法、docker、Linux常用运维、NLP等相关技术博文,时光荏苒,未来可期,加油~
      如果喜欢博主的文章可以关注博主的个人公众号【亓官劼】(qí guān jié),里面的文章更全更新更快。如果有需要找博主的话可以在公众号后台留言,我会尽快回复消息,其他平台私信回复较慢。
    在这里插入图片描述

    由于学习工作的需要,算法刷题将会逐渐由C++向Python3过度,正在过度中,如实现的不太优美,请见谅。

    本文原创为【亓官劼】(qí guān jié ),请大家支持原创,部分平台一直在恶意盗取博主的文章!!! 全部文章请关注微信公众号【亓官劼】。


      写在最前面,本教程为小白教程,很多进阶的内容都没有做添加,后期项目会添加很多需要的功能,届时会放到文章的最后,或者在Flask专栏中,有需要的可以自取,或者看其他的文章,文档也一样。例如现在登录时的图像验证码功能的文章已在专栏中发布,有需要的可以自己添加。本文章截止目前11.7万字,中有一般的代码,所有代码都是实时运行通过的。
      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    博主的站内博客导航:博客文章内容导航(实时更新)
    更多精彩博客文章推荐


    本篇文章目录

      如果嫌本篇文章太长,不好拉动的话,可以使用上面的目录今天跳转,或者查看博主之前分篇幅写的博客,相对简短一些,博客地址为:


      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    一、开发环境 & 项目简介

    本项目源码在GitHub上进行开源,本项目的GitHub地址为:OnlineForumPlatform,大家可以去clone全部源码,喜欢的话,也欢迎大家star一下。
       我们这里使用的是Python Web的Flask框架进行开发,使用的集成开发环境为Pycharm,使用的开发语言为Python3,前端为HTML/CSS/JavaScript/Bootstrap,数据库为MySQL,使用SQL语句进行描述。
      我们这次实战采用的项目是一个在线论坛系统,字如其名,就是开发一个在线的论坛系统。需要实现各个的功能如下:

    • 注册:注册账号
    • 登录:登录账号进入系统,如果登录普通用户,则只有普通用户的权限,如果是管理员账号,则有管理员账号权限。
    • 查看论坛问题列表:查看在线论坛系统中所有已发布的问题.
    • 发布问题:
      • 发布自己的问题,等待他人回答
      • 支持富文本输入
      • 支持Markdown输入
    • 问题详情页面:显示当前问题的讨论、回复。
    • 回答问题:回答他人问题。
    • 个人中心:
      • 显示个人账号信息
      • 可以修改个人账号信息
      • 显示个人发帖情况
    • 资源专区
      • 查看资源列表(已有资源)
      • 在线预览资源
      • 下载资源
      • 上传资源
          项目目前所涉及的功能就是这些,同时还有一些网页安全的一些措施,例如对访问客户的过滤等等功能。这就是我们这个实战项目的一个大致的功能介绍。

    博主的站内博客导航:博客文章内容导航(实时更新)
    更多精彩博客文章推荐

    二、新建一个Flask项目

      既然是开发项目,我们第一步当然是先新建一个项目啦!本来打算第一章写我们的Flask基础知识部分,但是又怕有的小伙伴不会创建我们的Flask项目,索性我们就保姆式教学到底,我们从新建项目开始。

    2.1 创建项目

    这里使用Pycharm专业版作为开发工具,如果还没有Pycharm的同学可以下载安装一个,如果没有激活的话,可以查看一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免费正版
    在这里插入图片描述
    这里选择Flask项目,我这里的项目名称为OnlineForumPltform,使用virtualenv虚拟环境,模板引擎为Jinja2。
    在这里插入图片描述
    这样一个Flask基本项目就创建好啦。创建完成之后页面为这样:
    在这里插入图片描述
    我们点击运行,测试一下第一个hello world:
    在这里插入图片描述
    运行之后,我们访问运行栏中的地址,默认的地址为:http://127.0.0.1:5000
    页面返回Hello World!就表示我们第一个已经完成了第一步!
    在这里插入图片描述
    下面我们来创建一些我们下面开发所需要的文件和文件夹。
    在这里插入图片描述
    还有requirements.txt文件是存放本项目所有插件和版本信息的文本,README.md文件是本项目的说明文件。

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    2.2 安装本项目所需要的一些插件

    点开设置,进行安装插件:
    在这里插入图片描述
    点击+进入添加插件页面,这里我们先添加一个PyMysql,其他的等我们需要的时候再进行添加。
    在这里插入图片描述

    2.3 创建数据库

    这里我们先创建一个空的数据库,然后我们使用PyCharm连接这个数据库,方便我们后面进行数据库相关的操作。
    (我这里是mac系统,mysql只安装了命令行版本,如果是安装图像界面版本的可以直接进行创建).终端进入mysql的命令为mysql -u root -p然后输入密码即可进入。

    Enter password:
    Welcome to the MySQL monitor.  Commands end with ; or \g.
    Your MySQL connection id is 8
    Server version: 8.0.16 MySQL Community Server - GPL
    
    Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
    
    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.
    
    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
    
    mysql> create database OnlineForumPlatform;
    Query OK, 1 row affected (0.06 sec)
    
    mysql>
    

    即可创建成功。

    创建完成之后我们使用PyChram来连接数据库。
    在这里插入图片描述
    选择mysql数据库,然后我们填写用户名root和密码,然后填写创建的数据库名称即可,填写完成之后点击测试连接
    在这里插入图片描述
    测试连接正确之后,我们点击OK即可
    在这里插入图片描述
    然后我们在pycharm右面的Database就可以看到我们的数据库了
    在这里插入图片描述
      好了,到目前为止我们项目所需要的一些基本的插件和数据库都创建完成啦~大家都创建的顺利吗?下面我们先插播一章基础的Flask基础知识讲解,然后就正式的到我们的项目实战环节啦!如果Flask基础知识部分已经了解的,可以直接跳到我们的四,开始我们的项目实战部分啦!在文章顶部有我们文章内的目录,可以直接进行跳转。

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。


    博主的站内博客导航:博客文章内容导航(实时更新)
    更多精彩博客文章推荐

    三、Flask基础知识部分

      磨刀不误砍柴工,既然是真正的零基础,我们就先来温习一下Flask的一些基础的语法,如果已经了解的这部分的话,可以直接跳到第四章呦~

    3.1 Flask程序的基本构造

      首先就是我们的Flask程序的一个基本构造,我们首先需要从flask模块中导入我们的Flask,这是我们框架的主要模块,然后我们还需要导入我们的render_template,这是用来渲染我们的模板的,将我们的HTML页面返回给请求者。
      在前面我新建的时候已经有一个默认的app.py,是return一个Hello World,各位小伙伴们都运行成功了吗?这里我们返回的是一个Html文件,在这里说一句,我们的HTML文件一般都是存放在我们的templates文件夹中,我们就可以直接在这里返回呦,大家可以创建一个demo.html,然后使用下面这个语看看能否返回你的页面呢?

    # 从flask中导入Flask和render_template
    from flask import Flask,render_template
    
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        return render_template('demo.html')
    
    if __name__ == '__main__':
        app.run()
    

    3.2 路由器请求方法限定

      在route的参数中设置method的值,默认是GET方法,但是我们很多涉及到表单的页面为了数据的安全性,我们需要使用POST方法去创建,所以我们这里就需要设置我们的请求方式啦methods=['GET', 'POST'],完整的样例为:

    @app.route('/', methods=['GET', 'POST'])
    def index():
        return render_template('demo.html')
    

    3.3 动态路由

      有时候我们需要一个动态的参数,然后我们根据参数来返回一个对应的页面,这个时候就需要使用到动态路由啦!例如下面这个路由,他需要一个参数交错name,然后他会返回一个hello + { 你输入的name},大家可以测试一下,是不是能正确的返回你的名字。这里当你在王志中只输入http://127.0.0.1:5000/user/则会报错,因为没有输入name参数。

    @app.route('/user/<name>', methods=['GET', 'POST'])
    def user(name):
        return 'hello {}'.format(name)
    

    其中参数可以通过类型进行限定,例如:int:name即只接受int类型的参数

    3.4 Jinja2模板引擎

      由于我们的Web系统是需要一个动态的网页,可能有部分内容数据是需要计算出来的,或者是从后端服务器的数据库中获取的,所以我们需要使用Jinja2模板引擎来对我们的模板进行一个渲染。在函数中传递参数,在HTML页面中,利用{{ }}获取传递的参数,例如我们下面这个例子,在服务器端向前端传输一个变量user_var,其中变量名称为html_var,然后我们在前端进行获取变量,并且显示。需要注意的是我们在传递参数的时候,等号前面的传递的参数名(也就是我们在前端获取的时候所需要使用的名),在等号后面的是我们当地的参数名,也就是服务器中的参数,一般来说,我们两名的命名是一样的。
    app.py

    from flask import Flask,render_template
    
    app = Flask(__name__)
    
    @app.route('/user', methods=['GET', 'POST'])
    def user():
        user_var="程序设计类辅助实验教学平台"
        return render_template('demo3.html', html_var=user_var)
    
    if __name__ == '__main__':
        app.run()
    
    

    demo3.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>This is a Demo3 Page</h1>
    <h2>It is only using for test</h2>
    这是传输过来的变量:{{ html_var }}
    </body>
    </html>
    

    显示效果:
    在这里插入图片描述
    在模板中注释符号为{# #},例如{# This is a annotation #}

    3.4 传参时,列表和字典的使用:

    app.py

    from flask import Flask,render_template
    
    app = Flask(__name__)
    
    
    @app.route('/', methods=['GET', 'POST'])
    def user():
        user_var="程序设计类辅助实验教学平台"
        list_test=['这是list的1','这是list的2','这是list的3','这是list的4','这是list的5']
        dict_test={
            'name': "这是测试name",
            'key': "这是测试key"
        }
        return render_template('demo3.html', html_var=user_var,list_test=list_test,dict_test=dict_test)
    
    
    if __name__ == '__main__':
        app.run()
    
    

    demo3.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>This is a Demo3 Page</h1>
    <h2>It is only using for test</h2>
    这是传输过来的变量:{{ html_var }}<br>
    <h2>列表使用:</h2>
    {{ list_test }}<br>
    {{ list_test.1 }}<br>
    {{ list_test[2] }}<br>
    <h2>dict使用:</h2>
    {{ dict_test }}<br>
    {{ dict_test.name }}<br>
    {{ dict_test['key']}}<br>
    </body>
    </html>
    

    页面效果:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPgyPAlk-1581604100658)(evernotecid://5DBAB900-A080-44B3-994E-762E3B8C44D4/appyinxiangcom/22161713/ENResource/p554)]

    3.5 控制语句的使用

    在{% %}中进行写Python控制语句,这里使用的语法规范是Python的,在结束控制的是要使用end例如for命令结束的时候,我们要写一行{% endfor %}

    {% for num in list_test %}
        {% if num > 2 %}
        {{ num }}<br>
        {% endif %}
    {% endfor %}
    

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    3.6 过滤器的基本使用

    {{ var_test|upper }}
    

    常见的过滤器

    过滤器名 说明
    safe 渲染时不转义
    capitalize 把值的首字母大写,其余小写
    lower 把值转换成小写形式
    upper 把值转换成大写形式
    title 把值中每个单词的首字母都转换成大写
    trim 把值的首位空格去掉
    striptags 渲染之前把值中所有的HTML标签删掉

    3.7 使用flash传递消息

    app.py

    from flask import Flask,render_template,request,flash
    
    app = Flask(__name__)
    app.secret_key='this is a secret key'
    
    
    @app.route('/', methods=['GET', 'POST'])
    def user():
        #request请求对象,包括请求方式,数据等
        #flash需要对内容加密,因此要设置secret_key,做加密消息混淆
        if request.method == 'POST':
            username=request.form.get('username')
            password1=request.form.get('password1')
            password2=request.form.get('password2')
            if not all([username,password1,password2]):
                #return '参数不完整'
                flash('参数不完整')
            elif password1 == password2:
                #return 'true'
                flash("true")
            else:
                #return 'false'
                flash("false")
        return render_template('demo3.html')
    
    
    
    if __name__ == '__main__':
        app.run()
    
    

    demo3.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <form method="post">
        <label>用户名:</label><input type="text" name="username"><br>
        <label>密码:</label><input type="password" name="password1"><br>
        <label>确认密码:</label><input type="password" name="password2"><br>
        <input type="submit" value="提交"><br>
    
        {% for message in get_flashed_messages() %}
            {{ message }}
        {% endfor %}
    </form>
    </body>
    </html>
    

    显示效果:
    在这里插入图片描述

    3.8 使用WTF实现web表单

    这里可以使用Flask-WTF扩展也可以直接使用我们HTML的<form>表单,都可以。

    3.8.1 WTForms支持的HTML标准字段

    字段类型 说明
    BooleanField 复选框,值为True和False
    DateField 文本字段,值为datetime.date格式
    DareTimeField 文本字段,值为datetime.datetime格式
    FileField 文件上传字段
    HiddenField 隐藏的文本字段
    MultipleFileField 多文本上传字段
    FieldList 一组指定类型的字段
    FloatField 文本字段,值为浮点数
    FormField 把一个表单作为字段嵌入另一个表单
    IntegerField 文本字段,值为整数
    PasswordField 密码文本字段
    RadioField 一组单选按钮
    SelectField 下拉列表
    SelectMultipleField 下拉列表,可选择多个值
    SubmitField 表单提交按钮
    StringField 文本字段
    TextAreaField 多行文本字段

    3.8.2 WTForms验证函数

    验证函数 说明
    DateRequired 确保转换类型后字段中有数据
    Email 验证电子邮件地址
    EqualTo 比较两个字段的值,常用于要求输入两次密码进行确认的时候
    InputRequired 确保转换类型前字段中有数据
    IPAddress 验证IPv4网络地址
    Length 验证输入字符串的长度
    MacAddress 验证MAC地址
    NumberRange 验证输入的值在数字范围之内
    Optional 允许字段中没有输入,将跳过其他验证函数
    Regexp 使用正则表达式验证输入值
    URL 验证URL
    UUID 验证UUID
    AnyOf 确保输入值在一组可能的值中
    NoneOf 确保输入值不在一组可能的值中

    3.8.3Flask-WTF扩展的安装

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hVSzefxi-1581604100659)(evernotecid://5DBAB900-A080-44B3-994E-762E3B8C44D4/appyinxiangcom/22161713/ENResource/p560)]
    在PyCharm中打开Preferences
    在这里插入图片描述
    打开Project选项板

    在这里插入图片描述
    进行Project Interpreter设置
    在这里插入图片描述

    点击添加‘+’,然后搜索Flask-WTF
    在这里插入图片描述
    选择好版本之后,Install Package,拓展包就添加完成了
    在这里插入图片描述


    在项目中导入Flask-WTF

    from flask_wtf import FlaskForm
    

    在项目中导入Flaks-WTF的HTML标准字段

    from wtforms import StringField,PasswordField,SubmitField
    

    在项目中导入Flask-WTF的验证函数

    from wtforms.validators import DataRequired, EqualTo
    

    在使用Flask-WTF拓展的时候,需要使用CSRF Token
    在html页面中加入

    {{ html_form.csrf_token() }}
    

    3.8.4使用Flask-WTF创建表单示例

    app.py

    from flask import Flask, render_template, request, flash
    from flask_wtf import FlaskForm
    from wtforms import StringField, PasswordField, SubmitField
    from wtforms.validators import DataRequired, EqualTo
    
    app = Flask(__name__)
    
    app.secret_key = "dsauhfabf"
    
    
    class LoginForm(FlaskForm):
        username = StringField('用户名', validators=[DataRequired()])
        password1 = PasswordField('密码', validators=[DataRequired()])
        password2 = PasswordField('确认密码', validators=[DataRequired(), EqualTo('password1', "两次密码填入不一致")])
        submit = SubmitField('提交')
    
    
    @app.route('/form', methods=['GET', 'POST'])
    def login():
        login_form = LoginForm()
        if request.method == 'POST':
            username = request.form.get('username')
            password1 = request.form.get('password1')
            password2 = request.form.get('password2')
            if login_form.validate_on_submit():
                return 'success'
            else:
                flash("false")
    
        return render_template('demo3.html', html_form=login_form)
    
    
    if __name__ == '__main__':
        app.run()
    

    demo3.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <form method="post">
        <br>
        {{ html_form.csrf_token() }}
        {{ html_form.username.label }}{{ html_form.username }}<br>
        {{ html_form.password1.label }}{{ html_form.password1 }}<br>
        {{ html_form.password2.label }}{{ html_form.password2 }}<br>
        {{ html_form.submit }}<br>
    
        {% for message in get_flashed_messages() %}
            {{ message }}
        {% endfor %}
    </form>
    </body>
    </html>
    

    页面效果:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RLYBfuGA-1581604100662)(evernotecid://5DBAB900-A080-44B3-994E-762E3B8C44D4/appyinxiangcom/22161713/ENResource/p565)]
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-20LlkPBu-1581604100662)(evernotecid://5DBAB900-A080-44B3-994E-762E3B8C44D4/appyinxiangcom/22161713/ENResource/p563)]
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEHqLKt5-1581604100663)(evernotecid://5DBAB900-A080-44B3-994E-762E3B8C44D4/appyinxiangcom/22161713/ENResource/p566)]
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R1tiRf7J-1581604100663)(evernotecid://5DBAB900-A080-44B3-994E-762E3B8C44D4/appyinxiangcom/22161713/ENResource/p567)]

    3.9 Flask-SQLAlchemy拓展

    使用Flask-SQLAichemy可以处理Python对象,而不是数据实体。这里我们也可以在项目中直接使用sql语句进行操纵数据库。

    3.9.1 Flask-SQLAlchemy拓展安装方法:

    • 通过PyCharm的project interpret 继续安装
    • 通过命令行进行安装
     pip install flask-sqlalchemy
    

    在Flask-SQLAlchemy中,数据库使用URL进行指定,MySQL的URL格式为:

    mysql://username:password@hostname/datebase
    

    例如 mysql://root:password@127.0.0.1:3306/demoDatabse

    安装pymysql
    pip install pymysql

    3.9.2 配置数据库:

    from flask_sqlalchemy import SQLAlchemy
    app.config['SQLALCHEMY_DATABASE_URI']= 'mysql+pymysql://root:password@127.0.0.1:3306/Demo'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    db = SQLAlchemy(app)
    

    3.9.3创建数据库类,映射数据库表

    class ClassInfo(db.Model):
        __tablename__ = 'classinfo'
        id = db.Column(db.Integer(), primary_key=True)
        className = db.Column(db.String(20))
        classSize = db.Column(db.Integer())
    

    注意,其中的类型的第一个字母要大写

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    3.9.4在视图中操作数据库查询

    通过数据库类的query对象中的方法进行查询

    mylist = ClassInfo.query.all()
        print(mylist)
        for it in mylist:
            print(it)
        test = mylist[0].className
        return render_template('demo3.html', html_form=login_form, test=test)
    

    3.9.5 常用的SQLAlchemy查询过滤器

    过滤器 说明
    filter() 把过滤器添加到原查询上,返回一个新查询
    filter_by() 把等值过滤器添加到原查询上,返回一个新查询
    limit() 使用指定的值限制原查询返回的结果数量,返回一个新查询
    offset() 偏移原查询返回的结果,返回一个新查询
    order_by() 根据指定条件对原查询进行排序,返回一个新查询
    group_by() 根据指定调教对原查询结果进行分组,返回一个新查询

    3.9.6常用SQLAlchemy查询执行方法

    方法 说明
    all() 以列表形式返回查询的所有结果
    first() 返回查询的第一个结果,如果没有结果,则返回None
    first_or_404() 返回查询的第一个结果,如果没有结果,则终止请求,返回4040错误响应
    get() 返回指定主键对应的行,如果没有对应的行,则返回NOne
    get_or_404 返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回404错误响应
    count() 返回查询结果的数量
    paginate() 返回一个Paginate对象,包含指定范围内的结果

    3.10 使用werkzeug计算密码散列值

      在项目中我们一般都需要进行注册、登录账户,而账户密码又是一个非常重要的用户信息,我们一般都需要对他进行加密处理,这里我们在werkzeug中的security模块实现了密码散列值的计算。共有两个函数generate_password_hash(password, method='pbkdf2:sha256', salt_length=8)
    这个函数的输入为原始密码,返回密码散列值的字符串形式,供存入用户数据库。
    check_password_hash(hash,password)
    这个函数的两个参数为,数据库中存放的密码hash值,和用户输入的密码,如果一致,返回True,如果不一致,返回False。
    这里是采用的128位的散列加密,所以我们在创建数据库表的时候要注意我们字段的类型。

    3.11 文件的上传、下载

    3.11.1 上传文件

    html代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>上传文件</title>
    </head>
    <body>
    <div>
        <form action="" method="post" enctype="multipart/form-data">
            <table>
                    <tr>
                        <td>
                            选择你需要上传的文件
                        </td>
                        <td>
                            <input type="file" name="file">
                        </td>
                        <td>
                            <input type="submit">
                        </td>
                    </tr>
            </table>
    
        </form>
    </div>
    </body>
    </html>
    

    要注意,在中要设置属性enctype="multipart/form-data"

    3.11.2 在后台接收文件并保存

    在后台接受文件使用

    uoload_file = request.files.get('file')
    

    进行获取,get(‘’)中参数是前端页面中的input的name

    3.11.3 在后台保存文件

    可以使用save()进行保存

    upload_file = request.files.get('file')
    file_path = 'store'
    wenjian.save(os.path.join(file_path,upload.filename))
    

    可以secure_filename()生成随机文件名,在werkzeug.utils中,也可以手动设置文件名,加安全性

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    3.12 Flask 蓝图

    蓝图的作用是使Flask项目更加的模块化,结构更清晰。可以将相同的模块视图函数放在同一个蓝图下,同一个文件中,方便管理。

    3.12.1 蓝图的基本用法

    • 在蓝图文件中导入Blueprint:
    from flask import Blueprint
    
    • 初始化蓝图:例如user蓝图
    user_bp = Blueprint('user',__name__)
    
    • 在主app文件中注册蓝图:
      例如user_bp蓝图在Blueprint/user.py文件中,注册函数为下
    from flask import Flask
    from flask import Blueprint
    from Blueprint.user import user_bp
    app = Flask(__name__)
    app.register_blueprint(user_bp)
    
    • 给每个蓝图添加前缀
      要给某个蓝图下的所有route添加各自的前缀,可以在初始化蓝图的时候设置:例如给user_bp的每个route前添加/user前缀
    from flask import Blueprint
    user_bp = Blueprint('user',__name__,url_prefix='/user')
    

      到这里为止,我们第Flask基本知识部分就技术了,这里面涵盖了一些Flask的一些基本的语法和应用,下面我们就要开始正式的进入我们的实战系列教程了。

    博主的站内博客导航:博客文章内容导航(实时更新)
    更多精彩博客文章推荐

    四、导航栏、注册、登录、注销、主页功能的实现

      下面来到我们正式的实战环节了,在第四部分我们将一起实现导航栏、注册、登录、注销、主页功能,手把手的带你来完成这些功能的实现。
    更多博主的文章可以查看博主的站内导航博客文章内容导航(实时更新)
    本项目所有源码在GitHub开源,GitHub地址为:OnlineForumPlatform
      有需要源码可以前去查看,喜欢的话可以star一下。在做完准备工作之后,我们就正式的开始开发了。博主将带领你实现在线论坛系统的导航条、注册、登录和主页功能,在实现的同时会讲解各个功能实现的原理,手把手的教你进入Python Web全栈开发,一个字一个字的代码完成本项目。

    4.1 导航栏实现

    我们首先从导航栏开始开发,每个页面需要有导航栏,可以说一个非常常用的组件了。这里我们先创建一个hmtl页面base.html进行开发导航栏相关功能,将文件建立在templates文件夹中。
    在这里先说明一下为什么要使用base.html这个名字,因为Jinja2模板是支持继承机制的,而导航栏又是几乎每个页面都需要使用到的一个组件,所以我们这里将导航栏这个文件base.html作为一个基类,其他所有的视图文件都继承自它,并在它的基础上进行重写相关的内容,实现各个视图的不同内容。
    在创建完文件之后,由于我们这个项目前端UI部分使用Bootstrap进行快速建站,还有不会Bootstarp的同学可以看一下之前的这篇教程:什么?你还不会Bootstrap?一文教会你Bootstrap,让你也可以快速建站

    我们这里选择一个3.4.0的版本,在html中引入:

    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
    

    在引用完成之后,我们先去bootstrap样式库中找一个喜欢的导航条样式,然后加入到我们的base.html中。
    我们这里使用这个样式的,直接加入到base.html中:

    <nav class="navbar navbar-default">
      <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="#">Brand</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="#">Link <span class="sr-only">(current)</span></a></li>
            <li><a href="#">Link</a></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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">
            <li><a href="#">Link</a></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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>
              </ul>
            </li>
          </ul>
        </div><!-- /.navbar-collapse -->
      </div><!-- /.container-fluid -->
    </nav>
    

    插入之后我们打开网页就可以看到如下效果:
    在这里插入图片描述

    然后我们在根据我们的需求,对这个导航条进行一定的更改,并且为base.html页面划分几个block。
    暂时先修改为这样,后续我们加完导航之后,还需再对各个标定的页面进行一个修改。修改之后的完整代码为:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>
            {% block title %}
    {#        其他页面重写标题的地方#}
            {% endblock %}
        </title>
        {% block css %}
    {#    其他页面引用样式或者js的地方#}
        {% endblock %}
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
        <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="navigation_bar">
            <nav class="navbar navbar-default">
              <div class="container-fluid">
    {#              由于这里我们不需要使用商标,所以对Bran部分进行了删除#}
                <!-- 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="#">Link</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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">
                    <li><a href="#">Link</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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>
                      </ul>
                    </li>
                  </ul>
                </div><!-- /.navbar-collapse -->
              </div><!-- /.container-fluid -->
            </nav>
        </div>
        <div>
            {% block content %}
    {#        其他页面重写页面内容的地方#}
            {% endblock %}
        </div>
    </body>
    </html>
    

    这里暂时加了3个block,分别是title:标题。css:引用css样式和js脚本。content:页面主题内容部分。
    导航栏的剩下内容将会在项目开发的过程中逐渐完善,因为它涉及多个页面,下面我们进行下一项功能页面。

    4.2 注册功能实现

    4.2.1 注册功能实现-数据库

    在实现注册功能的时候,我们首先就需要在数据库中创建一个表来存储我们的注册信息了。这个项目预设2端(普通用户端和管理员端),那么我们表中需要存储用户的信息有:用户名(这里使用邮箱),昵称,密码,用户的权限,注册时间,联系方式等信息,我们这里暂定收集用户名,昵称,密码,用户的权限,注册时间,联系方式,这6种信息用于注册,下面我们创建一个UserInformation表。这里可以使用pychram右面的Database使用图像界面创建,也可以使用命令行创建。

    在这里插入图片描述SQL语句为:

    create table UserInformation
    (
    	email varchar(128) not null,
    	nickname nvarchar(100) default '未设置昵称' null,
    	password varchar(128) not null,
    	type int default '0' null,
    	create_time datetime default '1999-9-9 9:9:9' not null,
    	phone varchar(128) null,
    	constraint UserInformation_pk
    		primary key (email)
    );
    
    

    这样我们一个用于存储用户信息的表就创建好了,下面我们来设计一个注册的前端页面。

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    4.2.2 注册功能实现-前端

    在写前端页面之前,由于我们每个页面都是继承自base.html,因此我们可以写个extend.html来方便我们每次进行继承,在此基础上进行开发。
    extend.html页面代码为:

    {% extends 'base.html' %}
    
    {% block title %}
    
    {% endblock %}
    
    {% block css %}
    
    {% endblock %}
    
    {% block content %}
    
    {% endblock %}
    

    下面我们就正式的开始写register.html页面了。
    首先我们去bootstrap样式库中去找个页头,为页面添加一个大标题。

    <div class="page-header">
      <h1>Example page header <small>Subtext for header</small></h1>
    </div>
    

    为了方便我们实时的调试我们的页面,我们先在app.py中为注册页面添加一个路由:

    from flask import *
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def hello_world():
        return render_template('base.html')
    
    
    # 注册页面
    @app.route('/register')
    def register():
        return render_template('register.html')
    
    
    if __name__ == '__main__':
        app.run()
    
    

    这样我们访问http://127.0.0.1:5000/register就可以实时查看我们页面的信息了,方便我们对UI部分进行调试,设计一个自己喜欢的UI。在引入页头之后,我们访问发现页面有点差强人意,左对齐一点也不好看,我们可以让他居中。我们在static/css文件夹中创建一个register.css来为register.html提供样式。创建完之后我们在register.html中引入这个css。

    <link rel="stylesheet" href="/static/css/register.css">
    

    下面我们就来选择一个表单进行设计一个注册页面。这里调整UI部分比较简单并且繁琐,所以就不一步一步的记录了,在调整完之后贴上完整的代码,给大家看一下整体的效果。

    初步设计之后,register.css:

    #page_header{
        text-align: center;
    }
    
    .register_content{
        /*调整边距,调到相对中间的位置*/
        margin:10% 25%;
    }
    
    #register_butt{
        text-align: center;
    }
    

    register.html:

    {% extends 'base.html' %}
    
    {% block title %}
    注册
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/register.css">
    {% endblock %}
    
    {% block content %}
    <div class="register_content">
        <div class="page-header" id="page_header">
          <h1>注册<small>Register</small></h1>
        </div>
        <div id="register_form">
            <form method="post">
              <div class="form-group">
                <label for="exampleInputEmail1">Email address</label>
                <input type="email" class="form-control" name="email" id="exampleInputEmail1" placeholder="Email address">
              </div>
              <div class="form-group">
                <label for="exampleInputEmail1">昵称</label>
                <input type="text" class="form-control" name="nickname" id="exampleInputEmail1" placeholder="昵称">
              </div>
              <div class="form-group">
                <label for="exampleInputPassword1">密码</label>
                <input type="password" class="form-control" name="password_1" id="exampleInputPassword1" placeholder="密码">
              </div>
                <div class="form-group">
                <label for="exampleInputPassword1">确认密码</label>
                <input type="password" class="form-control" name="password_2" id="exampleInputPassword1" placeholder="确认密码">
              </div>
              <div class="form-group">
                <label for="exampleInputEmail1">联系方式</label>
                <input type="text" class="form-control" name="phone" id="exampleInputEmail1" placeholder="联系方式">
              </div>
              <div id="register_butt">
                  <button type="submit" class="btn btn-default">注册</button>
                  <button type="button" class="btn btn-default" onclick="location.href='#'">登录</button>
              </div>
            </form>
        </div>
    
    </div>
    {% endblock %}
    

    页面效果为:
    在这里插入图片描述
    整体还是简洁大方的,下面我们在针对注册页面修改一下导航条,在导航条中添加到注册页面的导航,并且添加一个block来标定上面选取的页面。
    修改之后的base.html为:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>
            {% block title %}
    {#        其他页面重写标题的地方#}
            {% endblock %}
        </title>
        {% block css %}
    {#    其他页面引用样式或者js的地方#}
        {% endblock %}
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
        <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="navigation_bar">
            <nav class="navbar navbar-default">
              <div class="container-fluid">
    {#              由于这里我们不需要使用商标,所以对Bran部分进行了删除#}
                <!-- 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="{% block homepage_class %}{% endblock %}"><a href="#">首页<span class="sr-only">(current)</span></a></li>
                    <li><a href="#">Link</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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">
                    <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注册</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">Dropdown <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>#}
    {#                  </ul>#}
    {#                </li>#}
                  </ul>
                </div><!-- /.navbar-collapse -->
              </div><!-- /.container-fluid -->
            </nav>
        </div>
        <div>
            {% block content %}
    {#        其他页面重写页面内容的地方#}
            {% endblock %}
        </div>
    </body>
    </html>
    
    

    同时在对register.html中进行添加block register_class,添加完成之后的register.html为:

    {% extends 'base.html' %}
    
    {% block title %}
    注册
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/register.css">
    {% endblock %}
    
    {% block content %}
    <div class="register_content">
        <div class="page-header" id="page_header">
          <h1>注册<small>Register</small></h1>
        </div>
        <div id="register_form">
            <form method="post">
              <div class="form-group">
                <label for="exampleInputEmail1">Email address</label>
                <input type="email" class="form-control" name="email" id="exampleInputEmail1" placeholder="Email address">
              </div>
              <div class="form-group">
                <label for="exampleInputEmail1">昵称</label>
                <input type="text" class="form-control" name="nickname" id="exampleInputEmail1" placeholder="昵称">
              </div>
              <div class="form-group">
                <label for="exampleInputPassword1">密码</label>
                <input type="password" class="form-control" name="password_1" id="exampleInputPassword1" placeholder="密码">
              </div>
                <div class="form-group">
                <label for="exampleInputPassword1">确认密码</label>
                <input type="password" class="form-control" name="password_2" id="exampleInputPassword1" placeholder="确认密码">
              </div>
              <div class="form-group">
                <label for="exampleInputEmail1">联系方式</label>
                <input type="text" class="form-control" name="phone" id="exampleInputEmail1" placeholder="联系方式">
              </div>
              <div id="register_butt">
                  <button type="submit" class="btn btn-default">注册</button>
                  <button type="button" class="btn btn-default" onclick="location.href='#'">登录</button>
              </div>
            </form>
        </div>
    
    </div>
    {% endblock %}
    
    {% block register_class %}
    active
    {% endblock %}
    
    

    调整之后的注册页面为:
    在这里插入图片描述
    当在注册页面时,会将注册的导航着重显示。同时在导航条上添加了注册和登录页面的导航。

    4.2.3 注册功能实现-后端

    下面我们就来实现注册的服务器端功能,将前端发送的信息检查,如果不正确则返回提示,如果正确则将用户信息存储到数据库中。
    既然要使用到数据库,那我们就先来配置一下config.py,这个文件主要存放项目的配置信息。
    这里我们设置一下SECRET_KEY和db。
    config.py:

    # encoding:utf-8
    import os
    import pymysql
    
    DEBUG = False
    
    SECRET_KEY = os.urandom(24)
    
    db = pymysql.connect(host='localhost', user='root', password='password1q!', db='OnlineForumPlatform', port=3306)
    
    

    配置完成之后我们在app.py中导入config.py,并且绑定配置。

    from flask import *
    import config
    
    app = Flask(__name__)
    
    # 从对象中导入config
    app.config.from_object(config)
    

    然后我们开始写注册的后端逻辑功能。本系列第一次写后端代码,这里做一个详细的说明,有Flask基础不错的同学可以直接跳到后面的整体代码。
    由于我们路由默认的请求方式只有一种是GET请求,但是我们注册的话,为了安全,form表单一般使用POST请求,因此我们这里先设置请求方式:

    @app.route('/register',methods=['GET','POST'])
    

    然后我们再获取前端表单的信息:

    email = request.form.get('email')
    nickname = request.form.get('nickname')
    password_1 = request.form.get('password_1')
    password_2 = request.form.get('password_2')
    phone = request.form.get('phone')
    

    在获取完之后我们就需要对这些数据进行处理了,首先我们要检查数据是否完整,如果信息填写不完整肯定是不可以注册的,我们返回提示,这里我们使用flash传递提示回到注册页面。

            if not all([email,nickname,password_1,password_2,phone]):
                flash("信息填写不全,请将信息填写完整")
                return render_template('register.html')
    

    既然使用flash进行传递消息,那我们就需要在前端将flash消息显示出来。我们将这段代码放到前端合适的一个位置,这个位置自己选择一个显眼的位置即可。这里我讲这个信息提示放在了页头的下方。

    <span style=" font-size:20px;color: red" >
    	{% for item in get_flashed_messages() %}
    		{{ item }}
    	{% endfor %}
    </span>
    

    下面继续来完善我们的后端,信息填写完整之后,我们还需要验证两次密码输入的是否一致,如果不一致,则需要返回错误提示。

    if password_1 != password_2:
    	flash("两次密码填写不一致!")
    	return render_template('register.html')
    

    如果信息都填写正确了,那下面我们开始对密码进行加密,我们这里使用的是pbkdf2:sha256加密方式,对密码进行128位的散列加密,可以极大的保护用户信息的安全性。要使用这个加密,我们需要导入它:

    from werkzeug.security import generate_password_hash, check_password_hash
    

    然后我们使用generate_password_hash来加密我们的密码,由于password_1和password_2是一样的,那么我们只需要加密password_1即可:

    password = generate_password_hash(password_1, method="pbkdf2:sha256", salt_length=8)
    

    这样我们的准备工作就完成了,下面我们开始将信息存储到数据库中:
    首先我们要将我们config文件中配置的db导入进来:

    from config import db
    

    然后我们来获取db的cursor,来进行相关数据库操作,这里我们使用Python直接操纵数据库,当然,大家也可以使用Flask-SQLAlchemy来进行操作数据库,这里就使用Python直接操纵数据库的方式进行。
    首先我们要检查我们的数据库中email是否存在,即当前用户是否已经存在,我们使用email进行唯一表示用户,所以不允许重复,我们还需要检查一下email。

    try:
    	cur = db.cursor()
    	sql = "select * from UserInformation where email = '%s'"%email
        db.ping(reconnect=True)
        cur.execute(sql)
     	result = cur.fetchone()
    	if result is not None:
    		flash("该Email已存在!")
     		return render_template('register.html')
    except Exception as e:
    	raise e
    

    这里注册的用户类型我们肯定是不可以注册管理员的,不然这个管理员就是形同虚设了,我们这里通过register页面来注册的用户,我们统一都是普通用户,这里type置为0。然后创建时间我们使用服务器端来获取当前的时间。这里要使用time库,需要导入

    import time
    create_time = time.strftime("%Y-%m-%d %H:%M:%S")
    

    然后将数据插入数据库中,同时在插入完成之后我们应该返回首页,并且为登录状态,由于我们这里首页和登录都还没有实现,所以这里我们先写一个空的首页路由,并且创建一个index.html,使用路由返回index.html页面。
    index.html我们先这样空置,等在本讲的最后我们来实现这个首页。
    index.html:

    {% extends 'base.html' %}
    
    {% block title %}
    主页
    {% endblock %}
    
    {% block css %}
    
    {% endblock %}
    
    {% block content %}
    
    {% endblock %}
    

    完成注册服务器端功能之后的app.py全部代码为:

    from flask import *
    from werkzeug.security import generate_password_hash, check_password_hash
    from config import db
    import time
    import config
    
    app = Flask(__name__)
    
    # 从对象中导入config
    app.config.from_object(config)
    
    
    
    @app.route('/')
    def index():
        return render_template('index.html')
    
    
    # 注册页面
    @app.route('/register',methods=['GET','POST'])
    def register():
        if request.method == 'GET':
            return render_template('register.html')
        if request.method == 'POST':
            email = request.form.get('email')
            nickname = request.form.get('nickname')
            password_1 = request.form.get('password_1')
            password_2 = request.form.get('password_2')
            phone = request.form.get('phone')
            if not all([email,nickname,password_1,password_2,phone]):
                flash("信息填写不全,请将信息填写完整")
                return render_template('register.html')
            if password_1 != password_2:
                flash("两次密码填写不一致!")
                return render_template('register.html')
            password = generate_password_hash(password_1, method="pbkdf2:sha256", salt_length=8)
            try:
                cur = db.cursor()
                sql = "select * from UserInformation where email = '%s'"%email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result is not None:
                    flash("该Email已存在!")
                    return render_template('register.html')
                else:
                    create_time = time.strftime("%Y-%m-%d %H:%M:%S")
                    sql = "insert into UserInformation(email, nickname, password, type, create_time, phone) VALUES ('%s','%s','%s','0','%s','%s')" %(email,nickname,password,create_time,phone)
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    db.commit()
                    cur.close()
                    return redirect(url_for('index'))
            except Exception as e:
                raise e
    
    if __name__ == '__main__':
        app.run()
    

    这样我们的注册功能就全部实现啦!可以先去注册一个自己的测试账号,方便我们后面的各项功能测试呦!

    4.3 登录功能实现

    下面我们就来实现登录功能

    4.3.1 登录功能实现-前端

    登录的话,前端和我们的注册类似,上面一个页头,然后下面使用一个表单,并且设置一个block用来标记一下当前的页面即可,在注册的前端设计的时候已经有详细的说明,这里就不在赘述了,创建一个login.html,下面废话不过说,直接上代码:
    首先我们在app.py中写一个空的路由,方便我们在前端页面中使用url_for来重定向页面:

    # 注册页面
    @app.route('/login')
    def login():
        return render_template('login.html')
    

    然后我们创建一个login.css来设置页面的样式:

    #page_header{
        text-align: center;
    }
    
    .login_content{
        /*调整边距,调到相对中间的位置*/
        margin:10% 30%;
    }
    
    #login_butt{
        text-align: center;
    }
    

    login.html部分为:

    {% extends 'base.html' %}
    
    {% block title %}
    登录
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/login.css">
    {% endblock %}
    
    {% block content %}
    <div class="login_content">
        <div class="page-header" id="page_header">
          <h1>登录<small>Login</small></h1>
        </div>
        <div id="login_form">
            <form method="post">
                <span style=" font-size:20px;color: red" >
                    {% for item in get_flashed_messages() %}
                    {{ item }}
                    {% endfor %}
                </span>
              <div class="form-group">
                <label for="exampleInputEmail1">Email address</label>
                <input type="email" class="form-control" name="email" id="exampleInputEmail1" placeholder="Email address">
              </div>
              <div class="form-group">
                <label for="exampleInputPassword1">密码</label>
                <input type="password" class="form-control" name="password" id="exampleInputPassword1" placeholder="密码">
              </div>
              <div id="login_butt">
                  <button type="submit" class="btn btn-default">登录</button>
                  <button type="button" class="btn btn-default" onclick="location.href='{{ url_for('register') }}'">注册</button>
              </div>
            </form>
        </div>
    </div>
    {% endblock %}
    
    {% block login_class %}
    active
    {% endblock %}
    

    在登录页面完成之后,我们同时修改一下导航条的内容,在base.html中,将首页的导航修改为:

     <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登录</a></li>
    

    如果不修改base的话,直接运行上面的html修改之后的页面会报错,因为在login.html中使用了{% block login_class %} {% endblock %},这个是刚刚在base.html中添加的block,用于标记当前页面。

    修改完成之后我们的登录页面效果为:
    在这里插入图片描述

    4.3.2 登录功能实现-后端

    登录功能的后端我们主要分为两部分,一个是接受表单并验证表单信息的正确性,并进行反馈,第二部分就是登录状态的保持,下面我们将分这两部分来实现后端的功能。

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    4.3.2.1 登录功能实现-后端-验证登录信息

    这里同样的,我们登录的表单为了安全性,一般都使用POST来发送,这里先设置允许的请求方式。

    @app.route('/login',methods=['GET','POST'])
    

    然后我们来获取前端的Email和密码:

    # 登录页面
    @app.route('/login',methods=['GET','POST'])
    def login():
        if request.method == 'GET':
            return render_template('login.html')
        if request.method == 'POST':
            email = request.form.get('email')
            password = request.form.get('password')
    

    然后为了防止出错,我们先验证数据的完整性:

    if not all([email,password]):
        flash("请将信息填写完整!")
        return render_template('login.html')
    

    然后我们来验证密码是否正确,首先我们先从数据库中获取当前登录email的密码,并验证它,如果密码不存,则说明该用户不存在,即未注册,我们返回提示。如果密码存在,那么我就获取密码,然后使用check_password_hash()函数来验证它,如果密码正确,我们将用户名(即email)放入session中,方便我们下面进行实现登录状态保持的功能。
    代码为:

    # 登录页面
    @app.route('/login',methods=['GET','POST'])
    def login():
        if request.method == 'GET':
            return render_template('login.html')
        if request.method == 'POST':
            email = request.form.get('email')
            password = request.form.get('password')
            if not all([email,password]):
                flash("请将信息填写完整!")
                return render_template('login.html')
            try:
                cur = db.cursor()
                sql = "select password from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result is None:
                    flash("该用户不存在")
                    return render_template('login.html')
                if check_password_hash(result[0],password):
                    session['email'] = email
                    session.permanent = True
                    return redirect(url_for('index'))
                else:
                    flash("密码错误!")
                    return render_template('login.html')
            except Exception as e:
                raise e
    
    

    这样我们的登录的基本功能就实现了,大家可以去测试下,如果登录正常则会返回首页,如果登录失败则会显示各种各样的提示。下面我们就要实现登录的另一个功能,登录状态保持,我们登录之后如何让系统一直保持我们的登录状态呢?

    4.3.2.2 登录功能实现-后端-登录状态保持

    要实现登录状态保持,我们这里可以使用上下文钩子函数来一直保持登录状态。

    # 登录状态保持
    @app.context_processor
    def login_status():
        # 从session中获取email
        email = session.get('email')
        # 如果有email信息,则证明已经登录了,我们从数据库中获取登陆者的昵称和用户类型,来返回到全局
        if email:
            try:
                cur = db.cursor()
                sql = "select nickname,type from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result:
                    return {'email':email,'nickname':result[0] ,'user_type':result[1]}
            except Exception as e:
                raise e
        # 如果email信息不存在,则未登录,返回空
        return {}
    

    有了登录状态保持之后,我们就可以再次的修改导航条,将右侧的注册登录进行修改,当未登录时,显示登录注册,当已登录的时候,显示注销和{ 用户昵称 },用户昵称处我们可以使用下拉列表,等后期我们会在此添加功能。
    修改之后的导航栏右面内容为:

    {% if email %}
        <li class="{% block login_out_class %}{% endblock %}"><a href="{{ url_for('register') }}">注销</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <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>
          </ul>
        </li>
    {% else %}
        <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注册</a></li>
        <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登录</a></li>
    {% endif %}
    

    这样我们登录之后的页面效果为:在这里插入图片描述
    为了防止小伙伴不知道上面的这段代码到底在哪里修改,这里再贴一下修改后的base.html的全部代码:这里的注销的链接还没有写,在等会我们实现了注销的功能之后再在这里添加链接。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>
            {% block title %}
    {#        其他页面重写标题的地方#}
            {% endblock %}
        </title>
        {% block css %}
    {#    其他页面引用样式或者js的地方#}
        {% endblock %}
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
        <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="navigation_bar">
            <nav class="navbar navbar-default">
              <div class="container-fluid">
    {#              由于这里我们不需要使用商标,所以对Bran部分进行了删除#}
                <!-- 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="{% block homepage_class %}{% endblock %}"><a href="{{ url_for('index') }}">首页<span class="sr-only">(current)</span></a></li>
                    <li><a href="#">Link</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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 email %}
                        <li class=""><a href="{{ url_for('register') }}">注销</a></li>
                        <li class="dropdown">
                          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <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>
                          </ul>
                        </li>
                    {% else %}
                        <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注册</a></li>
                        <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登录</a></li>
                    {% endif %}
                  </ul>
                </div><!-- /.navbar-collapse -->
              </div><!-- /.container-fluid -->
            </nav>
        </div>
        <div>
            {% block content %}
    {#        其他页面重写页面内容的地方#}
            {% endblock %}
        </div>
    </body>
    </html>
    

    4.4 注销功能实现

    下面我们就来完成用户的注销功能,其实注销功能实现很简单,我们这里就简单粗暴的将session清空即可达到注销的效果,清空之后我们重定向到首页即可。

    # 用户注销
    @app.route('/logout')
    def logout():
        session.clear()
        return redirect(url_for(('index')))
    

    4.5 主页实现

    其实这里排版有一点小小的失误,我们应该第一个实现首页的,不过现在实现也并不影响。首页我们需要的功能很简单,找个漂亮唯美的背景图,中间加上几个"在线论坛系统"大字即可,一个漂漂亮亮的首页就完成了,因为我们目前首页不需要别的功能在,暂时就设置这样子就好了,我们先去随便找一个唯美的风景图。我们这里在百度图库中随便找了一个,然后把它下载放到我们的/static/img文件中。然后在我们之前创建的index.html中插入这张图片,再创建一个index.css,对样式进行调整。
    在插入之后我们发现图片和上面导航栏会有一点点的空隙,看着还不舒服。这里我们需要为base.html创建一个base.css来设置一下导航条的样式,为他设置height = 52px;这样会无缝衔接,这个值也可以稍微大一点。然后我们在base.html中引入样式。

    .navigation_bar{
        height: 52px;
    }
    
        <link rel="stylesheet" href="/static/css/base.css">
    

    这样之后我们的图片就可以无缝衔接了。我们的图片是作为一个大的div背景图片插入的,这样方便我们在上面进行显示标题。下面直接贴代码:
    index.html:

    {% extends 'base.html' %}
    
    {% block title %}
    主页
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/index.css">
    {% endblock %}
    
    {% block content %}
    <div class="index_content">
        <div class="index_title">
            <h1>在线论坛系统<small>Online Forum Platform</small></h1>
        </div>
    </div>
    {% endblock %}
    

    index.css:

    .index_content{
        margin: 0;
        padding: 0;
        width: 100%;
        /*这里由于图片高为1200,所以直接设置了1200,大家可以可以根据自己的需求进行设置*/
        height: 1200px;
        background-image: url("/static/img/index.jpeg");
    }
    .index_title{
        text-align: center;
        padding: 20% 20%;
    }
    .index_title h1{
        font-style: italic;
        font-size: 60px;
        text-shadow: 0.15em 0.15em 0.1em #333;
        font-weight: bolder;
    }
    

    在app.py中,我们主页的路由暂时只需要返回页面即可:

    # 主页
    @app.route('/')
    def index():
        return render_template('index.html')
    

    下面来让我们看一下主页的效果图:(博客直男审美,各位可以自己设计主页的效果)在这里插入图片描述
      好了,第四部分就讲到这里,下面我们将继续实现论坛的功能,该到了实现论坛真正功能的时候,这部分主要是各个系统都通用的功能:导航条、登录、注册、注销、主页。大家都顺利实现了吗?如果有什么问题可以评论区里进行询问,如果喜欢这篇文章的话,可以点赞关注支持下博主。


    五、发布帖子、论坛页面实现

    本项目所有源码在GitHub开源,GitHub地址为:OnlineForumPlatform
    有需要源码可以前去查看,喜欢的话可以star一下

    下面就开始第五部分的内容了,这部分将会带领大家一起实现论坛的主体功能,论坛的问题列表,帖子详情页面,发布帖子,回答问题等功能,让这个在线论坛系统成为一个“真正的论坛”,让他具有论坛的功能。


    博主的站内博客导航:博客文章内容导航(实时更新)
    更多精彩博客文章推荐


    5.1 发布帖子的功能实现

    在这里的haul,由于问题列表等页面,没问题的话无法具体的进行显示,所以这里我们先开发发布问题的功能,然后再开发问题列表页面。

    5.1.1 发布帖子的实现-前端

    前端的话我们先实现一个富文本编辑器来实现信息的编辑,使我们编辑的文章具有格式化,如果不知道如何在前端嵌入富文本编辑器的话,可以看我之前的博客:一本教你如何在前端实现富文本编辑器:一本教你如何在前端实现富文本编辑器如果需要创建Markdown文本编辑器的话,也可以看博主的博客:小白都能看得懂的教程 一本教你如何在前端实现富文本编辑器
    在此基础上,我们来设计我们的前端页面。
    这里我们直接上代码:
    post_issue.css:

    .post_issue_content{
        margin-left: 20%;
        margin-right: 20%;
    }
    
    #page_header{
        text-align: center;
    }
    
    #issue_titile_div{
        height: 35px;
        margin: 0;
    }
    
    /*设置富文本编辑器默认的高度*/
    #edui1_iframeholder{
        height: 200px;
    }
    
    #inputEmail3{
        margin-left: 0;
        padding-left: 0;
        width: 100%;
    }
    
    #post_issue_butt{
        margin-top: 60px;
        text-align: center;
        height: 35px;
    }
    
    #post_issue_butt button{
        height: 35px;
    }
    

    post_issue.html:

    {% extends 'base.html' %}
    
    {% block title %}
    发布帖子
    {% endblock %}
    
    {% block css %}
        <link rel="stylesheet" href="/static/css/post_issue.css">
        <!-- 引入配置文件 -->
        <script src="/static/ueditor/ueditor.config.js"}></script>
        <!-- 引入编辑器源码文件 -->
        <script src="/static/ueditor/ueditor.all.min.js"}></script>
    {% endblock %}
    
    {% block content %}
    <div class="post_issue_content">
        <div class="page-header" id="page_header">
          <h1>发布帖子<small>Post issue</small></h1>
        </div>
        <form class="post_issue_form">
            <div class="form-group" id="issue_titile_div">
                <input type="text" class="form-control" name="title" id="inputEmail3" placeholder="请输入标题">
            </div>
            <div class="ueditor_div">
                <script id="editor" type="text/plain">
                    请输入发布的内容!
    
                </script>
            </div>
            <div id="post_issue_butt">
              <button type="submit" class="btn btn-default">发布帖子</button>
            </div>
        </form>
    </div>
    <!-- 实例化编辑器 -->
    <script type="text/javascript">
        var editor = UE.getEditor('editor');
    </script>
    {% endblock %}
    
    {% block post_issue_class %}
    active
    {% endblock %}
    

    同时我们也可以修改下导航栏的右端,使登录时,显示发布帖子的导航,如果不修改,则需要将post_issue.html的最后三行删除,否则会报错。
    添加的内容为:添加在注销的标签之前即可.

    <li class="{% block post_issue_class %}{% endblock %}"><a href="{{ url_for('post_issue') }}">发布帖子</a></li>
    

    为了方便调试,我们这里在app.py中添加了发布帖子的路由:

    # 发布帖子
    @app.route('/post_issue')
    def post_issue():
        return render_template('post_issue.html')
    

    此时发布帖子的前端页面效果为:
    在这里插入图片描述
    至此,我们发布帖子的前端页面就完成了,页面还算不错,下面我们继续完成其他的功能。

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    5.1.2 发布帖子的实现-数据库

    下面我们来设计帖子的数据库。首先我们需要明确我们需要存储哪些内容:标题、内容、发布人的Id(即email)、发布时间、帖子号(用来唯一标示一个帖子)。但是由于我们是一个论坛,里面的内容还需要很多人来回复,因此我们设计成2个表,分别为Issue和Comment,使用Issue表存储每个Issue的标题、发布人、发布时间、Issue号,使用Comment来存储每个评论(包括第一楼,发帖人的内容作为第一楼)的内容、发布人、发布时间和Connment号。
    Issue表为:其中需要添加外键,email依赖UserInformation的email
    在这里插入图片描述
    SQL描述为:

    create table Issue
    (
    	Ino varchar(128) null,
    	email varchar(128) not null,
    	title text default null,
    	issue_time datetime null,
    	constraint Issue_UserInformation_email_fk
    		foreign key (email) references UserInformation (email)
    );
    
    create unique index Issue_Ino_uindex
    	on Issue (Ino);
    
    alter table Issue
    	add constraint Issue_pk
    		primary key (Ino);
    
    

    Comment表为:
    在这里插入图片描述
    在这里插入图片描述
    SQL语句为:

    create table Comment
    (
    	Cno varchar(128) not null,
    	Ino varchar(128) not null,
    	comment text null,
    	comment_time datetime default '1999-9-9 9:9:9' not null,
    	email varchar(128) null,
    	constraint Comment_pk
    		primary key (Cno, Ino),
    	constraint Comment_Issue_Ino_fk
    		foreign key (Ino) references Issue (Ino),
    	constraint Comment_UserInformation_email_fk
    		foreign key (email) references UserInformation (email)
    );
    
    
    

    这样我们发布帖子需要使用的数据库就完成啦。

    5.1.3 发布帖子的实现-后端

    下面我们来完成发布帖子的后端实现。首先我们先这是允许的请求方式

    @app.route('/post_issue',methods=['GET','POST'])
    

    然后我们再去将post_issue.html中的form的method设置为post:

    <form class="post_issue_form" method="post">
    

    我们开始来后端获取前端发送的标题和内容:

    title = request.form.get('title')
    comment = request.form.get('editorValue')
    

    然后我们来获取发布人(也就是当前登录账号),当前时间。

    email = session.get('email')
    issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
    

    下面我们开始获取Issue号,这里我们获取数据库中最大的Issue号,然后将它+1即可,如果没有,那么我们就设为1。

    try:
        cur = db.cursor()
        sql = "select max(Ino) from Issue"
        db.ping(reconnect=True)
        cur.execute(sql)
        result = cur.fetchone()
        if result is None:
            Ino = int(1)
        else:
            Ino = int(request[0]) + 1
    except Exception as e:
        raise e
    

    所有需要获取的数据都获取好了,我们将数据存入数据库中,这样发布帖子的功能也就算是完成了。在Issue中,为了信息安全起见,我们使用128位随机数来作为ID,具体使用在代码中体现。
    后端代码为;

    
    # 生成128随机id
    def gengenerateID():
        re = ""
        for i in range(128):
            re += chr(random.randint(65, 90))
        return re
    
    
    # 发布帖子
    @app.route('/post_issue', methods=['GET', 'POST'])
    def post_issue():
        if request.method == 'GET':
            return render_template('post_issue.html')
        if request.method == 'POST':
            title = request.form.get('title')
            comment = request.form.get('editorValue')
            email = session.get('email')
            issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
            try:
                cur = db.cursor()
                Ino = gengenerateID()
                sql = "select * from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                # 如果result不为空,即存在该ID,就一直生成128位随机ID,直到不重复位置
                while result is not None:
                    Ino = gengenerateID()
                    sql = "select * from Issue where Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    result = cur.fetchone()
                sql = "insert into Issue(Ino, email, title, issue_time) VALUES ('%s','%s','%s','%s')" % (
                Ino, email, title, issue_time)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                '1', Ino, comment, issue_time, email)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                return render_template('post_issue.html')
            except Exception as e:
                raise e
    

    这样我们发布帖子的整体功能就是完成了,下面我们再来完成显示帖子列表的功能,让我们发布的帖子显示出来。

    5.2 论坛页面的实现

    5.2.1 论坛页面的实现-前端

    论坛页面我们先简单做一个帖子列表的显示,后续有新功能的话,我们再来添加。这里没有什么东西,这里先设置一个简洁的页面,具体的功能完后面再逐渐的去完善,手把手的带你完成这个项目的全部过程。下面我们直接上效果图和代码吧,都是上面细讲过的东西,一个简单的ul构成的页面。这里的页面是静态的,等我们完成后端功能之后,我们再来将这里修改为一个动态的更新。
    效果图为:
    在这里插入图片描述
    formula.css代码:

    .formula_content{
        margin: 5% 20%;
    }
    
    #page_header{
        text-align: center;
    }
    
    .issue_list_ul{
        list-style-type: none;
        margin-left: 0;
        padding-left: 0;
    }
    .author_info{
        text-align: right;
    }
    
    .issue_div{
        border-bottom:1px solid #eee;
    }
    
    .issue_content{
        max-height: 200px;
    }
    

    formula.html代码;

    {% extends 'base.html' %}
    
    {% block title %}
    论坛
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/formula.css">
    {% endblock %}
    
    {% block content %}
    <div class="formula_content">
        <div class="page-header" id="page_header">
            <h1>帖子列表</h1>
        </div>
        <div class="issue_list_div">
            <ul class="issue_list_ul">
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <h3>
                                这里是测试的标题
                            </h3>
                            <p>
                                这里是测试发布第一楼内容
                            </p>
                        </div>
                        <div class="author_info">
                            <p class="post-info">
                                <span>作者:测试作者</span>
                                <span>发布时间:2020年3月28日 22:24:30</span>
                            </p>
                        </div>
                    </div>
                </li>
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <h3>
                                这里是测试的标题
                            </h3>
                            <p>
                                这里是测试发布第一楼内容
                            </p>
                        </div>
                        <div class="author_info">
                            <p class="post-info">
                                <span>作者:测试作者</span>
                                <span>发布时间:2020年3月28日 22:24:30</span>
                            </p>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
        
    {% endblock %}
    {% block formula_class %}
    active
    {% endblock %}
    

    在这里设置了block,在base.html进行了添加,如果base.html不修改的话,把最后3行删掉,否则会报错。在base.html中添加了论坛的导航<li class="{% block formula_class %}{% endblock %}"><a href="{{ url_for('formula') }}">论坛</a></li>
    修改完之后的base.html全部代码为:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>
            {% block title %}
    {#        其他页面重写标题的地方#}
            {% endblock %}
        </title>
        {% block css %}
    {#    其他页面引用样式或者js的地方#}
        {% endblock %}
        <link rel="stylesheet" href="/static/css/base.css">
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
        <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="navigation_bar">
            <nav class="navbar navbar-default">
              <div class="container-fluid">
    {#              由于这里我们不需要使用商标,所以对Bran部分进行了删除#}
                <!-- 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="{% block homepage_class %}{% endblock %}"><a href="{{ url_for('index') }}">首页<span class="sr-only">(current)</span></a></li>
                    <li class="{% block formula_class %}{% endblock %}"><a href="{{ url_for('formula') }}">论坛</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <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 email %}
                        <li class="{% block post_issue_class %}{% endblock %}"><a href="{{ url_for('post_issue') }}">发布帖子</a></li>
                        <li class=""><a href="{{ url_for('register') }}">注销</a></li>
                        <li class="dropdown">
                          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <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>
                          </ul>
                        </li>
                    {% else %}
                        <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注册</a></li>
                        <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登录</a></li>
                    {% endif %}
                  </ul>
                </div><!-- /.navbar-collapse -->
              </div><!-- /.container-fluid -->
            </nav>
        </div>
        <div class="content" style="padding: 0;margin: 0;">
            {% block content %}
    {#        其他页面重写页面内容的地方#}
            {% endblock %}
        </div>
    </body>
    </html>
    

    在这里为了方便我们使用url_for,所以我们需要在app.py中为formula设置路由:

    # 论坛页面
    @app.route('/formula')
    def formula():
        return render_template('formula.html')
    

    这就是前端部分了,下面我们再来实现论坛的后端功能

    5.2.2 论坛页面的实现-后端

    论坛页面后端的需要也很明显,就是要从数据库中将所有Issue数据返回到前端,在前端中显示。这里我们直接使用一个3表连接,将数据查出来,然后返回到前端:

    # 论坛页面
    @app.route('/formula')
    def formula():
        if request.method == 'GET':
            try:
                cur = db.cursor()
                            sql = "select Issue.Ino, Issue.email,UserInformation.nickname,issue_time,Issue.title,Comment.comment from Issue,UserInformation,Comment where Issue.email = UserInformation.email and Issue.Ino = Comment.Ino and Cno = '1' order by issue_time DESC "
                db.ping(reconnect=True)
                cur.execute(sql)
                issue_information = cur.fetchall()
                cur.close()
                return render_template('formula.html',issue_information = issue_information)
            except Exception as e:
                raise e
    

    然后我们在前端进行显示,这里我们在前端的部分将两个测试的li换成:

    {% for issue in issue_information %}
        <li class="issue_list_li">
            <div class="issue_div">
                <div class="issue_content">
                    <h3>
                        {{ issue[4] }}
                    </h3>
                    <article>
                        {{ issue[5]|safe }}
                    </article>
                </div>
                <div class="author_info">
                    <p class="post-info">
                        <span>作者:{{ issue[2] }}</span>&emsp;
                        <span>发布时间:{{ issue[3] }}</span>
                    </p>
                </div>
            </div>
        </li>
    {% endfor %}
    

    这样就完成了,这里再贴一下完整的formula.html代码,防止有的同学不知道替换哪些内容:

    {% extends 'base.html' %}
    
    {% block title %}
    论坛
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/formula.css">
    {% endblock %}
    
    {% block content %}
    <div class="formula_content">
        <div class="page-header" id="page_header">
            <h1>帖子列表</h1>
        </div>
        <div class="issue_list_div">
            <ul class="issue_list_ul">
                {% for issue in issue_information %}
                    <li class="issue_list_li">
                        <div class="issue_div">
                            <div class="issue_content">
                                <h3>
                                    {{ issue[4] }}
                                </h3>
                                <article>
                                    {{ issue[5]|safe }}
                                </article>
                            </div>
                            <div class="author_info">
                                <p class="post-info">
                                    <span>作者:{{ issue[2] }}</span>&emsp;
                                    <span>发布时间:{{ issue[3] }}</span>
                                </p>
                            </div>
                        </div>
                    </li>
                {% endfor %}
    
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <h3>
                                这里是测试的标题
                            </h3>
                            <p>
                                这里是测试发布第一楼内容
                            </p>
                        </div>
                        <div class="author_info">
                            <p class="post-info">
                                <span>作者:测试作者</span>
                                <span>发布时间:2020年3月28日 22:24:30</span>
                            </p>
                        </div>
                    </div>
                </li>
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <h3>
                                这里是测试的标题
                            </h3>
                            <p>
                                这里是测试发布第一楼内容
                            </p>
                        </div>
                        <div class="author_info">
                            <p class="post-info">
                                <span>作者:测试作者</span>
                                <span>发布时间:2020年3月28日 22:24:30</span>
                            </p>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
    
    {% endblock %}
    {% block formula_class %}
    active
    {% endblock %}
    

    论坛现在的页面效果:现在已经可以将我们之前发布的帖子显示出来了,小伙伴们可以再发几个帖子测试下功能:
    在这里插入图片描述
    好了,现在论坛页面也完成了,那么我们在发布完帖子之后,我们可以将页面跳转到帖子列表。
    在app.py中修改发布帖子的函数,直接将发布成功的return修改为:return redirect(url_for('formula'))
    修改之后的发布帖子的完整功能代码为:

    # 发布帖子
    @app.route('/post_issue', methods=['GET', 'POST'])
    def post_issue():
        if request.method == 'GET':
            return render_template('post_issue.html')
        if request.method == 'POST':
            title = request.form.get('title')
            comment = request.form.get('editorValue')
            email = session.get('email')
            issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
            try:
                cur = db.cursor()
                Ino = gengenerateID()
                sql = "select * from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                # 如果result不为空,即存在该ID,就一直生成128位随机ID,直到不重复位置
                while result is not None:
                    Ino = gengenerateID()
                    sql = "select * from Issue where Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    result = cur.fetchone()
                sql = "insert into Issue(Ino, email, title, issue_time) VALUES ('%s','%s','%s','%s')" % (
                Ino, email, title, issue_time)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                '1', Ino, comment, issue_time, email)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('formula'))
            except Exception as e:
                raise e
    

      好了,现在小伙伴们可以去测试自己的论坛系统啦!现在已经完成了一半了,我们可以发布问题并且显示出来了。第五部分的内容到这里结束啦~小伙伴们都顺利实现了吗?如果有问题可以在评论区里进行问哦,如果喜欢这篇文章,可以点赞关注支持下博主。
      博主的更多文章导航可以查看我的栈内导航文章,里有各个文章的超链接,实时更新。博客文章内容导航(实时更新)


    博主的站内博客导航:博客文章内容导航(实时更新)
    更多精彩博客文章推荐

    本项目所有源码在GitHub开源,GitHub地址为:OnlineForumPlatform有需要源码可以前去查看,喜欢的话可以star一下

    六、帖子详情、回复帖子、访问过滤功能实现

      在前面的几个部分中,我们完成了项目的创建,导航条、注册、登录、论坛页面、发布帖子等功能的实现,目前我们的系统已经可以发布和查看我们的帖子了,并且可以登录我们的账号。下面我们就来继续完善我们的系统,让他看查看每个帖子的详情,并且能够回复帖子等功能。

    6.1 帖子详情页面、帖子回复实现

      帖子详情页面的数据库我们已经在第三讲中实现了,就是我们的Issue和Comment表,下面我们来一起实现以下帖子详情页面的前端和后端功能,让它能够展示各个帖子的一个详情页面。

    6.1.1 帖子详情页面实现-后端

      这里我们先实现帖子详情页面的后端,因为我们发现如果先做前端的话,在做后端的话,还需要对前端进行一定的修改,这样不利于我们这样文章式的进行表述。所以我们在这里先做后端的功能,这里先创建一个issue_detail.html页面,留作后面的前端使用。
      如果要进入我们的帖子详情页面,那么首先我们需要一个参数,就是我们Ino(Issue的编号),因为这个编号是唯一表示我们的帖子的。所以在这里我们写帖子详情的路由的时候就和前面不太一样了,我们需要使用一个带参数的路由,用来接收这个Ino,跳转到我们相对应的帖子详情。

    # 问题详情
    @app.route('/issue/<Ino>')
    def issue_detail(Ino):
        return render_template('issue_detail.html')
    

    在有了Ino之后,我们就可以根据Ino来对数据库中的文章数据进行查找了。下面我们来思考我们需要返回到前端的数据由哪些呢?我们主要需要下面这项数据:

    • 文章标题
    • 每章每层楼的内容,包括:
      • 评论内容
      • 作者昵称
      • 评论发布时间
      • 楼号(Cno)

      我们主要就是需要这几项数据,数据里面的文章标题的每个问题详情页面固定的一个标题,每层楼的内容的每层楼不一样的,所以两个我们分别进行查询。

    # 问题详情
    @app.route('/issue/<Ino>')
    def issue_detail(Ino):
        try:
            if request.method == 'GET':
                cur = db.cursor()
                sql = "select Issue.title from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                # 这里返回的是一个列表,即使只有一个数据,所以这里使用cur.fetchone()[0]
                issue_title = cur.fetchone()[0]
                sql = "select UserInformation.nickname,Comment.comment,Comment.comment_time,Comment.Cno from Comment,UserInformation where Comment.email = UserInformation.email and Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                comment = cur.fetchall()
                cur.close()
                # 返回视图,同时传递参数
                return render_template('issue_detail.html',Ino=Ino,issue_title=issue_title,comment=comment)
        except Exception as e:
            raise e
    

    这就是目前简单的后端逻辑,虽说我们目前只有get请求方式,这里也限定一下,为了安全起见。

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    6.1.2 帖子详情页面实现-前端

      前端的话,我们这里为了便于理解,前端还是精简布局(博主直男审美,写不出漂亮的UI)即可。我们需要一个页头来显示我们的标题,然后下面使用一个<ul></ul>显示我们的帖子详情即可,在<li></li>中固定一个div,每层楼的结构固定,然后再在中间填充上我们从后端获取到的数据即可。前端的文件这里我们使用issue_detail.html和issue_detail.css。详细的结构可以去看我的GitHub,每一次博客更新之后我都会把源代码push到GitHub中。本项目的GitHub地址为:OnlineForumPlatform
      这里直接上代码吧,前端的代码和前面基本一样,前面已经各个步骤都仔细讲解过了,如果前端部分的还有不清楚的可以看看前面的。
    issue_detail.html:

    {% extends 'base.html' %}
    
    {% block title %}
        {{ issue_title }}
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/issue_detail.css">
    {% endblock %}
    
    {% block content %}
    <div class="formula_content">
        <div class="page-header" id="page_header">
            <h1>{{ issue_title }}</h1>
        </div>
        <div class="issue_list_div">
            <ul class="issue_list_ul">
                {% for comm in comment %}
                    <li class="issue_list_li">
                        <div class="issue_div">
                            <div class="issue_content">
                                <article>
                                    {{ comm[1]|safe }}
                                </article>
                            </div>
                            <div class="author_info">
    {#                            <p class="cno_info">{{ comm[3] }}</p>#}
                                <p class="info">
                                    <span class="cno_info">{{ comm[3] }}楼</span>&emsp;
                                    <span>
                                        <span>作者:{{ comm[0] }}</span>&emsp;&emsp;
                                        <span>发布时间:{{ comm[2] }}</span>
                                    </span>
                                </p>
                            </div>
                        </div>
                    </li>
                {% endfor %}
            </ul>
        </div>
    </div>
    {% endblock %}
    

    issue_detail.css:

    .formula_content{
        margin: 5% 20%;
    }
    
    #page_header{
        text-align: center;
    }
    
    .issue_list_ul{
        list-style-type: none;
        margin-left: 0;
        padding-left: 0;
    }
    .author_info{
        text-align: right;
    }
    
    .issue_div{
        border-bottom:1px solid #eee;
    }
    
    .issue_content{
        min-height: 88px;
    }
    
    .post-info{
        text-align: right;
    }
    
    .cno_info{
        text-align: left;
    }
    
    

      有仔细的小伙伴是不是发现了,这里前端的部分和论坛页面的前端基本类似,只不过改掉了其中显示的内容而已。页面效果为:
    在这里插入图片描述

    6.1.3 帖子回复实现-前端

      这里其实有点纠结放在哪里的,直接放在帖子详情的下面也可以,单独一个页面也可以,原本是打算单独一个页面的,因为那样的话,会比较放富文本编辑器和Markdown编辑器的双编辑器。不过又想了想,使用Markdown回复帖子的人应该不多,所以这里就放在下面的吧,使用富文本编辑器。如果想做双编辑器的话也可以自己稍作修改。
      这里只需要在最后添加一个<li></li>然后再嵌套一个div,然后添加一个富文本编辑器的实例即可,还不会在前端实现富文本编辑器的可以去看看博主之前的文章:一本教你如何在前端实现富文本编辑器如果需要实现Markdown文本编辑器也可以查看博主之前的文章:小白都能看得懂的教程 一本教你如何在前端实现markdown编辑器
    这里就直接上修改完的代码和效果图吧:
    issue_detail.html:

    {% extends 'base.html' %}
    
    {% block title %}
        {{ issue_title }}
    {% endblock %}
    
    {% block css %}
        <link rel="stylesheet" href="/static/css/issue_detail.css">
        <!-- 引入配置文件 -->
        <script src="/static/ueditor/ueditor.config.js"}></script>
        <!-- 引入编辑器源码文件 -->
        <script src="/static/ueditor/ueditor.all.min.js"}></script>
    {% endblock %}
    
    {% block content %}
    <div class="formula_content">
        <div class="page-header" id="page_header">
            <h1>{{ issue_title }}</h1>
        </div>
        <div class="issue_list_div">
            <ul class="issue_list_ul">
                {% for comm in comment %}
                    <li class="issue_list_li">
                        <div class="issue_div">
                            <div class="issue_content">
                                <article>
                                    {{ comm[1]|safe }}
                                </article>
                            </div>
                            <div class="author_info">
    {#                            <p class="cno_info">{{ comm[3] }}</p>#}
                                <p class="info">
                                    <span class="cno_info">{{ comm[3] }}楼</span>&emsp;
                                    <span>
                                        <span>作者:{{ comm[0] }}</span>&emsp;&emsp;
                                        <span>发布时间:{{ comm[2] }}</span>
                                    </span>
                                </p>
                            </div>
                        </div>
                    </li>
                {% endfor %}
                    <li>
                        <div>
                            <form class="post_issue_form" method="post">
                                <input type="hidden" name="Ino" value="{{ Ino }}">
                                <div class="ueditor_div">
                                    <script id="editor" type="text/plain">
                                    请输入回复的内容!
                                    </script>
                                </div>
                                <div id="post_issue_butt">
                                  <button type="submit" class="btn btn-default">回复</button>
                                </div>
                            </form>
                        </div>
                    </li>
            </ul>
        </div>
    <!-- 实例化编辑器 -->
    <script type="text/javascript">
        var editor = UE.getEditor('editor');
    </script>
    </div>
    {% endblock %}
    

    效果图:
    在这里插入图片描述
      效果还是不错的,这样我们就完成了我们前端的富文本编辑器的输入啦,是不是很有成就感~

    6.1.3 帖子回复实现-后端

      后端的话,我们需要做的就是将回复人的email、回复时间、回复楼号、回复内容存储进数据库中,并显示。这里由于我们是在同一个页面中进行回复,所以我们要使用到POST方法进行传输数据。下面我们首先将这些需要存储的数据在服务器端获取到:

    Ino = request.values.get('Ino')
    email = session.get('email')
    comment = request.form.get('editorValue')
    comment_time = time.strftime("%Y-%m-%d %H:%M:%S")
    

      这里的Cno需要来查找数据库中的最大Cno,然后进行+1所得。问题详情完整代码为:帖子回复实现的代码主要为POST请求部分:

    # 问题详情
    @app.route('/issue/<Ino>', methods=['GET', 'POST'])
    def issue_detail(Ino):
        if request.method == 'GET':
            try:
                if request.method == 'GET':
                    cur = db.cursor()
                    sql = "select Issue.title from Issue where Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    # 这里返回的是一个列表,即使只有一个数据,所以这里使用cur.fetchone()[0]
                    issue_title = cur.fetchone()[0]
                    sql = "select UserInformation.nickname,Comment.comment,Comment.comment_time,Comment.Cno from Comment,UserInformation where Comment.email = UserInformation.email and Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    comment = cur.fetchall()
                    cur.close()
                    # 返回视图,同时传递参数
                    return render_template('issue_detail.html', Ino=Ino, issue_title=issue_title, comment=comment)
            except Exception as e:
                raise e
    
        if request.method == 'POST':
            Ino = request.values.get('Ino')
            email = session.get('email')
            comment = request.values.get('editorValue')
            comment_time = time.strftime("%Y-%m-%d %H:%M:%S")
            try:
                cur = db.cursor()
                sql = "select max(Cno) from Comment where Ino = '%s' " % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                Cno = int(result[0]) + 1
                Cno = str(Cno)
                sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                Cno, Ino, comment, comment_time, email)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('issue_detail',Ino = Ino))
            except Exception as e:
                raise e
    

    页面效果:在这里插入图片描述
    这里提醒一下各位,在测试回复和发布帖子的时候一定要登录账号,这里还没有完善过滤器功能,下面进行完善

    6.2访问过滤功能实现

      在上面发布帖子和回复帖子的时候是不是有很多小伙伴都都发现在未登录的时候进行发布会报错?是因为我们的email是函数依赖于UerInformation表的,如果未登录的话则没有,也就无法插入到数据库中。同时还有很多功能页面我们是针对不同用户的,比如有的管理员功能页面,我们就不能让普通用户和访客进入,有些页面我们只想让登陆的用户进行查看,那我们就要让访客无法访问。
      下面我们就来实现这个功能,要使用这个功能,我们就需要使用Python的装饰器功能。如果对装饰器这个功能不太了解也没关系,我们这边只需要一些简单的功能即可。我会在代码中进行详细的进行注释。我们将装饰器放在decorators.py文件中。我们这里先简单的写一个限制登录的装饰器,如下。

    from functools import wraps
    from flask import session, url_for, redirect
    
    
    # 登录限制的装饰器 用于某些只让登录用户查看的网页
    def login_limit(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 我们这里用来区分是否登录的方法很简答,就是查看session中是否赋值了email,如果赋值了,说明已经登录了
            if session.get('email'):
                # 如果登录了,我们就正常的访问函数的功能
                return func(*args, **kwargs)
            else:
                # 如果没登录,我们就将它重定向到登录页面,这里大家也可以写一个权限错误的提示页面进行跳转
                return redirect(url_for('login'))
    
        return wrapper
    

      写完之后,我们在app.py中导入它from decorators import login_limit 然后再每一个我们需要进行登录限制的路由之后加上我们的装饰器@login_limit即可,此时我们需要进行限制登录的两个页面就是我们的发布帖子页面和我们的帖子详情页面需要进行一个限制。进行限制之后,如果我们没登录的时候进行发布帖子就会自动的跳转到登录页面,提醒你进行登录啦~
    这里在贴一下到目前为止app.py的完整文件代码吧:

    from flask import *
    from werkzeug.security import generate_password_hash, check_password_hash
    from config import db
    import random
    import time
    import config
    from decorators import login_limit
    
    app = Flask(__name__)
    
    # 从对象中导入config
    app.config.from_object(config)
    
    
    # 登录状态保持
    @app.context_processor
    def login_status():
        # 从session中获取email
        email = session.get('email')
        # 如果有email信息,则证明已经登录了,我们从数据库中获取登陆者的昵称和用户类型,来返回到全局
        if email:
            try:
                cur = db.cursor()
                sql = "select nickname,type from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result:
                    return {'email': email, 'nickname': result[0], 'user_type': result[1]}
            except Exception as e:
                raise e
        # 如果email信息不存在,则未登录,返回空
        return {}
    
    # 主页
    @app.route('/')
    def index():
        return render_template('index.html')
    
    
    # 注册页面
    @app.route('/register', methods=['GET', 'POST'])
    def register():
        if request.method == 'GET':
            return render_template('register.html')
        if request.method == 'POST':
            email = request.form.get('email')
            nickname = request.form.get('nickname')
            password_1 = request.form.get('password_1')
            password_2 = request.form.get('password_2')
            phone = request.form.get('phone')
            if not all([email, nickname, password_1, password_2, phone]):
                flash("信息填写不全,请将信息填写完整")
                return render_template('register.html')
            if password_1 != password_2:
                flash("两次密码填写不一致!")
                return render_template('register.html')
            password = generate_password_hash(password_1, method="pbkdf2:sha256", salt_length=8)
            try:
                cur = db.cursor()
                sql = "select * from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result is not None:
                    flash("该Email已存在!")
                    return render_template('register.html')
                else:
                    create_time = time.strftime("%Y-%m-%d %H:%M:%S")
                    sql = "insert into UserInformation(email, nickname, password, type, create_time, phone) VALUES ('%s','%s','%s','0','%s','%s')" % (
                        email, nickname, password, create_time, phone)
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    db.commit()
                    cur.close()
                    return redirect(url_for('index'))
            except Exception as e:
                raise e
    
    
    # 登录页面
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'GET':
            return render_template('login.html')
        if request.method == 'POST':
            email = request.form.get('email')
            password = request.form.get('password')
            if not all([email, password]):
                flash("请将信息填写完整!")
                return render_template('login.html')
            try:
                cur = db.cursor()
                sql = "select password from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result is None:
                    flash("该用户不存在")
                    return render_template('login.html')
                if check_password_hash(result[0], password):
                    session['email'] = email
                    session.permanent = True
                    cur.close()
                    return redirect(url_for('index'))
                else:
                    flash("密码错误!")
                    return render_template('login.html')
            except Exception as e:
                raise e
    
    
    # 用户注销
    @app.route('/logout')
    def logout():
        session.clear()
        return redirect(url_for(('index')))
    
    
    # 生成128随机id
    def gengenerateID():
        re = ""
        for i in range(128):
            re += chr(random.randint(65, 90))
        return re
    
    
    # 发布帖子
    @app.route('/post_issue', methods=['GET', 'POST'])
    @login_limit
    def post_issue():
        if request.method == 'GET':
            return render_template('post_issue.html')
        if request.method == 'POST':
            title = request.form.get('title')
            comment = request.form.get('editorValue')
            email = session.get('email')
            issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
            try:
                cur = db.cursor()
                Ino = gengenerateID()
                sql = "select * from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                # 如果result不为空,即存在该ID,就一直生成128位随机ID,直到不重复位置
                while result is not None:
                    Ino = gengenerateID()
                    sql = "select * from Issue where Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    result = cur.fetchone()
                sql = "insert into Issue(Ino, email, title, issue_time) VALUES ('%s','%s','%s','%s')" % (
                    Ino, email, title, issue_time)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                    '1', Ino, comment, issue_time, email)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('formula'))
            except Exception as e:
                raise e
    
    
    # 论坛页面
    @app.route('/formula')
    def formula():
        if request.method == 'GET':
            try:
                cur = db.cursor()
                sql = "select Issue.Ino, Issue.email,UserInformation.nickname,issue_time,Issue.title,Comment.comment from Issue,UserInformation,Comment where Issue.email = UserInformation.email and Issue.Ino = Comment.Ino order by issue_time DESC "
                db.ping(reconnect=True)
                cur.execute(sql)
                issue_information = cur.fetchall()
                cur.close()
                return render_template('formula.html', issue_information=issue_information)
            except Exception as e:
                raise e
    
    
    # 问题详情
    @app.route('/issue/<Ino>', methods=['GET', 'POST'])
    @login_limit
    def issue_detail(Ino):
        if request.method == 'GET':
            try:
                if request.method == 'GET':
                    cur = db.cursor()
                    sql = "select Issue.title from Issue where Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    # 这里返回的是一个列表,即使只有一个数据,所以这里使用cur.fetchone()[0]
                    issue_title = cur.fetchone()[0]
                    sql = "select UserInformation.nickname,Comment.comment,Comment.comment_time,Comment.Cno from Comment,UserInformation where Comment.email = UserInformation.email and Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    comment = cur.fetchall()
                    cur.close()
                    # 返回视图,同时传递参数
                    return render_template('issue_detail.html', Ino=Ino, issue_title=issue_title, comment=comment)
            except Exception as e:
                raise e
    
        if request.method == 'POST':
            Ino = request.values.get('Ino')
            email = session.get('email')
            comment = request.values.get('editorValue')
            comment_time = time.strftime("%Y-%m-%d %H:%M:%S")
            try:
                cur = db.cursor()
                sql = "select max(Cno) from Comment where Ino = '%s' " % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                Cno = int(result[0]) + 1
                Cno = str(Cno)
                sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                Cno, Ino, comment, comment_time, email)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('issue_detail',Ino = Ino))
            except Exception as e:
                raise e
    
    
    if __name__ == '__main__':
        app.run()
    

      好了,现在小伙伴们可以去测试自己的论坛系统啦!现在已经完成了一大半了,我们可以发布问题并且显示出来,还可以对帖子进行回复,查看帖子详情了,现在小小的在线论坛系统已经五脏俱全啦!(虽然设施简陋点点),同时我们还实现了我们的访问过滤功能,不然非法用户访问我们的页面。~小伙伴们都顺利实现了吗?如果有问题可以在评论区里进行问哦,如果喜欢这篇文章,可以点赞关注支持下博主。
      博主的更多文章导航可以查看我的栈内导航文章,里有各个文章的超链接,实时更新。博客文章内容导航(实时更新)


    博主的站内博客导航:博客文章内容导航(实时更新)
    更多精彩博客文章推荐


    七、个人中心相关页面功能实现

      本项目所有源码在GitHub开源,GitHub地址为:OnlineForumPlatform
      今天来到我们实战系列的第七部分,在之前的六部分里,我们已经完成了在线论坛系统的导航条、登录、注册、论坛页面、帖子详情页面等的功能实现,目前我们的论坛已经可以进行帖子发布,查看帖子详情,并且进行回复信息了。在上一部分中,我们还是先了功能限制,对不同用户的访问进行过滤。下面我们继续来完善我们的这个在线论坛系统,今天我们来完善我们的在线论坛系统的个人中心。

    7.1个人中心页面实现

      今天我们就来实现我们的个人中心页面,这里本来是打算使用AJAX实现的,个人中心使用AJAX实现的话,可以使得整体不变,当我们点击各个分栏的时候只返回各个分栏的信息,是一个非常好的选择。但是由于这个系列打算让每一个看的人都能够看懂,实现这个功能,所以我们这简化一点,直接使用普通的一个请求页面。后面有兴趣的同学可以进行进一步的优化,后面有空的话,我也会将后面逐渐优化的教程发出来。
      这里既然是小白的入门专场,我们就一起从简啦~主要是为了给大家练练手,带大家进入Python Web开发的大门。

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    7.1.1 个人中心页面-后端

      那我们就开始实现我们我的个人中心了,这里我们的个人中心只显示我们的一些基础资料(我们也没设置多少),这里只做一个大致的样式展示,更多的功能我们后续慢慢的进行一个拓展。
      我们去看了一下我们的数据库,发现我们个人中心能够进行展示的,也就只有我们的email、昵称、用户类型、创建时间和手机号码可以进行一个展示和修改。那我们就展示这么多吧,大家也可以添加一下个性签名,头像等一系列的个人标识进入数据库中。
      那我们就开始获取我们的数据了,首先我们需要限制只有登录的用户才可以进入到我们的个人中心,限制的方法在上一讲中已经实现了。

    # 个人中心
    @app.route('/personal')
    @login_limit
    def personal():
        if request.method == 'GET':
            email = session.get('email')
            try:
                cur = db.close()
                sql = "select email, nickname, type, create_time, phone from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                personal_info = cur.fetchone()
            except Exception as e:
                raise e
            return render_template('personal.html',personal_info = personal_info)
    

    7.1.2 个人中心页面-前端

      在后端获取完数据之后,我们在前端对获取到的数据进行展示即可。所以我们先设计个勉强能够看得过去的架子,来展示我们的数据。先上个效果图吧:在这里插入图片描述
    确实是比较简陋的,但是功能齐全吧。我们这里使用的是<table>标签进行显示的,也可以使用<li>进行显示。这里时间上代码吧,personal.html的代码为:

    {% extends 'base.html' %}
    
    {% block title %}
    个人中心
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/personal.css">
    {% endblock %}
    
    {% block content %}
    <div class="personal_content">
        <div class="page-header" id="page_header">
          <h1>个人中心<small>Personal center</small></h1>
        </div>
        <div class="personal_info">
            <table>
                <tr class="personal_tr">
                    <td class="personal_td">
                        Email address:
                    </td>
                    <td class="personal_td">
                        {{ personal_info[0] }}
                    </td>
                </tr>
                <tr class="personal_tr">
                    <td class="personal_td">
                        昵称:
                    </td>
                    <td class="personal_td">
                        {{ personal_info[1] }}
                    </td>
                </tr>
                <tr class="personal_tr">
                    <td class="personal_td">
                        注册时间:
                    </td>
                    <td class="personal_td">
                        {{ personal_info[3] }}
                    </td>
                </tr>
                <tr class="personal_tr">
                    <td class="personal_td">
                        手机号:
                    </td>
                    <td class="personal_td">
                        {{ personal_info[4] }}
                    </td>
                </tr>
                <tr class="personal_tr">
                    <td class="personal_td">
                        用户类型:
                    </td>
                    <td class="personal_td">
                        {% if personal_info[2] == 0 %}
                            普通用户
                        {% else %}
                            管理员
                        {% endif %}
                    </td>
                </tr>
            </table>
        </div>
    </div>
    {% endblock %}
    

    personal.css代码为:

    .personal_content{
        margin-left: 20%;
        margin-right: 20%;
        margin-top: 5%;
    }
    #page_header{
        text-align: center;
    }
    
    .personal_info{
        font-size: 24px;
        margin-left: 10%
    ;
    }
    
    .personal_td{
        width: 300px;
    }
    
    .personal_tr{
        height: 50px;
    }
    

    同时这里也修改了base.html的内容,我们让个人中心在下拉列表中进行显示,我们修改了下拉列表中第一个值和链接,效果图为:在这里插入图片描述
    这里的修改的代码为:

    <li class="dropdown">
                          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
                          <ul class="dropdown-menu">
                            <li><a href="{{ url_for('personal') }}">个人中心</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>
                          </ul>
                        </li>
    

    7.2 修改密码功能实现

      到这里我们来实现我们的修改密码功能,其实如果使用AJAX实现的话,这一讲的所有功能都应该在个人中心一个页面中进行体现的,我们这里就先这样分开实现吧。大家可以自行改进~
      在这里我们就先从前端开始实现了。

    7.2.1 修改密码功能实现-前端

      我们修改密码的话,这里采用一个简单的验证,就是知道我们当前的密码就可以进行一个修改密码。我们设计一个表单,分别输入旧密码,新密码,确认新密码即可。先上个效果图再说实现:在这里插入图片描述
      这效果图还是大家熟悉的味道,有没有!我们这里还是一个简单的div,里面加个页头,下面3个input。这里和前面的原理一样,这里我们预留了一个flash传输消息的地方,用于等会后端向前面传递消息提示,我们就直接上代码吧:
    change_password.html:

    {% extends 'base.html' %}
    
    {% block title %}
    修改密码
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/change_password.css">
    {% endblock %}
    
    {% block content %}
    <div class="change_password_content">
        <div class="page-header" id="page_header">
          <h1>修改密码<small>Change Password</small></h1>
        </div>
        <div class="change_password_div">
            <form method="post">
                <span style=" font-size:20px;color: red" >
                    {% for item in get_flashed_messages() %}
                    {{ item }}
                    {% endfor %}
                </span>
                <div class="form-group">
                    <label for="exampleInputPassword1">旧密码:</label>
                    <input type="password" class="form-control" name="old_password" id="exampleInputPassword1" placeholder="密码">
                </div>
                <div class="form-group">
                    <label for="exampleInputPassword1">新密码:</label>
                    <input type="password" class="form-control" name="new_password1" id="exampleInputPassword1" placeholder="密码">
                </div>
                <div class="form-group">
                    <label for="exampleInputPassword1">确认密码:</label>
                    <input type="password" class="form-control" name="new_password2" id="exampleInputPassword1" placeholder="密码">
                </div>
                <div id="password_butt">
                  <button type="submit" class="btn btn-default">修改密码</button>
              </div>
            </form>
        </div>
    </div>
    {% endblock %}
    

    change_paasword.css:

    #page_header{
        text-align: center;
    }
    .change_password_content{
        margin-left: 20%;
        margin-right: 20%;
        margin-top: 8%;
    }
    #password_butt{
        text-align: center;
    }
    

    7.2.1 修改密码功能实现-后端

      下面我们来实现修改密码的后端功能,使他能够正确的修改密码。我们首先获取我们的当前登录用户的用户名,这里我们的修改密码的功能也是只有我们登录的用户才可以访问的功能。首先我们判断是不是3个数据都获取到了,并且2个密码一致,如果有错误,我们返回提示。如果数据都正确的话,我们开始处理,先获取我们的email。我们获取到email之后去数据库中查找我们的密码,这里的密码是加密的,所以我们要使用check_password_hash()来进行验证,如果旧密码是正确的,我们就进行修改新密码,如果不正确,则返回。完整的后端代码为:

    # 修改密码
    @app.route('/change_password',methods=['GET','POST'])
    @login_limit
    def change_password():
        if request.method == 'GET':
            return render_template('change_password.html')
        if request.method == 'POST':
            old_password = request.form.get('old_password')
            new_password1 = request.form.get('new_password1')
            new_password2 = request.form.get('new_password2')
            if not all([old_password,new_password1,new_password2]):
                flash("信息填写不全!")
                return render_template('change_password.html')
            if new_password1 != new_password2:
                flash("两次新密码不一致!")
                return render_template('change_password.html')
            email = session.get('email')
            try:
                cur = db.cursor()
                sql = "select password from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                password = cur.fetchone()[0]
                if check_password_hash(password,old_password):
                    password = generate_password_hash(new_password1, method="pbkdf2:sha256", salt_length=8)
                    sql = "update UserInformation set password = '%s' where email = '%s'" % (password,email)
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    db.commit()
                    cur.close()
                    return render_template('index.html')
                else:
                    flash("旧密码错误!")
                    return render_template('change_password.html')
            except Exception as e:
                raise e
    

    在实现后端功能之后,我们可以再导航栏中加入修改密码的导航,我们还是加载下来列表中,将第二个修改为修改密码。

    <li class="dropdown">
      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
      <ul class="dropdown-menu">
        <li><a href="{{ url_for('personal') }}">个人中心</a></li>
        <li><a href="{{ url_for('change_password') }}">修改密码</a></li>
        <li><a href="#">Something else here</a></li>
        <li role="separator" class="divider"></li>
        <li><a href="#">Separated link</a></li>
      </ul>
    </li>
    

    效果为:在这里插入图片描述

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    7.3 查看发布帖子功能实现-后端

    7.3.1 查看发布帖子功能实现-后端

      下面我们来实现个人中心的另一个功能,就是查看我们自己所发布的所有的帖子信息,并且可以在此页面进入到我们所发布的帖子中去。这里我们还是先写后端,向前端去传输我们的数据。这个页面是也是需要我们登录才可以查看的,因为查看的是我们所登录的这个账号所发布的帖子列表。
      所以这里我们先获取我们需要的数据,然后把它返回到前端即可,这里先创建一个前端的html文件,这里使用show_issue.html,我们后端的代码为:

    # 查看已发布的帖子
    # 查看已发布的帖子
    @app.route('/show_issue')
    @login_limit
    def show_issue():
        if request.method == 'GET':
            email = session.get('email')
            try:
                cur = db.cursor()
                sql = "select ino, email, title, issue_time from Issue where email = '%s' order by issue_time desc" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                issue_detail = cur.fetchall()
            except Exception as e:
                raise e
            return render_template('show_issue.html',issue_detail=issue_detail)
    

    7.3.2 查看发布帖子功能实现-前端

      下面我们开始实现前端的一个功能,我们这里使用和论坛列表相似的功能,我们这里只需要显示帖子的标题即可。这里先上效果图:在这里插入图片描述
    我们这里只需要设计一个<li>将我们后端发送的数据仅显示出来即可。show_issue.html:

    {% extends 'base.html' %}
    
    {% block title %}
    已发布的帖子
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/show_issue.css">
    {% endblock %}
    
    {% block content %}
    <div class="show_issue_content">
        <div class="page-header" id="page_header">
            <h1>已发布的帖子列表</h1>
        </div>
        <div class="issue_list_div">
            <ul class="issue_list_ul">
                {% for issue in issue_detail %}
                    <li class="issue_list_li">
                        <div class="issue_div">
                            <div class="issue_content">
                                <h3>
                                    <a href="{{ url_for('issue_detail',Ino = issue[0]) }}">
                                        {{ issue[2] }}
                                    </a>
                                </h3>
                            </div>
                            <div class="author_info">
                                <p class="post-info">
                                    <span>发布时间:{{ issue[3] }}</span>
                                </p>
                            </div>
                        </div>
                    </li>
                {% endfor %}
            </ul>
        </div>
    </div>
    {% endblock %}
    

    show_issue.css:

    #page_header{
        text-align: center;
    }
    
    .issue_list_ul{
        list-style-type: none;
        margin-left: 0;
        padding-left: 0;
    }
    .author_info{
        text-align: right;
    }
    
    .issue_div{
        border-bottom:1px solid #eee;
    }
    
    .issue_content{
        max-height: 200px;
    }
    .show_issue_content{
        margin-right: 20%;
        margin-left: 20%;
        margin-top: 8%;
    }
    

    这里我们也修改一下base.html文件,设置下来列表。在这里插入图片描述
    我们在导航栏的下拉列表中添加我们这章个人中心的三个页面,这里贴一下目前base.html的全部代码,防止有的小伙伴找不到修改的地方。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>
            {% block title %}
    {#        其他页面重写标题的地方#}
            {% endblock %}
        </title>
        {% block css %}
    {#    其他页面引用样式或者js的地方#}
        {% endblock %}
        <link rel="stylesheet" href="/static/css/base.css">
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
        <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="navigation_bar">
            <nav class="navbar navbar-default">
              <div class="container-fluid">
    {#              由于这里我们不需要使用商标,所以对Bran部分进行了删除#}
                <!-- 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="{% block index_class %}{% endblock %}"><a href="{{ url_for('index') }}">首页<span class="sr-only">(current)</span></a></li>
                    <li class="{% block formula_class %}{% endblock %}"><a href="{{ url_for('formula') }}">论坛</a></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 email %}
                        <li class="{% block post_issue_class %}{% endblock %}"><a href="{{ url_for('post_issue') }}">发布帖子</a></li>
                        <li class=""><a href="{{ url_for('register') }}">注销</a></li>
                        <li class="dropdown">
                          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
                          <ul class="dropdown-menu">
                            <li><a href="{{ url_for('personal') }}">个人中心</a></li>
                            <li><a href="{{ url_for('change_password') }}">修改密码</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="{{ url_for('show_issue') }}">已发布的帖子</a></li>
                          </ul>
                        </li>
                    {% else %}
                        <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注册</a></li>
                        <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登录</a></li>
                    {% endif %}
                  </ul>
                </div><!-- /.navbar-collapse -->
              </div><!-- /.container-fluid -->
            </nav>
        </div>
        <div class="content" style="padding: 0;margin: 0;">
            {% block content %}
    {#        其他页面重写页面内容的地方#}
            {% endblock %}
        </div>
    </body>
    </html>
    

      好了,现在小伙伴们可以去测试自己的论坛系统啦!现在已经基本完成了了,我们可以发布问题并且显示出来,还可以对帖子进行回复,查看帖子详情了,现在小小的在线论坛系统已经五脏俱全啦!(虽然设施简陋点点),同时我们还实现了我们的访问过滤功能,不然非法用户访问我们的页面,在这一讲中我们实现了我们个人中心页面的各个功能~小伙伴们都顺利实现了吗?如果有问题可以在评论区里进行问哦,如果喜欢这篇文章,可以点赞关注支持下博主。
      博主的更多文章导航可以查看我的栈内导航文章,里有各个文章的超链接,实时更新。博客文章内容导航(实时更新)


    博主的站内博客导航:博客文章内容导航(实时更新)
    更多精彩博客文章推荐


    八、资源专区相关功能实现

      本项目所有源码在GitHub开源,GitHub地址为:OnlineForumPlatform
      今天来到我们实战系列的第八部分,资源专区相关功能实现,在之前的七个部分里,我们已经完成了在线论坛系统的导航条、登录、注册、论坛页面、帖子详情页面、个人页面、修改密码、查看已发布帖子,个人中心页面,修改密码,查看已发布帖子等的功能实现,目前我们的论坛已经可以进行帖子发布,查看帖子详情,并且进行回复信息了。在上一讲中,我们还是实现了功能限制,对不同用户的访问进行过滤。下面我们继续来完善我们的这个在线论坛系统,今天我们来为这个系统添加资源专区,这里我们主要分为资源上传,资源列表(查看资源列表),在线查看资源文件、资源文件下载的功能。

    8.1资源上传功能实现

      这里我们来开始实现资源上传功能,我们先从前端开始实现,然后到后端进行存储。之前写过一篇如何实现文件上传下载的博文,如果对这方面还不太了解的同学,可以先去看一下博文:Python Flask文件上传下载,这篇博文简单的介绍了如何实现文件的上传与下载,这里我们来进行一个详细的实现。

    8.1资源上传功能实现-前端

      资源文件上传,我们这里设计一个<div>,然后里面放一个<form>,进行文件的上传,和文件描述。这里先创建一个post_file.html文件用来当前的前端的文件,post_file.css文件用来记录样式。我们这个页面还是继承自base.html。还是先上个效果图,然后我们给一个源码,因为这里前端的话,我们就是一个<table>然后里面放3行的信息。
    在这里插入图片描述
    这里我们需要注意的就是,在我们的form中,不要忘记设置enctype,需要把这个属性设置为multipart/form-data
    post_file.html:

    {% extends 'base.html' %}
    
    {% block title %}
    资源上传
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/post_file.css">
    {% endblock %}
    
    {% block content %}
    <div class="post_file_content">
        <div class="page-header" id="page_header">
          <h1>资源上传<small>Resource upload</small></h1>
        </div>
        <div class="post_file_div">
            <form action="" method="post" enctype="multipart/form-data">
                <table id="file_table">
                    <tr>
                        <td>
                            选择你需要上传的文件
                        </td>
                        <td>
                            <input id="file_butt" type="file" name="file">
                        </td>
                    </tr>
                    <tr>
                        <td>
                            请输入文件名称:
                        </td>
                        <td>
                            <div class="form-group">
                                <input type="text" class="form-control" name="filename" id="exampleInputEmail1" placeholder="请输入文件名称:">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            请输入文件描述信息:
                        </td>
                        <td>
                            <div class="form-group">
                                <input type="text" class="form-control" name="file_info" id="exampleInputEmail1" placeholder="请输入文件描述信息:">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td colspan="2">
                            <div id="login_butt">
                              <button type="submit" class="btn btn-default">上传</button>
                          </div>
                        </td>
                    </tr>
                </table>
            </form>
        </div>
    </div>
    {% endblock %}
    

    post_file.css

    .post_file_content{
        margin-top: 8%;;
        margin-left: 25%;
        margin-right: 25%;
    }
    
    #page_header{
        text-align: center;
    }
    
    #file_table td{
        width: 200px;
    }
    #login_butt{
        text-align: center;
    }
    

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    8.1.2资源上传功能实现-数据库端

      这里我们又需要新增一个表了,用来存储我们的一个文件的存储信息,我们主要存储的信息有:Fno(用来唯一标识我们的文件)、文件名称、文件描述信息、文件上传时间。
    在这里插入图片描述
    SQL语句为:

    create table Files
    (
    	Fno varchar(128) not null,
    	filename nvarchar(128) default '未命名' null,
    	file_info nvarchar(128) default '没有描述信息' null,
    	file_time datetime null,
    	email varchar(128) null,
    	constraint Files_UserInformation_email_fk
    		foreign key (email) references UserInformation (email)
    );
    
    create unique index Files_Fno_uindex
    	on Files (Fno);
    
    alter table Files
    	add constraint Files_pk
    		primary key (Fno);
    
    
    

    8.1.3资源上传实现-后端

      下面我们来实现我们资源上传的数据库端,首先我们需要在我们的项目文件中创建一个文件夹用来存放我们上传的文件。这里创建了一个store文件夹用于存放上传的文件。在这里插入图片描述
    下面我们就来讲从前端发送的文件保存到我们的服务器的文件夹中。这里为了服务器文件的安全性,我们这里文件的名称采用再次命名,一来可以防止重复的文件名,二来可以防止文件名称中有一些特殊的字符。我们这里的文件名称采用随机的120字符,然后加上原本文件的后缀组成,这里可以防止本地存储位置的文件有重复。在代码中有各个步骤的详细注解,大家可以查看代码中的信息:

    # 生成120位随机id
    def gengenerateFno():
        re = ""
        for i in range(120):
            re += chr(random.randint(65, 90))
        return re
    
    # 资源上传页面
    @app.route('/post_file',methods=['GET','POST'])
    @login_limit
    def post_file():
        if request.method == 'GET':
            return render_template('post_file.html')
        if request.method == 'POST':
            email = session.get('email')
            upload_file = request.files.get('file')
            filename = request.form.get('filename')
            file_info = request.form.get('file_info')
            file_path = 'store'
            file_time = time.strftime("%Y-%m-%d %H:%M:%S")
            Fno = gengenerateFno()
            try:
                cur = db.cursor()
                sql = "select * from Files where Fno = '%s'" % Fno
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                # 如果result不为空,即该Fno已存在时,一直生成随机的Fno,只到该数据库中不存在
                while result is not None:
                    Fno = gengenerateFno()
                    sql = "select * from Files where Fno = '%s'" % Fno
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    result = cur.fetchone()
                # 获取文件的后缀
                upload_name = str(upload_file.filename)
                houzhui = upload_name.split('.')[-1]
                # 保存在本地的名字为生成的Fno+文件后缀,同时修改Fno的值
                Fno = Fno+"."+houzhui
                # 保存文件到我们的服务器中
                upload_file.save(os.path.join(file_path,Fno))
                # 将文件信息存储到数据库中
                sql = "insert into Files(Fno, filename, file_info, file_time,email) VALUES ('%s','%s','%s','%s','%s')" % (Fno,filename,file_info,file_time,email)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return render_template('index.html')
            except Exception as e:
                raise e
    

      这样我们就可以将我们的文件上传到我们是服务器端,并且保存到我们制定的文件夹中了。

    8.2 资源专区功能实现

      下面我们开始来实现我们的资源专区,这里的资源专区我们用来显示我们的所有的资源的一个列表,并且可以进行在线查看资源和下载资源。

    8.2.1 资源专区功能实现-后端

      资源专区的话,我们还是先实现后端,发送数据到前端,然后我们前端将接收到的数据进行一个排版,然后显示。大家有没有发现一个问题,我们刚刚先实现了资源的上传,但是我们并没有导航,那为什么我们要先实现资源上传呢?因为如果我们先实现资源专区的话,没有东西显示,用于测试啊~所以我们先实现资源上传,可以先上传文件,便于资源专区的实现。
      这里我们需要返回到前端的数据有Fno、文件名、文件描述、创建时间和创建人的昵称。

    # 资源专区
    @app.route('/source')
    def source():
        if request.method == 'GET':
            try:
                cur = db.cursor()
                sql = "select Fno,filename,file_info,file_time,nickname from Files,UserInformation where Files.email = UserInformation.email"
                db.ping(reconnect=True)
                cur.execute(sql)
                files = cur.fetchall()
                cur.close()
                return render_template('source.html',files = files)
            except Exception as e:
                raise e
    

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    8.2.2资源专区功能实现-前端

      资源专区的前端我们首先需要一个列表用来展示我们的服务器中已上传的文件的信息,并且导航到我们的文件在线查看,文件下载等功能。这里使用source.html用来实现我们的前端功能,使用source.css存放样式的描述信息。页面效果为:在这里插入图片描述
    在这里由于我们的在线查看文件和下载文件还没有实现,所有我们这里的超链接的地址是#,等下我们来进行实现。目前的前端的页面代码为:

    {% extends 'base.html' %}
    
    {% block title %}
    资源专区
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/source.css">
    {% endblock %}
    
    {% block content %}
    <div class="source_content">
        <div class="page-header" id="page_header">
          <h1>资源专区<small>Resources Zone</small></h1>
        </div>
        <div class="source_div">
            <ul class="source_ul">
                {% for file in files %}
                    <li class="issue_list_li">
                        <div class="issue_div">
                            <div class="issue_content">
                                <h3>
                                    <a href="#">
                                        {{ file[1] }}
                                    </a>
                                </h3>
                                <article>
                                    {{ file[2] }}
                                </article>
                            </div>
                            <div class="author_info">
                                <p class="post-info">
                                    <span>上传者:{{ file[4] }}</span>&emsp;&emsp;
                                    <span>上传时间:{{ file[3] }}</span>&emsp;
                                    <span><a href="#">在线查看</a></span>&emsp;
                                    <span><a href="#">下载文件</a></span>
                                </p>
                            </div>
                        </div>
                    </li>
                {% endfor %}
            </ul>
        </div>
    </div>
    {% endblock %}
    {% block source_class %}
    active
    {% endblock %}
    

    source.css:

    .source_content{
        margin-right: 20%;
        margin-left: 20%;
        margin-top: 8%;
    }
    
    .author_info{
        text-align: right;
    }
    
    .source_ul{
        list-style-type: none;
        margin-left: 0;
        padding-left: 0;
    }
    #page_header{
        text-align: center;
    }
    

      我们在base.html中添加了资源专区的导航,并且添加了source_class的block,如果不修改base.html的话,需要将source.html的最后3行删除,不然会报错。base中插入的代码为<li class="{% block source_class %}{% endblock %}"><a href="{{ url_for('source') }}">资源专区</a></li>插入在论坛导航的下面一行。

    8.2.3 实现文件在线查看功能

      其实这里面要想实现文件的在线查看功能非常的容易实现,我们只需要返回这个文件即可。我们这里需要在路由处传递一个参数Fno,用来寻找我们的文件。

    # 在线查看文件
    @app.route('/online_file/<Fno>')
    def online_file(Fno):
        return send_from_directory(os.path.join('store'), Fno)
    

    8.2.4 实现文件下载功能

      我们再来实现我们的文件下载功能,这个也是非常简单的,我们直接返回一个send_file即可实现文件的下载。

    # 文件下载功能
    @app.route('/download/<Fno')
    def download(Fno):
        return send_file(os.path.join('store') + "/" + Fno, as_attachment=True)
    

    在实现完我们的文件在线查看和文件下载之后,我们就可以修改我们的资源专区的前端,为其超链接添加一个正确的链接,让他能够实现正确的功能了。修改后的souce.html页面为:

    {% extends 'base.html' %}
    
    {% block title %}
    资源专区
    {% endblock %}
    
    {% block css %}
    <link rel="stylesheet" href="/static/css/source.css">
    {% endblock %}
    
    {% block content %}
    <div class="source_content">
        <div class="page-header" id="page_header">
          <h1>资源专区<small>Resources Zone</small></h1>
        </div>
        <div class="source_div">
            <ul class="source_ul">
                {% for file in files %}
                    <li class="issue_list_li">
                        <div class="issue_div">
                            <div class="issue_content">
                                <h3>
                                    <a href="{{ url_for('online_file',Fno = file[0]) }}">
                                        {{ file[1] }}
                                    </a>
                                </h3>
                                <article>
                                    {{ file[2] }}
                                </article>
                            </div>
                            <div class="author_info">
                                <p class="post-info">
                                    <span>上传者:{{ file[4] }}</span>&emsp;&emsp;
                                    <span>上传时间:{{ file[3] }}</span>&emsp;
                                    <span><a href="{{ url_for('online_file',Fno = file[0]) }}">在线查看</a></span>&emsp;
                                    <span><a href="{{ url_for('download',Fno = file[0]) }}">下载文件</a></span>
                                </p>
                            </div>
                        </div>
                    </li>
                {% endfor %}
            </ul>
        </div>
    </div>
    {% endblock %}
    {% block source_class %}
    active
    {% endblock %}
    

     &emps;然后我们这里再为我们上传资源的页面添加一个导航,由于我们上传资源的功能只有登录的用户才可以进行使用,所以我们在导航栏中添加,当我们登录时,显示上传资源的导航,我们在base.html中添加:(加在资源专区后面即可)

    {% if email %}
    	<li class="{% block post_file_class %}{% endblock %}"><a href="{{ url_for('post_file') }}">上传资源</a></li>
    {% endif %}
    

    为了防止我们有的小伙伴不知道插入到哪里,我们这里再贴一下目前base.html整体的代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>
            {% block title %}
    {#        其他页面重写标题的地方#}
            {% endblock %}
        </title>
        {% block css %}
    {#    其他页面引用样式或者js的地方#}
        {% endblock %}
        <link rel="stylesheet" href="/static/css/base.css">
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
        <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="navigation_bar">
            <nav class="navbar navbar-default">
              <div class="container-fluid">
    {#              由于这里我们不需要使用商标,所以对Bran部分进行了删除#}
                <!-- 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="{% block index_class %}{% endblock %}"><a href="{{ url_for('index') }}">首页<span class="sr-only">(current)</span></a></li>
                    <li class="{% block formula_class %}{% endblock %}"><a href="{{ url_for('formula') }}">论坛</a></li>
                    <li class="{% block source_class %}{% endblock %}"><a href="{{ url_for('source') }}">资源专区</a></li>
                    {% if email %}
                        <li class="{% block post_file_class %}{% endblock %}"><a href="{{ url_for('post_file') }}">上传资源</a></li>
                    {% endif %}
                  </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 email %}
                        <li class="{% block post_issue_class %}{% endblock %}"><a href="{{ url_for('post_issue') }}">发布帖子</a></li>
                        <li class=""><a href="{{ url_for('register') }}">注销</a></li>
                        <li class="dropdown">
                          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
                          <ul class="dropdown-menu">
                            <li><a href="{{ url_for('personal') }}">个人中心</a></li>
                            <li><a href="{{ url_for('change_password') }}">修改密码</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="{{ url_for('show_issue') }}">已发布的帖子</a></li>
                          </ul>
                        </li>
                    {% else %}
                        <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注册</a></li>
                        <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登录</a></li>
                    {% endif %}
                  </ul>
                </div><!-- /.navbar-collapse -->
              </div><!-- /.container-fluid -->
            </nav>
        </div>
        <div class="content" style="padding: 0;margin: 0;">
            {% block content %}
    {#        其他页面重写页面内容的地方#}
            {% endblock %}
        </div>
    </body>
    </html>
    

      然后我们app.py中的返回值也可以进行一个适当的优化,例如我们上传完资源之后可以重定向到我们的资源专区,而不是一开始的index(因为当时还没有资源专区)。所以这里也贴一下我们app.py的全部代码,给小伙伴们进行一个参考

    from flask import *
    from werkzeug.security import generate_password_hash, check_password_hash
    from werkzeug.utils import secure_filename
    from config import db
    import random
    import time
    import config
    import os
    import re
    from decorators import login_limit
    
    app = Flask(__name__)
    
    # 从对象中导入config
    app.config.from_object(config)
    
    
    # 登录状态保持
    @app.context_processor
    def login_status():
        # 从session中获取email
        email = session.get('email')
        # 如果有email信息,则证明已经登录了,我们从数据库中获取登陆者的昵称和用户类型,来返回到全局
        if email:
            try:
                cur = db.cursor()
                sql = "select nickname,type from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result:
                    return {'email': email, 'nickname': result[0], 'user_type': result[1]}
            except Exception as e:
                raise e
        # 如果email信息不存在,则未登录,返回空
        return {}
    
    
    # 主页
    @app.route('/')
    def index():
        return render_template('index.html')
    
    
    # 注册页面
    @app.route('/register', methods=['GET', 'POST'])
    def register():
        if request.method == 'GET':
            return render_template('register.html')
        if request.method == 'POST':
            email = request.form.get('email')
            nickname = request.form.get('nickname')
            password_1 = request.form.get('password_1')
            password_2 = request.form.get('password_2')
            phone = request.form.get('phone')
            if not all([email, nickname, password_1, password_2, phone]):
                flash("信息填写不全,请将信息填写完整")
                return render_template('register.html')
            if password_1 != password_2:
                flash("两次密码填写不一致!")
                return render_template('register.html')
            password = generate_password_hash(password_1, method="pbkdf2:sha256", salt_length=8)
            try:
                cur = db.cursor()
                sql = "select * from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result is not None:
                    flash("该Email已存在!")
                    return render_template('register.html')
                else:
                    create_time = time.strftime("%Y-%m-%d %H:%M:%S")
                    sql = "insert into UserInformation(email, nickname, password, type, create_time, phone) VALUES ('%s','%s','%s','0','%s','%s')" % (
                        email, nickname, password, create_time, phone)
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    db.commit()
                    cur.close()
                    return redirect(url_for('index'))
            except Exception as e:
                raise e
    
    
    # 登录页面
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'GET':
            return render_template('login.html')
        if request.method == 'POST':
            email = request.form.get('email')
            password = request.form.get('password')
            if not all([email, password]):
                flash("请将信息填写完整!")
                return render_template('login.html')
            try:
                cur = db.cursor()
                sql = "select password from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                if result is None:
                    flash("该用户不存在")
                    return render_template('login.html')
                if check_password_hash(result[0], password):
                    session['email'] = email
                    session.permanent = True
                    cur.close()
                    return redirect(url_for('index'))
                else:
                    flash("密码错误!")
                    return render_template('login.html')
            except Exception as e:
                raise e
    
    
    # 用户注销
    @app.route('/logout')
    def logout():
        session.clear()
        return redirect(url_for(('index')))
    
    
    # 生成128随机id
    def gengenerateID():
        re = ""
        for i in range(128):
            re += chr(random.randint(65, 90))
        return re
    
    
    # 发布帖子
    @app.route('/post_issue', methods=['GET', 'POST'])
    @login_limit
    def post_issue():
        if request.method == 'GET':
            return render_template('post_issue.html')
        if request.method == 'POST':
            title = request.form.get('title')
            comment = request.form.get('editorValue')
            email = session.get('email')
            issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
            try:
                cur = db.cursor()
                Ino = gengenerateID()
                sql = "select * from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                # 如果result不为空,即存在该ID,就一直生成128位随机ID,直到不重复位置
                while result is not None:
                    Ino = gengenerateID()
                    sql = "select * from Issue where Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    result = cur.fetchone()
                sql = "insert into Issue(Ino, email, title, issue_time) VALUES ('%s','%s','%s','%s')" % (
                    Ino, email, title, issue_time)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                    '1', Ino, comment, issue_time, email)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('formula'))
            except Exception as e:
                raise e
    
    
    # 论坛页面
    @app.route('/formula')
    def formula():
        if request.method == 'GET':
            try:
                cur = db.cursor()
                sql = "select Issue.Ino, Issue.email,UserInformation.nickname,issue_time,Issue.title,Comment.comment from Issue,UserInformation,Comment where Issue.email = UserInformation.email and Issue.Ino = Comment.Ino and Cno = '1' order by issue_time DESC "
                db.ping(reconnect=True)
                cur.execute(sql)
                issue_information = cur.fetchall()
                cur.close()
                return render_template('formula.html', issue_information=issue_information)
            except Exception as e:
                raise e
    
    
    # 问题详情
    @app.route('/issue/<Ino>', methods=['GET', 'POST'])
    @login_limit
    def issue_detail(Ino):
        if request.method == 'GET':
            try:
                if request.method == 'GET':
                    cur = db.cursor()
                    sql = "select Issue.title from Issue where Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    # 这里返回的是一个列表,即使只有一个数据,所以这里使用cur.fetchone()[0]
                    issue_title = cur.fetchone()[0]
                    sql = "select UserInformation.nickname,Comment.comment,Comment.comment_time,Comment.Cno from Comment,UserInformation where Comment.email = UserInformation.email and Ino = '%s'" % Ino
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    comment = cur.fetchall()
                    cur.close()
                    # 返回视图,同时传递参数
                    return render_template('issue_detail.html', Ino=Ino, issue_title=issue_title, comment=comment)
            except Exception as e:
                raise e
    
        if request.method == 'POST':
            Ino = request.values.get('Ino')
            email = session.get('email')
            comment = request.values.get('editorValue')
            comment_time = time.strftime("%Y-%m-%d %H:%M:%S")
            try:
                cur = db.cursor()
                sql = "select max(Cno) from Comment where Ino = '%s' " % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                Cno = int(result[0]) + 1
                Cno = str(Cno)
                sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                Cno, Ino, comment, comment_time, email)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('issue_detail',Ino = Ino))
            except Exception as e:
                raise e
    
    
    # 个人中心
    @app.route('/personal')
    @login_limit
    def personal():
        if request.method == 'GET':
            email = session.get('email')
            try:
                cur = db.cursor()
                sql = "select email, nickname, type, create_time, phone from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                personal_info = cur.fetchone()
            except Exception as e:
                raise e
            return render_template('personal.html',personal_info = personal_info)
    
    
    # 修改密码
    @app.route('/change_password',methods=['GET','POST'])
    @login_limit
    def change_password():
        if request.method == 'GET':
            return render_template('change_password.html')
        if request.method == 'POST':
            old_password = request.form.get('old_password')
            new_password1 = request.form.get('new_password1')
            new_password2 = request.form.get('new_password2')
            if not all([old_password,new_password1,new_password2]):
                flash("信息填写不全!")
                return render_template('change_password.html')
            if new_password1 != new_password2:
                flash("两次新密码不一致!")
                return render_template('change_password.html')
            email = session.get('email')
            try:
                cur = db.cursor()
                sql = "select password from UserInformation where email = '%s'" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                password = cur.fetchone()[0]
                if check_password_hash(password,old_password):
                    password = generate_password_hash(new_password1, method="pbkdf2:sha256", salt_length=8)
                    sql = "update UserInformation set password = '%s' where email = '%s'" % (password,email)
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    db.commit()
                    cur.close()
                    return render_template('index.html')
                else:
                    flash("旧密码错误!")
                    return render_template('change_password.html')
            except Exception as e:
                raise e
    
    # 查看已发布的帖子
    @app.route('/show_issue')
    @login_limit
    def show_issue():
        if request.method == 'GET':
            email = session.get('email')
            try:
                cur = db.cursor()
                sql = "select ino, email, title, issue_time from Issue where email = '%s' order by issue_time desc" % email
                db.ping(reconnect=True)
                cur.execute(sql)
                issue_detail = cur.fetchall()
            except Exception as e:
                raise e
            return render_template('show_issue.html',issue_detail=issue_detail)
    
    # 生成120位随机id
    def gengenerateFno():
        re = ""
        for i in range(120):
            re += chr(random.randint(65, 90))
        return re
    
    # 资源上传页面
    @app.route('/post_file',methods=['GET','POST'])
    @login_limit
    def post_file():
        if request.method == 'GET':
            return render_template('post_file.html')
        if request.method == 'POST':
            email = session.get('email')
            upload_file = request.files.get('file')
            filename = request.form.get('filename')
            file_info = request.form.get('file_info')
            file_path = 'store'
            file_time = time.strftime("%Y-%m-%d %H:%M:%S")
            Fno = gengenerateFno()
            try:
                cur = db.cursor()
                sql = "select * from Files where Fno = '%s'" % Fno
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
                # 如果result不为空,即该Fno已存在时,一直生成随机的Fno,只到该数据库中不存在
                while result is not None:
                    Fno = gengenerateFno()
                    sql = "select * from Files where Fno = '%s'" % Fno
                    db.ping(reconnect=True)
                    cur.execute(sql)
                    result = cur.fetchone()
                # 获取文件的后缀
                upload_name = str(upload_file.filename)
                houzhui = upload_name.split('.')[-1]
                # 保存在本地的名字为生成的Fno+文件后缀,同时修改Fno的值
                Fno = Fno+"."+houzhui
                # 保存文件到我们的服务器中
                upload_file.save(os.path.join(file_path,Fno))
                # 将文件信息存储到数据库中
                sql = "insert into Files(Fno, filename, file_info, file_time,email) VALUES ('%s','%s','%s','%s','%s')" % (Fno,filename,file_info,file_time,email)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('source'))
            except Exception as e:
                raise e
    
    # 资源专区
    @app.route('/source')
    def source():
        if request.method == 'GET':
            try:
                cur = db.cursor()
                sql = "select Fno,filename,file_info,file_time,nickname from Files,UserInformation where Files.email = UserInformation.email"
                db.ping(reconnect=True)
                cur.execute(sql)
                files = cur.fetchall()
                cur.close()
                return render_template('source.html',files = files)
            except Exception as e:
                raise e
    
    # 在线查看文件
    @app.route('/online_file/<Fno>')
    def online_file(Fno):
        return send_from_directory(os.path.join('store'), Fno)
    
    # 文件下载功能
    @app.route('/download/<Fno>')
    def download(Fno):
        return send_file(os.path.join('store') + "/" + Fno, as_attachment=True)
    
    if __name__ == '__main__':
        app.run()
    

      目前我们的项目35583行代码了,这里面其实有很多代码是我们引入的插件的代码和我们富文本编辑器的代码,我们把它去掉,我们纯手动码的代码也有3982行代码了,也算一个五脏俱全的小项目了,(到这里,博主已经码字108256个字了)。
      好了,现在小伙伴们可以去测试自己的论坛系统啦!现在已经完成了,我们可以发布问题并且显示出来,还可以对帖子进行回复,查看帖子详情,个人中心,资源专区了,现在小小的在线论坛系统已经五脏俱全啦!(虽然设施简陋了点),同时我们还实现了我们的访问过滤功能,不然非法用户访问我们的页面,在这一讲中我们实现了我们个人中心页面的各个功能~小伙伴们都顺利实现了吗?如果有问题可以在评论区里进行问哦,如果喜欢这篇文章,可以点赞关注支持下博主。
      博主的更多文章导航可以查看我的栈内导航文章,里有各个文章的超链接,实时更新。博客文章内容导航(实时更新)


    九、项目迁移

      现在来到我们实战系列的最后一个部分了,项目迁移的一些内容,在之前的八个部分里,我们已经完成了在线论坛系统的导航条、登录、注册、论坛页面、帖子详情页面、个人页面、修改密码、查看已发布帖子、资源专区、文件上传下载等的功能实现,目前我们的论坛已经可以进行帖子发布,查看帖子详情,并且进行回复信息了、资源的上传下载,各个页面的流量过滤。下面我们继续来完善我们的这个在线论坛系统,今天我们来继续完善我们的系统,今天我们来实现一些我们如何保存我们系统的一些插件信息和数据库信息,如何对项目进行迁移。

    9.1 生成插件文件版本

      我们项目中一般都插入了很多不同的插件,我们如果要将项目部署到其他的机器上,我们就需要知道我们安装了哪些插件,并且在迁移的机器上进行安装这些插件,这里我们可以使用pip3将我们项目中使用的插件版本进行导出,可以导出到我们的requirments.txt文件中,也可以自行选择;

    pip3 freeze >requirements.txt
    

    这里导出的我们目前这个项目所使用的插件信息有:

    click==7.1.1
    Flask==1.1.1
    itsdangerous==1.1.0
    Jinja2==2.11.1
    MarkupSafe==1.1.1
    PyMySQL==0.9.3
    Werkzeug==1.0.0
    

    随着我们项目功能的增加,用的是插件也会逐渐的增加,手动安装费时费力。在导出到requirements.txt之后,我们就可以在迁移的机器上使用pip3进行安装相对应版本的插件,安装的命令为;

    pip3 install -r requirements.txt
    

    这样我们就可以在迁移的机器中安装和我们开发环境中一致的一个插件了。

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    9.2生成数据库表结构和数据库数据迁移

      我们前面插件是完成迁移了,可是我们没数据库也没法运行我们的项目啊,我们项目的很多数据都在我们的数据库中,下面我们就来生成我们数据库的表结构,首先我们得创建一个数据库,和我们项目中的名称一致,然后我们可以是PyCharm中的Database的SQL Generate进行生成我们数据库的表结构:在这里插入图片描述
    在这里插入图片描述
      然后我们直接将右面的SQL语句进行保存就好啦,这里我们可以选择上面自带的保存为文件的功能,也可以在migrate中创建一个文件用来存储我们的SQL语句,这里我使用的db.sql用来存储,然后把右面的代码复制进去就好了。

      现在我们数据库表的结构也建立完成了,下面我们就需要来生成我们数据库内数据的一个插入语句,将我们数据内的数据进行迁移啦。这里还是使用我们的PyCharm进行自动的生成Insert语句(强大的PyCharm):在这里插入图片描述
      我们选择合适的一个文件夹生成,点击之后就会自动生成我们各个表数据的一个insert语句,我们到迁移的机器上将这些语句运行就好了。
      到这里我们就已经生成了我们的项目中所有插件的版本信息和数据库的迁移语句啦。(这里如果使用Flask-SQLAlchemy进行使用数据库的话,也有其他的方法进行迁移)。


    到这里为止,我们本系列《Python Web全栈开发入门实战教程教程》也就到此结束了,全文11万字多一点,本篇博文送给那些想入门Web开发而又不知所措的小伙伴们,希望对你们能够有所帮助,带你们打开Web开发的大门。进入大门之后的世界就需要你们自己去闯荡啦!

    在最后再插播一次自己简介:

    大家好,我叫亓官劼(qí guān jié ),在CSDN这个大家庭中写写博客,博主的更多文章导航可以查看我的栈内导航文章,里有各个文章的超链接,实时更新。博客文章内容导航(实时更新)

    如果喜欢博主的话,可以点赞、关注,支持下博主
    祝大家一切顺利!时光荏苒,未来可期!

      本文原创为CSDN博主亓官劼,原文链接为:收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!,请大家支持原创,拒绝抄袭。

    展开全文
  • Python全栈

    2018-07-20 10:54:38
    Python全栈的技术学习资源,视频+源码配套齐全,便于理解
  • python全栈

    2017-03-22 11:27:33
    python全栈之路:正在更新

    python全栈之路:正在更新

    转载自https://blog.ansheng.me/article/python-full-stack-way/
    作者:安生

    展开全文
  • IT行业,技术要比学历、年龄、从业经验更为重要,技术水平直接决定就业薪资,想要学好python,首先要先了解精通Python语言基础、Python web开发、Python爬虫、Python数据分析这四大方面。 全栈即指的是全栈工程师,...
  • Python 全栈9期

    2018-08-12 20:37:17
    Python 全栈第9期,懂的进 Python 全栈第9期,懂的进 Python 全栈第9期,懂的进 Python 全栈第9期,懂的进 Python 全栈第9期,懂的进 Python 全栈第9期,懂的进
  • python全栈工程师

    万人学习 2018-04-08 11:34:05
    您观看课程学习后 可入群领取【超全Python资料包+17本学习电子书】 本课程内容由浅入深详细讲解了Python整套开发流程,包含了:Python基础、Python Web开发、Python爬虫...四个月的学习,成就真正的Python全栈工程师。
  • Python全栈9期(第一部分):基础+模块+面向对象+网络编程 Python全栈9期(第二部分):并发编程+数据库+前端 Python全栈9期(第三部分):django基础 Python全栈9期(第四部分):django进阶 Python全栈9期(第五...
  • Python全栈9期(第一部分):基础+模块+面向对象+网络编程 Python全栈9期(第二部分):并发编程+数据库+前端 Python全栈9期(第三部分):django基础 Python全栈9期(第四部分):django进阶 Python全栈9期(第五...
  • Python全栈9期(第一部分):基础+模块+面向对象+网络编程 Python全栈9期(第二部分):并发编程+数据库+前端 Python全栈9期(第三部分):django基础 Python全栈9期(第四部分):django进阶 Python全栈9期(第五...
  • Python全栈目录

    2019-10-02 08:25:36
    day01:01 python全栈s3 day1 计算机发展史.mp402 python全栈s3 day1 计算机系统.mp403 python全栈s3 day1 小结.ev4.mp404 python全栈s3 day1 数据的概念.ev4.mp405 python全栈s3 day1 进制转换.ev4.mp406 python全栈...

    day01:
    01 python全栈s3 day1 计算机发展史.mp4
    02 python全栈s3 day1 计算机系统.mp4
    03 python全栈s3 day1 小结.ev4.mp4
    04 python全栈s3 day1 数据的概念.ev4.mp4
    05 python全栈s3 day1 进制转换.ev4.mp4
    06 python全栈s3 day1 原码补码反码.ev4.mp4
    07 python全栈s3 day1 物理层和数据链路层.ev4.mp4
    08 python全栈s3 day1 网络层和arp协议.ev4.mp4
    09 python全栈s3 day1 传输层和应用层.mp4

    day02:
    01 python全栈s3 day2 上节课复习.ev4.mp4
    02 python全栈s3 day2 arp协议复习.ev4.mp4
    03 python全栈s3 day2 字符编码.mp4

    day03:
    01 python全栈s3 day3  网络基础和dos命令.ev4.mp4
    02 python全栈s3 day3  为何学习linux.ev4.mp4
    03 python全栈s3 day3  课程内容介绍.ev4.mp4
    04 python全栈s3 day3  操作系统内核与系统调用.ev4.mp4
    05 python全栈s3 day3  操作系统安装原理.ev4.mp4
    06 python全栈s3 day3  linux操作系统安装part1.ev4.mp4
    07 python全栈s3 day3  linux操作系统安装part2.ev4.mp4
    08 python全栈s3 day3  初识linux命令.ev4.mp4
    09 python全栈s3 day3  linux操作系统目录结构.ev4.mp4
    10 python全栈s3 day3  目录及文件操作.ev4.mp4

    day04:
    01 python全栈s3 day4  上节课复习.ev4.mp4
    02 python全栈s3 day4  创建用户相关的文件.ev4.mp4
    03 python全栈s3 day4  用户增删该查及组相关操作.ev4.mp4
    04 python全栈s3 day4  对文件的权限管理.ev4.mp4
    05 python全栈s3 day4  对目录的权限管理.ev4.mp4
    06 python全栈s3 day4  权限管理补充..mp4
    07 python全栈s3 day4  属主属组及基于数字的权限管理.ev4.mp4

    day05:
    01 python全栈s3 day5  上节课复习.ev4.mp4
    02 python全栈s3 day5  文件合并与文件归档.ev4.mp4
    03 python全栈s3 day5  文件归档与两种压缩方式.ev4.mp4
    04 python全栈s3 day5  vim编辑器.ev4.mp4
    05 python全栈s3 day5  系统启动流程.ev4.mp4
    06 python全栈s3 day5  grub加密.ev4.mp4
    07 python全栈s3 day5  bios加密.ev4.mp4
    08 python全栈s3 day5  top命令.ev4.mp4
    09 python全栈s3 day5  free命令.ev4.mp4
    10 python全栈s3 day5  进程管理.ev4.mp4

    day06:
    01 python全栈s3 day6  上节课复习.ev4(可能是加密过的文件,请核实).mp4
    02 python全栈s3 day6  磁盘分区part1.ev4.mp4
    03 python全栈s3 day6  磁盘分区part2.ev4.mp4
    04 python全栈s3 day6  文件系统与挂载.ev4.mp4
    05 python全栈s3 day6 挂载信息讲解.ev4.mp4
    06 python全栈s3 day6 磁盘用满的两种情况.ev4.mp4
    07 python全栈s3 day6 软连接和硬链接.ev4.mp4
    08 python全栈s3 day6 软连接和硬链接补充.ev4.mp4

    day07:
    01 python全栈s3 day7  ip地址与子网划分.ev4.mp4
    02 python全栈s3 day7  ip地址配置.ev4.mp4
    03 python全栈s3 day7  虚拟机网络模式.ev4.mp4
    04 python全栈s3 day7  三层隔离验证试验.ev4.mp4

    day08:
    01 python全栈s3 day8  上节课复习.ev4.mp4
    02 python全栈s3 day8  软件包介绍.ev4.mp4
    03 python全栈s3 day8  rpm软件包管理part1.ev4.mp4
    04 python全栈s3 day8  rpm软件包管理part2.ev4.mp4
    05 python全栈s3 day8  yum软件包管理part1.ev4.mp4
    06 python全栈s3 day8  yum软件包管理part2.ev4.mp4
    07 python全栈s3 day8  yum软件包管理part3.ev4.mp4
    08 python全栈s3 day8  源码安装python3.5.ev4.mp4
    09 python全栈s3 day8  ssh服务.ev4.mp4
    10 python全栈s3 day8  apache服务.ev4.mp4
    11 python全栈s3 day8  samba服务part1.ev4.mp4
    12 python全栈s3 day8  samba服务part2.ev4.mp4

    day09:
    01 python全栈s3 day9  Python开发系列课程概要.ev4.mp4
    02 python全栈s3 day9  Python作业要求以及博客.ev4.mp4
    03 python全栈s3 day9  编程语言介绍.ev4.mp4
    04 python全栈s3 day9  Python种类介绍.ev4.mp4
    05 python全栈s3 day9  Python安装以及环境变量的操作(一).ev4.mp4
    06 python全栈s3 day9  Python安装以及环境变量的操作(二).ev4.mp4
    07 python全栈s3 day9  Python初识以及变量(一).ev4.mp4
    07 python全栈s3 day9  Python初识以及变量(二).ev4.mp4
    08 python全栈s3 day9  Python条件语句和基本数据类型(一).ev4.mp4
    08 python全栈s3 day9  Python条件语句和基本数据类型(二).ev4.mp4
    09 python全栈s3 day9  Python while循环语句以及练习题.ev4.mp4
    10 python全栈s3 day9  练习题讲解.ev4.mp4

    day10:
    01 python全栈s3 day10 上节内容回顾以及补充.ev4.mp4
    02 python全栈s3 day10  上周作业实现.ev4.mp4
    03 python全栈s3 day10  Pycharm的安装和使用.ev4.mp4
    04 python全栈s3 day10  Python 运算符(一).ev4.mp4
    04 python全栈s3 day10  Python 运算符(一).ev4.mp4.bdc-downloading
    05 python全栈s3 day10  Python 运算符(二).ev4.mp4
    06 python全栈s3 day10  Python 运算符以及总结.ev4.mp4
    07 python全栈s3 day10  Python 基本数据类型介绍.ev4.mp4
    08 python全栈s3 day10  Python 整形的魔法.ev4.mp4
    09 python全栈s3 day10  Python 字符串的魔法(一).ev4.mp4
    10 python全栈s3 day10  Python 字符串的魔法(二).ev4.mp4



    day11:
    01 python全栈s3 day11  Python 字符串的魔法(三).ev4.mp4
    02 python全栈s3 day11  Python 字符串的魔法(四).ev4.mp4
    03 python全栈s3 day11  Python 字符串的魔法(五).ev4.mp4
    04 python全栈s3 day11  Python 字符串的魔法(六).ev4.mp4
    05 python全栈s3 day11  Python 字符串的魔法(七).ev4.mp4
    06 python全栈s3 day11  Python 字符串的魔法(八).ev4.mp4
    07 python全栈s3 day11  Python 字符串的魔法(九).ev4.mp4
    08 python全栈s3 day11  Python range的用法以及练习.ev4.mp4
    09 python全栈s3 day11  Python 课上练习解释.ev4.mp4
    10 python全栈s3 day11  Python 基础知识练习题试题(一).ev4.mp4

    day12:
    01 python全栈s3 day12  今日内容介绍以及基础测试题答案讲解.ev4.mp4
    02 python全栈s3 day11  Python 列表的魔法(一).ev4.mp4
    03 python全栈s3 day11  Python 列表的魔法(二).ev4.mp4
    04 python全栈s3 day11  Python 列表的魔法(三).ev4.mp4
    05 python全栈s3 day11  Python 元组的魔法(一).ev4.mp4
    06 python全栈s3 day11  Python 元组的魔法(二).ev4.mp4
    07 python全栈s3 day11  Python 字典的魔法(一).ev4.mp4
    08 python全栈s3 day11  Python 错误更正:布尔值可以作为字典的key.ev4.mp4
    09 python全栈s3 day11  Python 字典的魔法(三).ev4.mp4
    10 python全栈s3 day11  Python 今日内容整理.ev4.mp4

    day13:
    oldboy1.ev4.mp4

    day14:
    01 python s3 day14 数据类型和变量总结.ev4.mp4
    02 python s3 day14 集合定义和基本操作方法.ev4.mp4
    03 python s3 day14 集合关系运算交,差,并集.ev4.mp4
    04 python s3 day14 集合的其他内置方法.ev4.mp4
    05 python s3 day14 集合补充.ev4.mp4
    06 python s3 day14 百分号字符串拼接.ev4.mp4
    07 python s3 day14 format字符串格式化.ev4.mp4
    08 python s3 day14 数学意义的函数与python中的函数.ev4.mp4
    09 python s3 day14 为何要有函数.ev4.mp4
    10 python s3 day14 函数返回值.ev4.mp4
    11 python s3 day14 可变长参数.ev4.mp4

    day15:
    01 python s3 day15 上节课复习.ev4.mp4
    02 python s3 day15 全局变量与局部变量part1.ev4.mp4
    03 python s3 day15 全局变量与局部变量part2.ev4.mp4
    04 python s3 day15 全局变量与局部变量part3.ev4.mp4
    05 python s3 day15 风湿理论之函数即变量.ev4.mp4
    06 python s3 day15 函数递归.ev4.mp4
    07 python s3 day15 函数递归补充.ev4.mp4

    day16:
    01 python s3 day16 上节课回顾.ev4.mp4
    02 python s3 day16 函数作用域.ev4.mp4
    03 python s3 day16 函数作用域补充.ev4.mp4
    04 python s3 day16 匿名函数.ev4.mp4
    05 python s3 day16 .匿名函数补充.ev4.mp4
    06 python s3 day16 函数式编程介绍.ev4.mp4
    07 python s3 day16 函数式编程尾递归调用优化.ev4.mp4
    08 python s3 day16 map函数.ev4.mp4
    09 python s3 day16 map函数filter函数part1.ev4.mp4
    10 python s3 day16 reduce函数.ev4.mp4
    11 python s3 day16 map reduce filter总结.ev4.mp4
    12 python s3 day16 内置函数part1.ev4.mp4

    day17:
    01 python s3 day17 课前吹牛.ev4.mp4
    02 python s3 day17 zip方法.ev4.mp4
    03 python s3 day17 max和min高级使用part1.ev4.mp4
    04 python s3 day17 max和min高级使用part2.ev4.mp4
    05 python s3 day17 其他内置函数.ev4.mp4
    06 python s3 day17 文件操作的其他模式.ev4.mp4
    day10

    day18:
    01 python s3 day18 上节课复习.ev4.mp4
    02 python s3 day18 文件处理b模式.ev4.mp4
    03 python s3 day18 文件操作的其他方法.ev4.mp4
    04 python s3 day18 文件seek方法补充.ev4.mp4
    05 python s3 day18 迭代器协议和for循环工作机制.ev4.mp4
    06 python s3 day18 迭代器补充.ev4.mp4
    07 python s3 day18 三元运算,列表解析,生成器表达式.ev4.mp4

    day19:
    01 python s3 day19 生成器函数.ev4.mp4
    02 python s3 day19 生成器函数的好处.ev4.mp4
    03 python s3 day19 母鸡下蛋的传说.ev4.mp4
    04 python s3 day19 生成器特性阐释.ev4.mp4
    05 python s3 day19 生产者消费者模型.ev4.mp4
    06 python s3 day19 第三次作业讲解一.ev4.mp4
    07 python s3 day19 第三次作业讲解二.ev4.mp4

    day20:
    01 python s3 day20 上节课回顾.ev4.mp4
    02 python s3 day20 装饰器基本理论(1).ev4.mp4
    03 python s3 day20 高阶函数使用.ev4.mp4
    04 python s3 day20 函数闭包.ev4.mp4
    05 python s3 day20 函数闭包装饰器基本实现.ev4.mp4
    06 python s3 day20 函数闭包加上返回值.ev4.mp4
    07 python s3 day20 函数闭包加上参数.ev4.mp4
    08 python s3 day20 函数闭包补充:解压序列.ev4.mp4
    09 python s3 day20 函数闭包为函数加上认证功能.ev4.mp4
    10 python s3 day20 函数闭包模拟session.ev4.mp4
    11 python s3 day20 函数闭包装饰器运行流程.ev4.mp4
    12 python s3 day20 函数闭包带参数装饰器.ev4.mp4

    day21:
    01 python s3 day21 查询功能part1.ev4.mp4
    02 python s3 day21 查询功能part2.ev4.mp4
    03 python s3 day21 修改功能.ev4.mp4
    04 python s3 day21 程序的解耦.ev4.mp4
    05 python s3 day21 module模块和包的介绍 .ev4.mp4
    06 python s3 day21 模块的执行以及__name__.ev4.mp4
    07 python s3 day21  关于模块的介绍.ev4.mp4
    08 python s3 day21 time时间模块.ev4.mp4
    09 python s3 day21 random模块.ev4.mp4

    day22:
    01 python s3 day22 模块的补充 .ev4.mp4
    02 python s3 day22 sys修改环境变量 .ev4.mp4
    03 python s3 day22 BASEDIR的介绍 .ev4.mp4
    04 python s3 day22 os模块的介绍 .ev4.mp4
    05 python s3 day22 sys模块的介绍 .ev4.mp4
    06 python s3 day22 json模块 .ev4.mp4
    07 python s3 day22 pickle模块 .ev4.mp4
    08 python s3 day22 shelve模块 .ev4.mp4
    09 python s3 day22 XML模块 .ev4.mp4
    10 python s3 day22 re模块简介 .ev4.mp4
    11 python s3 day22 re模块之元字符 .ev4.mp4

    day23:
    01 python s3 day23 re模块之转义字符 .ev4.mp4
    02 python s3 day23 re模块之分组 .ev4.mp4
    03 python s3 day23 re模块之方法 .ev4.mp4
    04 python s3 day23 re模块总结 .ev4.mp4
    05 python s3 day23 logging模块 .ev4.mp4
    06 python s3 day23 re模块补充 .ev4.mp4
    07 python s3 day23 configparse模块 .ev4.mp4
    08 python s3 day23 hashlib模块 .ev4.mp4
    09 python s3 day23 计算器作业以及思路 .ev4.mp4
    10 python s3 day23  模块导入补充.ev4.mp4

    day24:
    01 python s3 day24 面向对象设计part1.ev4.mp4
    02 python s3 day24 面向对象设计part2.ev4.mp4
    03 python s3 day24 类相关知识part1.ev4.mp4
    04 python s3 day24 对象相关知识.ev4.mp4
    05 python s3 day24 类属性增删改查.ev4.mp4
    06 python s3 day24 实例属性的增删改查.ev4.mp4
    07 python s3 day24 对象与实例属性.ev4.mp4
    08 python s3 day24 对象与实例属性补充.ev4.mp4
    09 python s3 day24 面向对象作业.ev4.mp4

    day25:
    01 python s3 day25 上节课回顾.ev4.mp4
    02 python s3 day25 静态属性.ev4.mp4
    03 python s3 day25 类方法.ev4.mp4
    04 python s3 day25 静态方法.ev4.mp4
    05 python s3 day25 小结.ev4.avi
    06 python s3 day25 组合.ev4.mp4
    07 python s3 day25 继承part1.ev4.mp4
    08 python s3 day25 接口继承与归一化设计.ev4.mp4
    09 python s3 day25 继承顺序之mro线性顺序列表.ev4.mp4
    10 python s3 day25 在python2中的继承顺序是什么.ev4.mp4
    11 python s3 day25  在子类中调用父类方法part1.ev4.mp4
    12 python s3 day25 super调用父类的方法.ev4.mp4
    13 python s3 day25 选择系统作业讲解.ev4.mp4

    day26:
    01 python s3 day26 学生自主复习.ev4.mp4
    02 python s3 day26 分享列表.ev4.mp4
    03 python s3 day26 多态.ev4.mp4
    04 python s3 day26 封装part1.ev4.mp4
    05 python s3 day26 封装part2.ev4.mp4
    06 python s3 day26  面向对象概念总结.ev4.mp4
    07 python s3 day26  反射part1.ev4.mp4
    08 python s3 day26 反射part2及动态导入模块.ev4.mp4
    09 python s3 day26 类的内置attr属性.ev4.mp4
    10 python s3 day26 类内置attr属性补充.ev4.mp4
    11 python s3 day26 继承的方式完成包装.ev4.mp4
    12 python s3 day26  组合的方式完成授权.ev4.mp4

    day27:
    01 python s3 day27 os模块复习.ev4.mp4
    02 python s3 day27 上节课复习.ev4.mp4
    03 python s3 day27 内置函数补充及getattribute.ev4.mp4
    04 python s3 day27 getattribue补充.ev4.mp4
    05 python s3 day27 item系列.ev4.mp4
    06 python s3 day27 str与repr.ev4.mp4
    07 python s3 day27 自定制format.ev4.mp4
    08 python s3 day27 slots属性.ev4.mp4
    09 python s3 day27 doc属性.ev4.mp4
    10 python s3 day27 module和class.ev4.mp4
    11 python s3 day27 析构方法.ev4.mp4
    12 python s3 day27 call方法.ev4.mp4
    13 python s3 day27 迭代器协议.ev4.mp4
    14 python s3 day27 迭代器协议实现斐波那契数列.ev4.mp4
    16 python s3 day27 描述符答疑.ev4.mp4
    17 python s3 day27  描述符优先级.ev4.mp4
    18 python s3 day27 软件开发规范.ev4.mp4
    19 python s3 day27 pycharm干的好事.ev4.mp4

    day28:
    01 python s3 day28 上节课复习.ev4.mp4
    02 python s3 day28 上下文管理协议part1.ev4.mp4
    03 python s3 day28 上下文管理协议part2.ev4.mp4
    04 python s3 day28 异常的构成简单了解.ev4.mp4
    05 python s3 day28 描述符应用part1.ev4.mp4
    06 python s3 day28 描述符应用part2.ev4.mp4
    07 python s3 day28 描述符应用part3.ev4.mp4
    08 python s3 day28 类的装饰器的基本原理.ev4.mp4
    09 python s3 day28 类的装饰器增强版.ev4.mp4
    10 python s3 day28 类的装饰器的应用.ev4.mp4
    11 python s3 day28 自定制property.ev4.mp4
    12 python s3 day28 自定制property流程分析.ev4.mp4
    13 python s3 day28 自定制property实现延迟计算功能.ev4.mp4
    14 python s3 day28 property补充.ev4.mp4
    15 python s3 day28  元类介绍.ev4.mp4
    16 python s3 day28  自定义元类.ev4.mp4
    函数复习.ev4.mp4
    文件操作复习.ev4.mp4

    day29:
    01 python s3 day29 上节课复习.ev4.mp4
    02 python s3 day29 什么是异常处理及异常处理的两种方式对比.ev4.mp4
    03 python s3 day29 多分支与万能异常.ev4.avi
    04 python s3 day29 异常处理的其他内容.ev4.mp4
    05 python s3 day29 什么时候用异常处理.ev4.mp4
    06 python s3 day29 什么是socket.ev4.mp4
    07 python s3 day29 套接字发展及分类.ev4.mp4
    08 python s3 day29 基于tcp协议的套接字编程.ev4.mp4
    09 python s3 day29 socket底层工作原理解释.ev4.mp4
    10 python s3 day29  tcp三次握手与四次挥手.ev4.mp4
    内置函数复习.ev4.mp4

    day30:
    01 python s3 day30 上节课复习.ev4.mp4
    02 python s3 day30 客户端服务端循环收发消息.ev4.mp4
    03 python s3 day30 socket收发消息原理剖析.ev4.mp4
    04 python s3 day30 服务端循环链接请求来收发消息.ev4.mp4
    05 python s3 day30 补充part1.ev4.avi
    06 python s3 day30 补充part2.ev4.mp4
    07 python s3 day30 udp套接字.ev4.mp4
    08 python s3 day30 recv与recvfrom的区别及基于udp实现ntp服务.ev4.mp4
    09 python s3 day30 基于tcp实现远程执行命令.ev4.mp4
    10 python s3 day30 基于tcp实现远程执行命令测试结果.ev4.mp4
    11 python s3 day30 粘包现象.ev4.mp4
    12 python s3 day30 粘包解决方法.ev4.mp4
    time复习.ev4.mp4

    day31:
    01 python s3 day31 上节课复习.ev4.mp4
    02 python s3 day31 socketserver实现并发.ev4.mp4
    03 python s3 day31 socketserver模块介绍.ev4.mp4
    04 python s3 day31 socketserver源码分析tcp版本.ev4.mp4
    05 python s3 day31 socketserver源码分析udp版.ev4.mp4
    06 python s3 day31 ftp作业要求讲解.ev4.mp4
    07 python s3 day31 补充:认证客户端链接合法性.ev4.mp4
    re复习下部分.ev4.mp4
    复习内置函数补充部分.ev4.mp4

    day32:
    01 python s3 day32 FTP之参数解析与命令分发.ev4.mp4
    02 python s3 day32 FTP之逻辑梳理.ev4.avi
    03 python s3 day32 FTP之验证功能1.ev4.mp4
    04 python s3 day32 FTP之验证功能2.ev4.mp4
    05 python s3 day32 FTP之文件上传.ev4.mp4
    06 python s3 day32 FTP之断点续传1.ev4.mp4
    07 python s3 day32 FTP之断点续传2.ev4.mp4
    08 python s3 day32 FTP之进度条.ev4.mp4
    09 python s3 day32 FTP之cd切换1.ev4.mp4
    10 python s3 day32 FTP之cd切换2.ev4.mp4
    11 python s3 day32 FTP之创建文件夹及MD5校验思路.ev4.mp4

    day33:
    01 python s3 day33 操作系统历史.ev4.mp4
    02 python s3 day33 进程的概念.ev4.avi
    03 python s3 day33  线程的概念.ev4.mp4
    04 python s3 day33  线程的调用以及join方法.ev4.mp4
    05 python s3 day33  setDaemon方法和继承式调用.ev4.baiduyun.downloading.mp4
    05 python s3 day33  setDaemon方法和继承式调用.ev4.mp4

    day34:
    01 python s3 day34  上节知识回顾.ev4.mp4
    02 python s3 day34  并发并行与同步异步的概念.ev4.mp4
    03 python s3 day34  GIL的概念.ev4.mp4
    04 python s3 day34  同步锁.ev4.mp4
    05 python s3 day34  递归锁.ev4.mp4
    06 python s3 day34  同步对象event.ev4.mp4
    07 python s3 day34  信号量.ev4.mp4
    08 python s3 day34  线程队列.ev4.mp4
    09 python s3 day34  生产者消费者模型.ev4.mp4
    10 python s3 day34 多进程的调用.ev4.mp4

    day35:
    01 python s3 day35 进程通信.ev4.mp4
    02 python s3 day35 进程池.ev4.mp4
    03 python s3 day35 协程.ev4.mp4
    04 python s3 day35 事件驱动模型.ev4.mp4
    05 python s3 day35 IO模型前戏.ev4.mp4
    06 python s3 day35 阻塞IO与非阻塞IO.ev4.mp4
    07 python s3 day35 select及触发方式.ev4.mp4
    08 python s3 day35 select监听多连接.ev4.mp4
    09 python s3 day35 select与epoll的实现区别.ev4.mp4

    day36:
    01 python s3 day36 异步IO.ev4.mp4
    02 python s3 day36 selectors模块介绍.ev4.mp4
    03 python s3 day36 selectors模块应用.ev4.mp4

    day37:
    01 python s3 day37 selctors实现文件上传与下载.ev4.mp4
    02 python s3 day37 html的介绍.ev4.mp4
    03 python s3 day37 html文档树的概念.ev4.mp4
    04 python s3 day37 meta标签以及一些基本标签.ev4.mp4
    05 python s3 day37 img标签和列表标签.ev4.mp4
    06 python s3 day37 form表单之input标签.ev4.mp4
    07 python s3 day37 通过form向server端发送数据.ev4.mp4
    08 python s3 day37 form表单之select标签.ev4.mp4
    09 python s3 day37 table标签.ev4.mp4

    day38:
    01 python s3 day38 css的四种引入方式.ev4.mp4
    02 python s3 day38 css的四种基本选择器.ev4.mp4
    03 python s3 day38 css的组合选择器.ev4.mp4
    04 python s3 day38 css的属性选择器.ev4.mp4
    05 python s3 day38 css的伪类.ev4.mp4
    06 python s3 day38 css的选择器优先级.ev4.mp4
    07 python s3 day38 css的背景属性.ev4.mp4

    day39:
    01 python s3 day39 css的文本属性与边框属性.ev4.mp4
    02 python s3 day39 css的列表属性与display属性.ev4.mp4
    03 python s3 day39 css的内外边距.ev4.mp4
    04 python s3 day39 css的内外边距补充.ev4.avi
    05 python s3 day39 css的float属性.ev4.mp4
    06 python s3 day39 css的清除浮动.ev4.mp4
    07 python s3 day39 css的定位.ev4.mp4
    08 python s3 day39 css的margin定位.ev4.mp4

    day40:
    01 python s3 day40 抽屉作业之head区域(导航条).ev4.mp4
    02 python s3 day40 抽屉作业之置顶区域.ev4.mp4
    03 python s3 day40 抽屉作业之content部分一.ev4.mp4
    04 python s3 day40 抽屉作业之content部分二.ev4.mp4
    05 python s3 day40 抽屉作业之页码部分.ev4.mp4
    06 python s3 day40 抽屉作业之footer部分.ev4.mp4

    day41:
    01 python s3 day41 JS的历史以及引入方式.ev4.mp4
    02 python s3 day41 JS的基础规范.ev4.mp4
    03 python s3 day41 JS的基本数据类型.ev4.mp4
    04 python s3 day41 JS的运算符.ev4.mp4
    05 python s3 day41 JS的控制语句与循环.ev4.mp4
    06 python s3 day41 JS的循环与异常.ev4.mp4
    07 python s3 day41 JS的字符串对象.ev4.mp4
    08 python s3 day41 JS的数组对象.ev4.mp4
    09 python s3 day41 JS的函数对象.ev4.mp4

    day42:
    01 python s3 day42 JS的函数作用域.ev4.avi
    02 python s3 day42 JS的window对象之定时器.ev4.mp4
    03 python s3 day42 JS的history对象和location对象.ev4.mp4
    04 python s3 day42 JS的DOM节点.ev4.mp4
    05 python s3 day42 JS的DOM节点.ev4.mp4

    day43:
    01 python s3 day43 上节知识回顾.ev4.mp4
    02 python s3 day43 js之onsubmit事件与组织事件外延.ev4.mp4
    03 python s3 day43 DOM节点的增删改查与属性设值.ev4.mp4
    04 python s3 day43 正反选练习.ev4.avi
    05 python s3 day43 js练习之二级联动.ev4.mp4
    06 python s3 day43 jquery以及jquery对象介绍.ev4.mp4
    07 python s3 day43 jquery选择器.ev4.mp4
    08 python s3 day43 jquery的查找筛选器.ev4.mp4
    09 python s3 day43 jquery练习之左侧菜单.ev4.mp4

    day44:
    01 python s3 day44  jquery属性操作之html,text,val方法.ev4.mp4
    02 python s3 day44  jquery循环方法和attr,prop方法.ev4.mp4
    03 python s3 day44  jquery模态对话框与clone的应用.ev4.mp4
    04 python s3 day44  jqueryCSS操作之offsets,position以及scrolltop.ev4.mp4
    05 python s3 day44  jquery事件绑定与事件委托.ev4.mp4
    06 python s3 day44  jquery动画效果.ev4.mp4
    07 python s3 day44  jquery扩展与插件.ev4.mp4
    08 python s3 day44  jquery扩展补充.ev4.mp4
    09 python s3 day44  本周作业轮播图以及思路.ev4.mp4

    day45:
    轮播图片css部分.ev4.mp4
    轮播图片js部分.ev4.mp4

    day46:
    01 python s3 day46  数据库与dbms的概念.ev4.mp4
    02 python s3 day46  sql规范.ev4.mp4
    03 python s3 day46  数据库操作DDL.ev4.mp4
    04 python s3 day46 mysql的数据类型.ev4.mp4
    05 python s3 day46  数据表操作.ev4.mp4
    06 python s3 day46  表记录之增删改操作.ev4.mp4
    07 python s3 day46  表记录查询之查询(一).ev4.mp4
    08 python s3 day46  表记录查询之查询(二).ev4.mp4

    day47:
    01 python s3 day47  多表查询之连接查询.ev4.mp4
    02 python s3 day47  级联删除与set null.ev4.mp4
    03 python s3 day47  多表查询之连接查询.ev4.mp4
    04 python s3 day47  多表查询之复合查询与子查询.ev4.mp4
    05 python s3 day47  mysql之索引.ev4.mp4

    day48:
    01 python s3 day48  python操作数据库pymysql.ev4.mp4
    02 python s3 day48  数据库之事务.ev4.mp4
    03 python s3 day48  mysql事务之savepoint.ev4.mp4

    day49:
    01 python s3 day49  http协议之请求协议.ev4.mp4
    02 python s3 day49  http协议之响应协议.ev4.mp4
    03 python s3 day49  web框架的概念.ev4.mp4
    04 python s3 day49   做一个最简答web框架.ev4.mp4
    05 python s3 day49  MVC模式和MTV模式.ev4.mp4
    06 python s3 day49  django的一个简单应用.ev4.mp4
    07 python s3 day49  django静态文件之static.ev4.mp4
    08 python s3 day49  django的url控制系统.ev4.mp4
    09 python s3 day49  django的urlConf补充.ev4.mp4

    day50:
    01 python s3 day50  django之视图函数的介绍.ev4.mp4
    02 python s3 day50  django视图之redirec.ev4.mp4
    03 python s3 day50  django模板之变量.ev4.mp4
    04 python s3 day50  django模板之过滤器.ev4.mp4
    05 python s3 day50  django模板之控制语句if和for循环.ev4.mp4
    06 python s3 day50  django模板之标签tag补充.ev4.mp4
    07 python s3 day50  django模板之自定义filter和simple_tag.ev4.mp4
    08 python s3 day50  django模板之继承标签extend和添加标签include.ev4.mp4

    day51:
    01 python s3 day51  数据库表与表之间的一对多多对多的关系.ev4.mp4
    02 python s3 day51  Django的ORM的概念.ev4.mp4
    03 python s3 day51  ORM对单表的增删改操作.ev4.mp4
    04 python s3 day51  ORM查询API.ev4.mp4
    05 python s3 day51  模糊查询之万能的双下换线.ev4.avi

    day52:
    01 python s3 day52  上节知识回顾.ev4.avi
    02 python s3 day52  ORM多表操作之一对多增加记录.ev4.mp4
    02 python s3 day52  ORM多表操作之一对多查询之对象查询.ev4.mp4
    04 python s3 day52  ORM多表操作之一对多查询之双下划线查询.ev4.mp4
    05 python s3 day52  ORM多表操作之多对多添加记录.ev4.mp4
    06 python s3 day52  ORM多表操作之多对多查询.ev4.mp4
    07 python s3 day52  ORM多表操作之F查询与Q查询.ev4.mp4
    08 python s3 day52  ORM的querySet集合对象的特性.ev4.mp4

    day53:
    01 python s3 day53  admin介绍.ev4.avi
    02 python s3 day53  alex首秀失败.ev4.mp4
    03 python s3 day53  自定义admin样式.ev4.mp4
    04 python s3 day53  admin补充.ev4.mp4
    05 python s3 day53  COOKIE介绍.ev4.mp4
    06 python s3 day53  COOKIE和SESSION配合使用.ev4.mp4

    day54:
    01 python全栈3 day54 今日内容概要 .ev4.avi
    02 python全栈3 day54 Django内容回顾(一).ev4.mp4
    03 python全栈3 day54 Django内容回顾(二).ev4.mp4
    04 python全栈3 day54 Django内容回顾(三).ev4.mp4
    05 python全栈3 day54 Django请求生命周期之Http请求.ev4.mp4
    06 python全栈3 day54 Django请求生命周期之FBV和CBV.ev4.mp4
    07 python全栈3 day54 Django请求生命周期之CBV扩展.ev4.mp4
    08 python全栈3 day54 瞎扯淡.ev4.mp4
    09 python全栈3 day54 Django请求生命周期之响应内容(一).ev4.mp4
    10 python全栈3 day54 Django请求生命周期之响应内容(二).ev4.mp4
    11 python全栈3 day54 学员管理示例:数据库设计.ev4.mp4
    12 python全栈3 day54 学员管理示例:班级管理.ev4.mp4
    13 python全栈3 day54 学员管理示例:学员管理.ev4.mp4

    day55:
    01 python全栈3 day55 Django的ORM基本操作补充之概要.ev4.mp4
    02 python全栈3 day55 Django的ORM基本操作补充之一对多(一).ev4.mp4
    03 python全栈3 day55 Django的ORM基本操作补充之一对多(二).ev4.mp4
    04 python全栈3 day55 学员管理示例:编辑学生.ev4.mp4
    05 python全栈3 day55 Django的ORM基本操作补充之多对多(一).ev4.mp4
    06 python全栈3 day55 Django的ORM基本操作补充之多对多(二).ev4.mp4
    07 python全栈3 day55 学员管理示例:为班级分配老师(一).ev4.mp4
    08 python全栈3 day55 学员管理示例:为班级分配老师(二).ev4.mp4
    09 python全栈3 day55 初识Ajax以及简单应用.ev4.mp4
    10 python全栈3 day55 学员管理示例:Ajax删除学生.ev4.mp4
    11 python全栈3 day55 本节作业以及内容补充.ev4.avi

    day56:
    01 python全栈3 day56 上节内容回顾.ev4.mp4
    02 python全栈3 day56 创建Project以及表结构.ev4.mp4
    03 python全栈3 day56 基于BootStrap和FontAwesome制作页面.ev4.mp4
    04 python全栈3 day56 创建学生信息(一).ev4.mp4
    05 python全栈3 day56 创建学生信息(二).ev4.mp4
    06 python全栈3 day56 删除学生信息.ev4.mp4

    day57:
    01 python全栈3 day57 上节内容回顾.ev4.mp4
    02 python全栈3 day57 上节bug修复.ev4.mp4
    03 python全栈3 day57 编辑学生信息之前端功能.ev4.mp4
    04 python全栈3 day57 编辑学生信息之后台处理.ev4.mp4
    05 python全栈3 day57 以上内容总结.ev4.avi
    06 python全栈3 day57 Ajax功能之dataType和traditional.ev4.mp4

    day58:
    01 python全栈3 day58 今日内容概要.ev4~1.avi
    02 python全栈3 day58 Ajax补充之serialize.ev4.mp4
    03 python全栈3 day58 分页功能介绍.ev4.mp4
    04 python全栈3 day58 分页基础知识.ev4.mp4
    05 python全栈3 day58 Django内置分页.ev4.mp4
    06 python全栈3 day58 扩展Django内置分页.ev4.mp4
    07 python全栈3 day58 自定义分页组件.ev4.mp4
    08 python全栈3 day58 DjangoForm组件初识.ev4.mp4

    day59:
    01 python全栈3 day59 Form组件之生成HTML标签.ev4.mp4
    02 python全栈3 day59 Form组件之详解字段(一).ev4.mp4
    03 python全栈3 day59 Form组件之详解字段(二).ev4.mp4
    04 python全栈3 day59 Form组件之详解字段(三).ev4.mp4
    05 python全栈3 day59 Form组件之常用标签示例.ev4.mp4
    06 python全栈3 day59 Form组件之动态绑定数据(一).ev4.mp4
    07 python全栈3 day59 Form组件之动态绑定数据(二).ev4.mp4

    day60-Djang_序列化:
    分享-1.ev4.mp4
    分享-2.ev4.mp4
    分享-3.ev4.mp4
    分享-4.ev4.mp4
    分享-5.ev4.mp4
    分享-6.ev4.mp4

    day61:
    01 python全栈3 day61 上节内容回顾.ev4.mp4
    02 python全栈3 day61 上传文件.ev4.mp4
    03 python全栈3 day61 制作上传按钮.ev4.mp4
    04 python全栈3 day61 Form组件上传文件.ev4.avi
    05 python全栈3 day61 上传相关内容梳理.ev4.mp4
    06 python全栈3 day61 Model操作知识提问.ev4.mp4
    07 python全栈3 day61 Model操作概述.ev4.mp4
    08 python全栈3 day61 Model字段.ev4.mp4
    09 python全栈3 day61 Model连表字段参数详解.ev4.mp4
    10 python全栈3 day61 Model自定义多对多第三张表.ev4.mp4
    11 python全栈3 day61 强插一道面试题.ev4.mp4
    12 python全栈3 day61 Model连表操作梳理(一).ev4.mp4
    13 python全栈3 day61 多对多自关联.ev4.mp4
    14 python全栈3 day61 Model连表操作梳理(二).ev4.mp4
    16 python全栈3 day61 Model操作补充(一).ev4.mp4
    17 python全栈3 day61 再插两道JavaScript面试题.ev4.mp4
    18 python全栈3 day61 Model操作之select_related以及prefetch_related.ev4.mp4
    19 python全栈3 day61 Model操作补充(二).ev4.mp4
    20 python全栈3 day61 Model操作知识梳理以及补充.ev4.mp4
    21 python全栈3 day61 JavaScript两道面试题讲解.ev4.mp4

    day62:
    01 python全栈3 day62 今日内容概要.ev4.avi
    02 python全栈3 day62 创建基本项目.ev4.mp4
    03 python全栈3 day62 XMLHttpRequest对象发送请求.ev4.mp4
    04 python全栈3 day62 XMLHttpRequest对象发送POST请求.ev4.mp4
    05 python全栈3 day62 Iframe伪造Ajax请求.ev4.mp4
    06 python全栈3 day62 Iframe伪造回调函数.ev4.mp4
    07 python全栈3 day62 上述内容整理.ev4.avi
    08 python全栈3 day62 FormData对象以及Ajax文件上传.ev4.mp4
    09 python全栈3 day62 Iframe文件上传.ev4.mp4
    10 python全栈3 day62 Iframe上传文件.ev4.mp4
    11 python全栈3 day62 JSONP实现AJax跨域.ev4.mp4
    12 python全栈3 day62 内容整理以及CORS简单介绍.ev4.mp4

    day63:
    01 python全栈3 day63 项目以及学习介绍.ev4.avi
    02 python全栈3 day63 企业官网示例功能介绍(一).ev4.mp4
    03 python全栈3 day63 企业官网示例功能介绍(二).ev4.mp4
    04 python全栈3 day63 企业官网示例以及数据库表结构.ev4.mp4
    05 python全栈3 day63 企业官网示例作业要求.ev4.mp4
    06 python全栈3 day63 Toando源码基本基本介绍(一).ev4.mp4
    07 python全栈3 day63 Toando源码基本基本介绍(二).ev4.mp4
    分享-1.ev4.mp4
    分享-2 .ev4.mp4

    day64 组合搜索:
    01 python全栈3 day64 组合搜索(一)无声.ev4.mp4
    02 python全栈3 day64 组合搜索(二).ev4.mp4
    03 python全栈3 day64 组合搜索(三).ev4.mp4
    04 python全栈3 day64 组合搜索(四).ev4.mp4
    05 python全栈3 day64 组合搜索(五).ev4.mp4
    06 python全栈3 day64 瀑布流(一).ev4.mp4
    07 python全栈3 day64 瀑布流(二).ev4.mp4
    08 python全栈3 day64 瀑布流作业.ev4.avi

    day65 瀑布流:
    01 python全栈3 day65 今日内容概要.ev4.avi
    02 python全栈3 day65 瀑布流作业讲解(一).ev4.mp4
    03 python全栈3 day65 瀑布流作业讲解(二).ev4.mp4
    04 python全栈3 day65 保障系统需求分析.ev4.mp4
    05 python全栈3 day65 保障系统数据库设计.ev4.mp4
    06 python全栈3 day65 保障系统目录结构规定.ev4.mp4
    07 python全栈3 day65 阶段任务安排.ev4.mp4

    day66:
    01 python全栈3 day66 保障系统主页功能讲解.ev4.mp4
    02 python全栈3 day66 保障系统主页分类和分页的实现.ev4.mp4
    03 python全栈3 day66 阶段作业:保障系统登录注册.ev4~1.avi

    day67 网页验证:
    01 python全栈3 day67 保障系统之登录注册功能讲解.ev4.mp4
    02 python全栈3 day66 保障系统之网站验证码(一).ev4.mp4
    03 python全栈3 day66 保障系统之网站验证码(二).ev4.mp4
    04 python全栈3 day66 保障系统之网站验证码(三).ev4.mp4
    05 python全栈3 day66 保障系统之一个月免登陆.ev4.mp4
    06 python全栈3 day66 任务安排.ev4.mp4

    day68 知识库:
    01 python全栈3 day68 保障系统之个人知识库主页.ev4.mp4
    02 python全栈3 day68 保障系统之个人知识库内容筛选.ev4.mp4
    03 python全栈3 day68 保障系统之文章最终页.ev4.mp4
    04 python全栈3 day68 保障系统之KindEditor基本使用.ev4.mp4
    05 python全栈3 day68 保障系统之下节预告.ev4.avi
    06 python全栈3 day68 今日作业以及下节预告.ev4.avi

    day69 后台布局:
    01 python全栈3 day68 后台管理功能介绍.ev4.mp4
    02 python全栈3 day68 后台管理页面布局(一).ev4.mp4
    03 python全栈3 day68 后台管理页面布局(二).ev4.mp4
    04 python全栈3 day68 后台管理页面布局(三).ev4.mp4
    05 python全栈3 day68 后台管理页面布局(四).ev4.mp4
    06 python全栈3 day68 今日作业以及下节预告.ev4.mp4

    day70 报障单:
    01 python全栈3 day70 后台管理之创建报障单.ev4.mp4
    02 python全栈3 day70 后台管理之处理报障单.ev4.mp4
    03 python全栈3 day70 后台管理之画图流程.ev4.mp4
    04 python全栈3 day70 后台管理之Highchart统计保障单.ev4.mp4

    day71  权限管理:
    01 python全栈3 day71 权限管理要求.ev4.mp4
    02 python全栈3 day71 权限管理数据库表设计.ev4.mp4
    03 python全栈3 day71 填充权限数据.ev4.mp4
    04 python全栈3 day71 作业:获取权限以及菜单信息.ev4.mp4
    05 python全栈3 day71 作业思路讲解.ev4.mp4
    06 python全栈3 day71 权限管理之获取用户权限信息.ev4.mp4
    07 python全栈3 day71 权限管理之获取用户菜单信息(一).ev4.mp4
    08 python全栈3 day71 权限管理之获取用户菜单信息(二).ev4.avi
    09 python全栈3 day71 权限管理之用户权限挂靠到菜单上.ev4.mp4
    10 python全栈3 day71 权限管理之处理菜单等级关系(一).ev4.mp4
    11 python全栈3 day71 权限管理之处理菜单等级关系(二).ev4.mp4

    day72 菜单生成:
    01 python全栈3 day72 上节内容概要以及标记应该显示的菜单.ev4.mp4
    02 python全栈3 day72 权限管理之递归生成多级菜单(一).ev4.mp4
    03 python全栈3 day72 权限管理之递归生成多级菜单(二).ev4.mp4
    04 python全栈3 day72 权限管理之标记当前以及激活菜单.ev4.mp4
    05 python全栈3 day72 权限管理之基本使用.ev4.mp4
    06 python全栈3 day72 权限管理之封装权限组件.ev4.avi
    07 python全栈3 day72 下节预告.ev4.mp4

    day73 - cmdb:
    01 python全栈3 day73 CMDB项目介绍.ev4.mp4
    02 python全栈3 day73 CMDB开发背景.ev4.mp4
    03 python全栈3 day73 CMDB开发目的.ev4.mp4
    04 python全栈3 day73 CMDB资产采集方式之agent.ev4.mp4
    05 python全栈3 day73 CMDB资产采集方式之ssh.ev4.mp4
    06 python全栈3 day73 CMDB资产采集方式之saltstack.ev4.mp4
    07 python全栈3 day73 CMDB资产采集方式之puppet.ev4.mp4
    08 python全栈3 day73 CMDB资产采集方式比较.ev4.mp4
    09 python全栈3 day73 CMDB资产采集内容梳理.ev4.mp4
    10 python全栈3 day73 CMDB资产采集功能实现之agent.ev4.mp4
    11 python全栈3 day73 CMDB资产采集功能实现之ssh.ev4.mp4
    12 python全栈3 day73 CMDB资产采集功能实现之saltstack(一).ev4.mp4
    13 python全栈3 day73 CMDB资产采集功能实现之saltstack(二).ev4.avi
    14 python全栈3 day73 CMDB资产采集插件开发(一).ev4.mp4
    15 python全栈3 day73 内容回顾之面向对象继承.ev4.mp4
    16 python全栈3 day73 CMDB资产采集插件开发(二).ev4.mp4
    17 python全栈3 day73 CMDB资产采集插件开发(三).ev4.mp4
    18 python全栈3 day73 作业:基于配置文件加载插件.ev4.mp4

    day74:
    01 python全栈3 day74 CMDB项目上节作业讲解.ev4.mp4
    02 python全栈3 day73 CMDB项目采集资产数据(一).ev4.mp4
    03 python全栈3 day73 CMDB项目采集资产数据之唯一标识规定.ev4.mp4
    04 python全栈3 day73 CMDB项目采集资产数据(二).ev4.avi
    05 python全栈3 day73 CMDB项目采集资产数据(三).ev4.mp4
    06 python全栈3 day73 知识拾遗之线程进程池.ev4.mp4
    07 python全栈3 day73 CMDB项目采集资产之日志记录.ev4.mp4
    08 python全栈3 day73 自定义JSON序列化类型.ev4.mp4
    09 python全栈3 day73 本节作业.ev4.mp4

    day75:
    01 python全栈3 day75 基于requests模块汇报资产数据.ev4.mp4
    02 python全栈3 day75 基于requests模块汇报API验证(一).ev4.mp4
    03 python全栈3 day75 基于requests模块汇报API验证(二).ev4.mp4
    04 python全栈3 day75 CMDB项目示例之API验证流程.ev4.mp4
    05 python全栈3 day75 CMDB项目之数据库表结构(一).ev4.mp4
    06 python全栈3 day75 CMDB项目之数据库表结构(二).ev4.mp4
    07 python全栈3 day75 CMDB项目之资产汇报并持久化(一).ev4.mp4
    08 python全栈3 day75 CMDB项目之资产汇报并持久化(二).ev4.mp4
    09 python全栈3 day75 CMDB项目之持久化资产流程.ev4.mp4
    10 python全栈3 day75 本周作业.ev4.avi

    day76:
    01 python全栈3 day76 CMDB项目CURD组件之配置文件构造.ev4.mp4
    02 python全栈3 day76 CMDB项目CURD组件之神奇的单@符号.ev4.mp4
    03 python全栈3 day76 CMDB项目CURD组件之神奇的双@符号(一).ev4.mp4
    04 python全栈3 day76 CMDB项目CURD组件之神奇的双@符号(二).ev4.mp4
    05 python全栈3 day76 CMDB项目CURD组件之自定义td属性.ev4.mp4
    06 python全栈3 day76 下节内容预习.ev4.mp4

    day77:
    01 python全栈3 day77 CMDB项目CURD组件之进入编辑模式.ev4.mp4
    02 python全栈3 day77 CMDB项目CURD组件之全选取消反选和编辑模式.ev4.mp4
    03 python全栈3 day77 CMDB项目CURD组件之内容截图.ev4.mp4
    04 python全栈3 day77 CMDB项目CURD组件之进入编辑模式详细.ev4.mp4
    05 python全栈3 day77 CMDB项目CURD组件之属性中应用神奇的单@符号.ev4.mp4
    06 python全栈3 day77 CMDB项目CURD组件之退出编辑模式.ev4.mp4
    07 python全栈3 day77 CMDB项目CURD组件之更新数据.ev4.mp4
    08 python全栈3 day77 CMDB项目CURD组件之基于jQuery扩展封装组件.ev4.mp4
    09 python全栈3 day77 CMDB项目10分钟搞定页面基本操作.ev4.mp4
    10 python全栈3 day77 CMDB项目CURD组件之分页功能.ev4.mp4
    11 python全栈3 day77 CMDB项目CURD组件之搜索功能介绍.ev4.avi
    12 python全栈3 day77 CMDB项目总结.ev4.mp4

    day78:
    01 python s3 fullstack 剩余项目概览.ev4.mp4
    02 python s3 fullstack CRM项目需求分析.ev4.mp4
    03 python s3 fullstack CRM项目需求分析2.ev4.mp4
    04 python s3 fullstack CRM项目需求分析及架构设计.ev4.mp4
    05 python s3 fullstack CRM项目表结构设计.ev4.mp4
    06 python s3 fullstack CRM项目表结构设计2.ev4.mp4

    day79 crm kingadmin:
    01 python s3 fullstack CRM项目实战-前端页面布局.ev4.mp4
    02 python s3 fullstack CRM项目实战-登录页面开发.ev4.avi
    03 python s3 fullstack CRM项目实战-登录页面开发2.ev4.mp4
    04 python s3 fullstack CRM项目实战-动态菜单设计.ev4.mp4
    05 python s3 fullstack CRM项目实战-kingadmin开发设计.ev4.mp4
    06 python s3 fullstack CRM项目实战-kingadmin自动发现及注册功能开发.ev4.mp4
    07 python s3 fullstack CRM项目实战-kingadmin model obj list页面开发.ev4.mp4
    08 python s3 fullstack CRM项目实战-kingadmin 根据list_display配置生成数据列表.ev4.mp4
    09 python s3 fullstack CRM项目实战-kingadmin 多条件过滤功能开发.ev4.mp4
    10 python s3 fullstack CRM项目实战-kingadmin 多条件过滤功能开发2.ev4.mp4

    day80 分页:
    01 python s3 fullstack day80 课前鸡汤.ev4 (1).avi
    01 python s3 fullstack day80 课前鸡汤.ev4.avi
    02 python s3 fullstack day80 分页功能开发.ev4 (1).mp4
    02 python s3 fullstack day80 分页功能开发.ev4.mp4
    03 python s3 fullstack day80 分页功能优化.ev4 (1).mp4
    03 python s3 fullstack day80 分页功能优化.ev4.mp4
    04 python s3 fullstack day80 排序功能开发.ev4.mp4
    04 python s3 fullstack day80 排序功能开发.ev4.mp4.bdc-downloading
    05 python s3 fullstack day80 分页 排序  筛选组合使用.ev4 (1).mp4
    05 python s3 fullstack day80 分页 排序  筛选组合使用.ev4.mp4
    06 python s3 fullstack day80 搜索功能开发.ev4 (1).mp4
    06 python s3 fullstack day80 搜索功能开发.ev4.mp4

    day81:
    01 python fullstack s3 CRM项目实战 - 动态modelform的实现.ev4.mp4
    02 python fullstack s3 CRM项目实战 - 动态modelform 增加自定义样式.ev4.mp4
    03 python fullstack s3 CRM项目实战 - 实现任意表的增删改查.ev4.mp4
    04 python fullstack s3 CRM项目实战 - 只读字段的处理.mp4.ev4.mp4
    05 python fullstack s3 CRM项目实战 - filter_horizontal的实现.ev4.mp4
    06 python fullstack s3 CRM项目实战 - filter_horizontal的实现2.ev4.mp4

    day82 kingadmin del 和 验证:
    01 python fullstack s3 CRM项目实战- kingadmin m2m filter_horizontal优化.ev4.mp4
    02 python fullstack s3 CRM项目实战- kingadmin 对象删除功能开发.ev4.mp4
    03+python+fullstack+s3+CRM项目实战-+kingadmin对象删除功能2.ev4.mp4
    04+python+fullstack+s3+CRM项目实战-+kingadmin+admin+action功能开发.ev4.mp4
    05+Python+fullstack+s3+CRM项目-csrf+token验证原理.ev4.mp4

    day83 学员报名:
    01  python fullstack s3 CRM项目-kingadmin批量删除.ev4.mp4
    02  python fullstack s3 CRM项目-学员报名流程开发.ev4.mp4
    03  python fullstack s3 CRM项目-学员报名流程开发2.ev4.mp4
    04  python fullstack s3 CRM项目-学员报名流程开发3.ev4.mp4
    05  python fullstack s3 CRM项目-学员报名流程开发4.ev4.mp4
    06  python fullstack s3 CRM项目-学员报名流程开发5.ev4.mp4
    07  python fullstack s3 CRM项目-本次作业需求.ev4.mp4

    day84:
    01 python fullstack s3 SSO介绍.ev4.mp4
    02 python fullstack s3 用户自定义认证.ev4.mp4
    03 python fullstack s3 用户自定义认证2.ev4.mp4
    04 python fullstack s3 万能通用权限框架设计.ev4.mp4
    05 python fullstack s3 万能通用权限框架设计2.ev4.mp4
    06 python fullstack s3 万能通用权限框架设计-自定义权限钩子实现.mp4

    day85 堡垒机需求:
    01 python fullstack s3 堡垒机项目实战-需求讨论.ev4.mp4
    02 python fullstack s3 堡垒机项目实战-需求讨论2.ev4.mp4
    03 python fullstack s3 堡垒机项目实战-表结构设计.ev4.mp4

    day86:
    01 ptyhon day86 s3 堡垒机项目实战-用户交互程序开发.ev4.mp4
    02 ptyhon day86 s3 堡垒机项目实战-用户交互程序开发2.ev4.mp4
    03 ptyhon day86 s3 堡垒机项目实战-通过paramiko记录ssh会话记录.ev4.mp4
    04 ptyhon  day86 s3 堡垒机项目实战-把parmaiko代码嵌入用户交互程序.ev4.mp4
    05 ptyhon  day86 s3 堡垒机项目实战-在数据库里记录用户会话数据.ev4.mp4

    day87 堡垒机前端:
    01 python fullstack s3 堡垒机项目实战-前端模板的选择.ev4.mp4
    02 python fullstack s3 堡垒机项目实战-web ssh的使用.ev4.mp4
    03 python fullstack s3 堡垒机项目实战-批量任务的思路.ev4.mp4
    04 python fullstack s3 堡垒机项目实战-批量任务的前端页面开发.ev4.mp4

    day88:
    01 python 88 s3 堡垒机实战-批量命令后端开发.ev4.mp4
    02 python 88 s3 堡垒机实战-批量命令后端开发2.ev4.mp4
    03 python 88 s3 堡垒机实战-批量命令前端获取执行结果.ev4.mp4
    04 python88 s3 堡垒机实战-批量文件分发.ev4.mp4
    05 python 88s3 堡垒机实战-批量文件分发2.ev4.mp4

    day89 github:
    01 python fullstack s3 版本管理工具介绍.ev4.mp4
    02 python fullstack s3 git基本使用.ev4.mp4
    03 python fullstack s3 github使用.ev4.mp4
    04 python fullstack s3 git 分支开发流程.ev4.mp4
    05 python fullstack s3 git 分支开发流程2.ev4.mp4
    06 python fullstack s3 restful规范介绍.ev4.mp4
    07 python fullstack s3 restful api设计指南.ev4.mp4

    day90 rabbitmq:
    01 python fullstack s3 django rest framework.ev4.baiduyun.downloading.mp4
    02 python fullstack s3 rabbitmq 介绍及基本使用.ev4.baiduyun.downloading.mp4
    03 python fullstack s3 rabbitmq 消息安全接收.ev4.mp4
    04 python fullstack s3 rabbitmq 消息持久化.ev4.mp4
    05 python fullstack s3 rabbitmq 消息订阅发布.ev4.mp4
    06 python fullstack s3 rabbitmq 消息组播.ev4.mp4
    07 python fullstack s3 rabbitmq 消息RPC.ev4.mp4

    day91:
    01 python全栈3 day91 阶段课程安排介绍.ev4.mp4
    02 python全栈3 day91 爬虫介绍.ev4.mp4
    03 python全栈3 day91 初识爬虫之采集汽车资讯信息(一).ev4.mp4
    04 python全栈3 day91 初识爬虫之采集汽车资讯信息(二).ev4.mp4
    05 python全栈3 day91 requests和beautfulsoup模块基本使用.ev4.mp4
    06 python全栈3 day91 示例:自动登录抽屉新热榜(一).ev4.mp4
    07 python全栈3 day91 示例:自动登录抽屉新热榜(二).ev4.mp4
    08 python全栈3 day91 requests模块详细介绍(一).ev4.mp4
    09 python全栈3 day91 requests模块详细介绍(二).ev4.mp4
    10 python全栈3 day91 requests模块详细介绍(三).ev4.mp4

    day92:
    01 python全栈3 day92 BeautifulSoup模块详细介绍(一).ev4.mp4
    02 python全栈3 day92 BeautifulSoup模块详细介绍(二).ev4.mp4
    03 python全栈3 day92 示例:自动登录知乎.ev4.mp4
    04 python全栈3 day92 示例:自动登录博客园.ev4.mp4
    05 python全栈3 day92 作业之开发Web微信.ev4.mp4

    day93 登录微信:
    01 python全栈3 day93 开发Web微信前戏.ev4.mp4
    02 python全栈3 day93 Web微信流程介绍.ev4.avi
    03 python全栈3 day93 Web微信之用户扫码.ev4.mp4
    04 python全栈3 day93 Web微信之用户信息初始化.ev4.mp4
    05 python全栈3 day93 Web微信开发总结.ev4.avi

    day94 微信收发消息:
    01 python全栈3 day94 Web微信之获取用户初始化信息并展示.ev4.mp4
    02 python全栈3 day93 Web微信之发送微信消息(一).ev4.mp4
    03 python全栈3 day93 Web微信之发送微信消息(二).ev4.mp4
    04 python全栈3 day93 Web微信之获取用户消息.ev4.mp4
    05 python全栈3 day93 Web微信开发总结.ev4.mp4

    day95  多线程,异步:
    01 python全栈3 day95 多线程实现并发请求.ev4.mp4
    02 python全栈3 day95 多进程实现并发请求.ev4.mp4
    03 python全栈3 day95 异步IO模块的使用(一).ev4.mp4
    04 python全栈3 day95 异步IO模块的使用(二).ev4.mp4
    05 python全栈3 day95 自定义异步IO模块前戏.ev4.avi
    06 python全栈3 day95 自定义异步IO模块开发.ev4.mp4
    07 python全栈3 day95 自定义异步IO模块开发.ev4.mp4

    day96:
    01 python全栈3 day96 Scrapy爬虫介绍.ev4.mp4
    02 python全栈3 day95 抽屉示例:初识Scrapy并获取新闻标题.ev4.mp4
    03 python全栈3 day95 抽屉示例:递归获取所有页码.ev4.mp4
    04 python全栈3 day95 抽屉示例:基于pipeline实现标题和URL持久化.ev4.mp4

    day97:
    01 python全栈3 day97 Scrapy上节回顾.ev4.mp4
    02 python全栈3 day97 去除重复URL.ev4.mp4
    03 python全栈3 day96 pipeline补充.ev4.mp4
    04 python全栈3 day96 自动登录抽屉并点赞.ev4.mp4
    05 python全栈3 day96 scrapy框架扩展.ev4.mp4
    06 python全栈3 day96 配置文件.ev4.mp4

    day98:
    01 python全栈3 day98 Scrapy配置之自动限速以及缓存.ev4.mp4
    02 python全栈3 day98 Scrapy之默认代理以及扩展代理.ev4.mp4
    03 python全栈3 day98 Scrapy之自定义Https证书.ev4.mp4
    04 python全栈3 day98 Scrapy配置之下载中间件.ev4.mp4
    05 python全栈3 day98 Scrapy配置之爬虫中间件.ev4.mp4
    06 python全栈3 day98 Scrapy配置之自定义scrapy命令.ev4.avi
    07 python全栈3 day98 Scrapy源码流程简述.ev4.mp4
    day98.py.mp4

    day99 框架:
    01 python全栈3 day99 今日内容概要.ev4.mp4
    02 python全栈3 day99 Scrapy源码剖析前戏之Twisted使用.ev4.mp4
    03 python全栈3 day99 Scrapy源码剖析前戏之Twisted使用(二).ev4.mp4
    04 python全栈3 day99 Scrapy源码剖析前戏之Twisted使用(三).ev4.mp4
    05 python全栈3 day99 Scrapy源码剖析之自定义Low版框架(一).ev4.mp4
    06 python全栈3 day99 Scrapy源码剖析之自定义Low版框架(二).ev4.mp4
    07 python全栈3 day99 Scrapy源码剖析之自定义TinyScrapy框架(一).ev4.mp4
    08 python全栈3 day99 Scrapy源码剖析之自定义TinyScrapy框架(二).ev4.mp4
    09 python全栈3 day99 Scrapy源码剖析流程解析.ev4.avi

     

     

    day100:
    01 python全栈3 day100 Tornado学习概要.ev4.avi
    02 python全栈3 day100 Tornado基本操作(一).ev4.mp4
    03 python全栈3 day100 Tornado基本操作(二).ev4.mp4
    04 python全栈3 day100 Tornado基本操作(三).ev4.mp4
    05 python全栈3 day100 Tornado自定义Session知识前戏.ev4.mp4
    06 python全栈3 day100 Tornado自定义Session(一).ev4.mp4
    07 python全栈3 day100 Tornado自定义Session(二).ev4.mp4
    08 python全栈3 day100 Tornado自定义Session(三).ev4.mp4

    day101:
    01 python全栈3 day101 WebSocket介绍.ev4.avi
    02 python全栈3 day101 WebSocket握手过程分析.ev4.mp4
    03 python全栈3 day101 基于Python实现WebSocket握手过程.ev4.mp4
    04 python全栈3 day101 位运算补充.ev4.mp4
    05 python全栈3 day101 WebSocket数据解析过程.ev4.mp4
    06 python全栈3 day101 基于Tornado的WebSocket实现聊天室(一).ev4.mp4
    07 python全栈3 day101 基于Tornado的WebSocket实现聊天室(二).ev4.mp4

    day102:
    01 python全栈3 day102 今日内容复习目标.ev4.mp4
    02 python全栈3 day102 异步非阻塞框架介绍(一).ev4.avi
    03 python全栈3 day102 异步非阻塞框架介绍(二).ev4.mp4
    04 python全栈3 day102 Tornado异步非阻塞功能使用(一).ev4.mp4
    05 python全栈3 day102 Tornado异步非阻塞功能使用(二).ev4.mp4
    06 python全栈3 day102 自定义Web框架(同步).ev4.mp4
    07 python全栈3 day102 自定义Web框架支持同步和异步非阻塞.ev4.mp4
    08 python全栈3 day102 复习计划.ev4.mp4

    day103:
    01 python s3 fullstack 缓存数据库介绍.ev4.mp4
    02 python s3 fullstack redis string操作.ev4.mp4
    03 python s3 fullstack redis hash 操作.ev4.mp4
    04 python s3 fullstack redis list操作.ev4.mp4
    05 python s3 fullstack redis 集合操作.ev4.mp4
    06 python s3 fullstack redis 其他常用命令.ev4.mp4
    07 python s3 fullstack redis 发布订阅.ev4.mp4
    08 python s3 fullstack celery介绍和基本使用.ev4.mp4
    09 python s3 fullstack celery在项目中使用.ev4.mp4
    10 python s3 fullstack celery定时任务.ev4.mp4
    11 python s3 fullstack celery在项目中使用.ev4.mp4
    12 python s3 fullstack celery 在django中实现定时任务.ev4.mp4

    day104:
    01pythonfullstacks3就业指导-简历制作.mp4
    02pythonfullstacks3就业指导-简历制作2.mp4
    03pythonfullstacks3就业指导-简历制作3.mp4
    04pythonfullstacks3就业指导-简历制作4.mp4
    06pythonfullstacks3就业指导-如何面试2.mp4
    07pythonfullstacks3就业指导-如何面试3.mp4
    08pythonfullstacks3就业指导-最后的鸡汤.mp4
    天帅分享:Nginx+uWSGI+Django部署.mp4

    转载于:https://www.cnblogs.com/Mrsun123/p/9551115.html

    展开全文
  • Python全栈9期(第一部分):基础+模块+面向对象+网络编程 -2019-01-09 17:23 Python全栈9期(第五部分):django高级 -2019-01-09 17:23 Python全栈9期(第四部分):django进阶 -2019-01-09 17:23 Python全栈9期...
  • (5)\\python全栈day41-50\\python全栈s3 day41;目录中文件数:10个 ├─(1) 01 python s3 day41 JS的历史以及引入方式.avi ├─(2) 02 python s3 day41 JS的基础规范.avi ├─(3) 03 python s3 day41 JS的基本数据...
  • python全栈3期

    2019-01-21 23:22:25
    python全栈3期
  • Python全栈开发

    2018-11-30 10:16:58
    这是一份python全栈开发的好资源.......希望大家好好学习!
  • Python全栈9期(第一部分):基础+模块+面向对象+网络编程 Python全栈9期(第五部分):django高级 Python全栈9期(第四部分):django进阶 Python全栈9期(第十一部分):django rest framework + vue Python...
  • python全栈教程

    2018-03-08 14:34:14
    old boy python全栈的视频教程,无密,链接失效请联系
  • 老男孩python全栈第三期视频老男孩python全栈第三期视频老男孩python全栈第三期视频老男孩python全栈第三期视频
  • python全栈视频

    2018-03-20 22:46:54
    老男孩python全栈第三期全部视频及课件,包含104天,70G高清视频。
  • 套餐中一共包含8门Python课程(共246讲)助你从零进阶Python全栈工程师! 课程1:《Python零基础入门视频教程》 课程2:《Python爬虫从入门到实战》 课程3:《Python使用Flask开发Web服务》 课程4:《Python使用...
  • python全栈入门与实战

    2019-02-24 10:02:36
    包含python全栈入门和实战全部知识点,内容涉及前后端开发和系统设计流程以及思路
  • python全栈3期104天-课件与源码;python全栈3期104天-课件与源码;python全栈3期104天-课件与源码python全栈3期104天-课件与源码
  • Python全栈+GUI实战

    2020-08-02 20:43:48
    本套餐为python全栈一课通+GUI实战专题,课程内容涉及,python基础,python进阶,前端,数据库,django,flask,数据分析,股票分析,机器学习,人脸识别等个模块构成,同时涉及GUI编程,从基本控件,高级控件,QSS,数据库...
  • Python全栈工程师
  • Python全栈工程师
  • 谢邀一、什么是Python web全栈工程师?全栈工程师是指掌握多种技能,并能利用多种技能独立完成产品的人。也叫全端工程师(同时具备前端和后台能力),英文Full Stack developer。当下全栈工程师的概念很火,而Python是...

    谢邀

    一、什么是Python web全栈工程师?

    全栈工程师是指掌握多种技能,并能利用多种技能独立完成产品的人。也叫全端工程师(同时具备前端和后台能力),英文Full Stack developer。

    当下全栈工程师的概念很火,而Python是一种全栈的开发语言,所以你如果能学好Python,那么前端,后端,测试,大数据分析,爬虫等这些工作你都能胜任。

    全栈开发工程师需要掌握的知识包括:后端开发、前端开发、界面设计、产品设计、数据库、各种移动客户端、三屏兼容、restFul API设计和OAuth等等,比较前卫的项目,还需要掌握Single Page Application、Web Socket、HTML5/CSS3这些技术以及像第三方开发像微信公众号微博应用等等。

    全栈工程师(Full-Stack Engineer),是一个在IT行业圈子里越来越热门的话题,无论是像Facebook这样的大型公司,还是刚刚起步的初创公司,都开始招募全栈工程师。据说,Facebook声称:“我们只招全栈工程师!”

    有人曾开玩笑说,全栈工程师是资本家的阴谋,因为老板想雇一个人来做三个人的工作。

    二、Python web全栈工程师薪资

    全栈工程师不管在哪个语言中都是人才中的人才,而Python web全栈工程师工资基本上都高出20K,所以如果你能力足够,首选就是Python web全栈工程师。

    python全栈开发任职资格

    1、具有python开发数据处理软件的经验;

    2、精通Python,掌握numpy,scipy,matplotlib,pandas等数据处理方面常用的第三方python库;

    3、熟悉至少一种Sql数据库 (mysql/ sqlserver/oracle);

    4、热爱编程、具有良好的代码风格;

    5、做事具有条理性,具有良好的自学能力、分析问题以及解决问题的能力。

    三、为什么web全栈开发用Python?

    我们都知道Web一直都是不可忽视的存在,我们离不开网络,离不开Web,利用Python的框架可以做网站,而且都是一些精美的前端界面,还有我们需要掌握一些数据的应用。

    因为 python代码的优雅美观且易于维护这一特点,越来越多的人选择使用 Python做Web开发。而 Python的 Web框架百花齐放,目前比较流行的框架有大包大揽的 Django,小巧灵活的 Flask、 Bottle,还有性能高效的异步框架 Tornado、 sanic。这么多框架只要选择一个,阅读他的文档,就可以很轻松的搭建一个 web app,完全不需要去管他实现的原理。

    如果你依然在编程的世界里迷茫,可以加入我们的Python学习圈。看看前辈们是如何学习的!交流经验!从基础的python脚本到web开发、爬虫、django、数据挖掘等,零基础到项目实战的资料都有整理。送给每一位python的小伙伴!分享一些学习的方法和需要注意的小细节,点击加入我们的 python学习者聚集地

    四、全栈开发工程师的价值

    全栈开发师的厉害之处并不是他掌握很多知识,可以一个人干多份工作。而是说他在处理问题的时候拥有全局性思维。

    现在科技日新月异,web前端不再是从前切个图用个jQuery上个AJAX兼容各种浏览器那么简单。现代的Web前端,你需要用到模块化开发、多屏兼容、MVC,各种复杂的交互与优化,甚至你需要用到Node.js来协助前端的开发。

    所以说一个现代化的项目,是一个非常复杂的构成,我们需要一个人来掌控全局,他不需要是各种技术的资深专家,但他需要熟悉到各种技术。对于一个团队特别是互联网企业来说,有一个全局性思维的人显得尤其重要,这个时候也就彰显了全栈开发工程师的价值。

    全栈开发师经手的项目不但会注意到网页优化,而且会考虑到API来兼容各种客户端,更会考虑到三屏兼容的问题。不会说项目中完全使用AJAX而不顾SEO,也不会为了功能性而忽略访问速度,他会很好的把握其中平衡,因为他知道各个板块的权重和实现成本。

    所以,看了这么多,你应该清楚了,Python web全栈工程师薪资高,但是要求也高。最重要的是学好Python,打好基础,从Python技术起步,网络爬虫,逐步做到Python web全栈工程师。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,045
精华内容 5,618
关键字:

python全栈

python 订阅