• Python-Flask web框架


    一、环境部署

    # mkvirtualenv flask_py2
    # pip install flask==0.10.1
    # pip freeze > requirements.txt
    # pip install -r requirements.txt
    

    二、HelloWorld

    # coding:utf-8
    
    from flask import Flask
    
    # 创建flask的应用对象
    app = Flask(__name__)  # __name__表示当前的模块名字
    
    @app.route('/index')
    def index():
        """定义视图函数"""
        return "Hello World"
    
    
    if __name__ == '__main__':
        # 启动server
        app.run()
    
    

    三、静态文件目录

    # 创建flask的应用对象
    app = Flask(__name__,  # __name__表示当前的模块名字
                static_url_path="/python",  # 访问静态资源的url前缀, http://127.0.0.1:5000/python/index.html
                static_folder="static",  # 静态文件目录,默认是static
                template_folder="templates",  # 模板文件目录,默认templates
                )
    

    四、配置参数的设置

    • 设置配置参数的3种方式
    app.config.from_pyfile("config.cfg")  # 使用配置文件方式一
    
    class Config(object):
        DEBUG = True
    app.config.from_object(Config)  # 第二种配置方式
    
    app.config["DEBUG"] = True  # 第三种配置方式
    
    • 读取配置参数
    print(app.config.get("DEBUG"))  # 读取配置参数方式一
    
    from flask import current_app
    print(current_app.config.get("DEBUG"))  # 读取配置参数方式二
    

    五、run的使用

    if __name__ == '__main__':
        # 启动server
        app.run(host="0.0.0.0", port=5000, debug=True)
    

    六、路由

    from flask import Flask
    from flask import redirect
    from flask import url_for
    
    
    app = Flask(__name__)
    
    
    @app.route("/")
    def index():
        return "Hello, World!"
    
    
    # 通过methods限制访问方式
    @app.route("/post_only", methods=["POST"])
    def post_only():
        return "post only page"
    
    
    # 同一个视图函数2个URL
    @app.route("/hi")
    @app.route("/hi2")
    def hi():
        return "hi page"
    
    
    @app.route("/login")
    def login():
        # url = "/"
        url = url_for("index")  # 获取index视图函数的url
        return redirect(url)  # 重定向
    
    
    if __name__ == '__main__':
        print(app.url_map)  # 查看整个flask中的路由信息
        app.run(debug=True)
    

    七、转换器

    from flask import Flask
    from werkzeug.routing import BaseConverter
    from flask import redirect
    from flask import url_for
    
    app = Flask(__name__)
    
    
    @app.route("/goods/<int:goods_id>")  # 转换器,接收参数;int如果不加则是匹配字符串
    def goods_detail(goods_id):
        return "goods detail page, goods id: %d" % goods_id
    
    
    # 自定义转换器
    class MobileConverter(BaseConverter):
        """构建一个匹配手机号的普通转换器"""
    
        def __init__(self, url_map):
            super(MobileConverter, self).__init__(url_map)
            self.regex = r'1[34578]d{9}'
    
    
    class RegexConverter(BaseConverter):
        """构建一个万能转换器"""
    
        def __init__(self, url_map, regex):
            # 调用父类的初始化方法
            super(RegexConverter, self).__init__(url_map)
            # 将正则表达式的参数保存到对象中,flask用这个属性实现路由的匹配
            self.regex = regex
    
        def to_python(self, value):
            print("to_python: ", value)
            return value
    
        def to_url(self, value):
            return value
    
    
    # 将自定义转换器添加到flask的应用中
    app.url_map.converters["mobile"] = MobileConverter
    app.url_map.converters["re"] = RegexConverter
    
    
    # 使用自定义转换器
    @app.route("/send/<mobile:mobile>")
    @app.route("/send2/<re(r'1[34578]d{9}'):mobile>")
    def send_sms(mobile):
        return "send sms to %s" % mobile
    
    
    @app.route("/index")
    def index():
        url = url_for("send_sms", mobile="13312322156")
        return redirect(url)
    
    
    if __name__ == '__main__':
        app.run()
    
    

    八、接收request请求参数

    request对象保存了一次http请求的所有信息

    • data:请求的数据,字符串
    • form:请求的表单数据,multiDict
    • args:请求的查询参数,multiDict
    • cookies:请求的cookie信息,Dict
    • headers:请求的报文头,EnvironHeaders
    • method:请求使用的HTTP方法,GET/POST等
    • url:请求的URL地址,string
    • files:请求上传的文件
    from flask import Flask
    from flask import request
    
    app = Flask(__name__)
    
    
    @app.route("/index", methods=["GET", "POST"])
    def index():
        # request包含前端发送过来的所有请求信息
        # 提取表单格式的数据
        name = request.form.get("name", "no_name")  # no_name是默认值
        age = request.form.get("age", "0")
    
        # 取出多个重名的参数
        name_list = request.form.getlist("name")
        print(name_list)
    
        # 接收查询字符串参数
        city = request.args.get("city")
        print(city)
    
        # 获取请求体数据
        print(request.data)
        return "hello name=%s, age=%s" % (name, age)
    
    
    @app.route("/register")
    def register():
        # 获取method
        if request.method == "GET":
            pass
        elif request.method == "POST":
            pass
    
    
    if __name__ == '__main__':
        app.run()
    
    

    九、上传文件

    from flask import Flask
    from flask import request
    
    app = Flask(__name__)
    
    
    @app.route("/upload", methods=["POST"])
    def upload():
        """接收前端上传的文件"""
        file_obj = request.files.get("pic")
        if file_obj is None:
            # 表示没有发送文件
            return "未上传文件"
        # 保存文件
        # with open("files/demo.jpg", "wb") as f:
        #     data = file_obj.read()
        #     f.write(data)
        file_obj.save("files/demo2.jpg")
        return "上传成功"
    
    
    if __name__ == '__main__':
        app.run()
    
    

    十、abort 提前终止视图函数

    from flask import Flask
    from flask import request
    from flask import abort
    from flask import Response
    
    app = Flask(__name__)
    
    
    @app.route("/login", methods=["POST"])
    def login():
        name = request.form.get("name")
        pwd = request.form.get("pwd")
        if name != "dongfei" or pwd != "dpwd":
            # 使用abort函数立即终止视图函数执行,并且返回前端特定信息
            abort(403)  # 传递状态码信息
    
            # 返回信息
            # resp = Response("login failed")
            # abort(resp)
    
        return "login success"
    
    
    @app.errorhandler(404)
    def handle_404_error(err):
        """自定义处理错误方法"""
        # 这个返回值是前端用户看到的结果
        return "页面找不到了,错误:%s" % err
    
    
    if __name__ == '__main__':
        app.run()
    
    

    十一、response构造

    from flask import Flask
    from flask import make_response
    
    app = Flask(__name__)
    
    
    @app.route("/index")
    def index():
        # 使用元组返回自定义的响应信息
        # 响应体, 状态码, 响应头
        return "index page", 200, [("frame", "flask"), ]
    
    
    @app.route("/index2")
    def index2():
        # 使用字典返回自定义的响应信息
        # 响应体, 自定义状态码, 响应头
        return "index page", "600 my status", {"frame": "flask"}
    
    
    @app.route("/index3")
    def index3():
        # 使用make_response构造响应信息
        resp = make_response("index3 page")
        resp.status = "666 my index3 status"
        resp.headers["frame"] = "flask"
        return resp
    
    
    if __name__ == '__main__':
        app.run()
    
    

    十二、返回json响应

    from flask import Flask
    from flask import jsonify
    
    app = Flask(__name__)
    
    
    @app.route("/index")
    def index():
        # json是字符串
        data = {
            "name": "dongfei",
            "age": 18
        }
        # return json.dumps(data), 200, {"Content-Type": "application/json"}
        # jsonify帮助转换json后并且设置响应头
        return jsonify(data)
    
    
    if __name__ == '__main__':
        app.run()
    

    十三、cookie

    from flask import Flask
    from flask import make_response
    from flask import request
    
    
    app = Flask(__name__)
    
    
    @app.route("/set_cookie")
    def set_cookie():
        resp = make_response("success")
        # 设置cookie,默认有效期是临时cookie,浏览器关闭则失效
        resp.set_cookie("key_cookie", "value_cookie")
    
        # 设置cookie有效期,单位s
        resp.set_cookie("key2_cookie", "value2_cookie", max_age=3600)
        return resp
    
    
    @app.route("/get_cookie")
    def get_cookie():
        # 获取cookie
        cookie = request.cookies.get("key_cookie")
        return cookie
    
    
    @app.route("/del_cookie")
    def del_cookie():
        resp = make_response("del success")
        # 删除cookie
        resp.delete_cookie("key_cookie")
        return resp
    
    
    if __name__ == '__main__':
        app.run()
    

    十四、session

    from flask import Flask
    from flask import session
    
    app = Flask(__name__)
    
    # flask的session需要的秘钥
    app.config["SECRET_KEY"] = "ALKSJDFLWJERJLKJSLDJF21324"
    
    
    # flask默认把session保存到cookie中
    
    
    @app.route("/login")
    def login():
        # 设置session
        session["name"] = "dongfei"
        session["mobile"] = 111111
        return "login success"
    
    
    @app.route("/index")
    def index():
        # 获取session
        name = session.get("name")
        return "hello %s" % name
    
    
    if __name__ == '__main__':
        app.run()
    
    

    十五、请求钩子 hook

    befor_first_request:  在处理第一个请求前运行
    @app.befor_first_request
    
    before_request:  在每次请求前运行
    
    after_request(response):  如果没有未处理的异常抛出,在每次请求后运行
    
    teardown_request(response):  在每次请求后运行,即使有未处理的异常抛出
    
    
    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route("/index")
    def index():
        print("in the index")
        a = 1/0
        return "in the index"
    
    
    @app.before_first_request
    def handle_before_first_request():
        """在第一次请求处理之前先被执行"""
        print("in the handle_before_first_request")
    
    
    @app.before_request
    def handle_before_request():
        """在每次请求前运行"""
        print("in the handle_before_request")
    
    
    @app.after_request
    def handle_after_request(response):
        """视图函数没有出现异常的情况下,在每次请求之后被执行"""
        print("in the handle_after_request")
        return response
    
    
    @app.teardown_request
    def handle_teardown_request(response):
        """无论视图函数有没有出现异常的情况下,在每次请求之后被执行"""
        print("in the handle_teardown_request")
        return response
    
    
    if __name__ == '__main__':
        app.run()
    
    

    十六、Flask-Script

    >pip install Flask-Script
    
    from flask import Flask
    # 启动命令的管理类
    from flask_script import Manager
    
    app = Flask(__name__)
    
    # 让manager管理app
    manager = Manager(app)
    
    
    @app.route("/index")
    def index():
        return "index page"
    
    
    if __name__ == '__main__':
        # 通过管理对象启动flask
        manager.run()
    
    
    >python 12_flask_script.py runserver -h 127.0.0.1 -p 5000
    

    十七、模板template

    1、模板变量

    from flask import Flask
    from flask import render_template
    
    app = Flask(__name__)
    
    
    @app.route("/index")
    def index():
        data = {
            "name": "dongfei",
            "age": 20,
            "my_dict": {"city": "biejing"},
            "my_list": [1, 2, 3]
        }
        return render_template("index.html", **data)
    
    
    if __name__ == '__main__':
        app.run()
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <p>name = {{ name }}</p>
        <p>age = {{ age }}</p>
        <p>my_dict: city = {{ my_dict.city }}</p>
        <p>my_list: {{ my_list }}</p>
    </body>
    </html>
    

    2、过滤器

    • 字符串过滤器
    safe:禁用转义;
      <p>{{ '<em>hello</em>' | safe }}</p>
    
    capitalize:把变量值的首字母转成大写,其余字母转小写;
      <p>{{ 'hello' | capitalize }}</p>
    
    lower:把值转成小写;
      <p>{{ 'HELLO' | lower }}</p>
    
    upper:把值转成大写;
      <p>{{ 'hello' | upper }}</p>
    
    title:把值中的每个单词的首字母都转成大写;
      <p>{{ 'hello' | title }}</p>
    
    trim:把值的首尾空格去掉;
      <p>{{ ' hello world ' | trim }}</p>
    
    reverse:字符串反转;
      <p>{{ 'olleh' | reverse }}</p>
    
    format:格式化输出;
      <p>{{ '%s is %d' | format('name',17) }}</p>
    
    striptags:渲染之前把值中所有的HTML标签都删掉;
      <p>{{ '<em>hello</em>' | striptags }}</p>
    
    • 列表过滤器
    first:取第一个元素
      <p>{{ [1,2,3,4,5,6] | first }}</p>
    
    last:取最后一个元素
      <p>{{ [1,2,3,4,5,6] | last }}</p>
    
    length:获取列表长度
      <p>{{ [1,2,3,4,5,6] | length }}</p>
    
    sum:列表求和
      <p>{{ [1,2,3,4,5,6] | sum }}</p>
    
    sort:列表排序
      <p>{{ [6,2,3,1,5,4] | sort }}</p>
    
    • 自定义过滤器
    from flask import Flask
    from flask import render_template
    
    app = Flask(__name__)
    
    
    @app.route("/index")
    def index():
        data = {
            "name": "dongfei",
            "age": 20,
            "my_dict": {"city": "biejing"},
            "my_list": [1, 2, 3, 4, 5, 6]
        }
        return render_template("index2.html", **data)
    
    
    # 自定义过滤器
    def list_step_2(li):
        """处理列表,步长为2的取值"""
        return li[::2]
    
    
    # 注册过滤器
    app.add_template_filter(list_step_2, "li2")  # li2是过滤器名字
    
    
    # 自定义过滤器方法二
    @app.template_filter("li3")
    def list_step_3(li):
        """处理列表,步长为2的取值"""
        return li[::3]
    
    
    if __name__ == '__main__':
        app.run()
    
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        {#去首尾空白字符#}
        <p>{{ "  flask world  " | trim}}</p>
    
        <p>{{ my_list | li2}}</p>
        <p>{{ my_list | li3}}</p>
    </body>
    </html>
    

    3、表单Flask-WTF扩展

    >pip install Flask-WTF
    
    • WTForms支持的HTML标准字段

    • WTForms常用验证函数

    from flask import Flask
    from flask import render_template
    from flask import redirect
    from flask import url_for
    from flask import session
    from flask_wtf import FlaskForm
    from wtforms import StringField  # WTForms支持的HTML标准字段
    from wtforms import PasswordField
    from wtforms import SubmitField
    from wtforms.validators import DataRequired  # WTForms验证函数
    from wtforms.validators import EqualTo
    
    app = Flask(__name__)
    
    app.config["SECRET_KEY"] = "lkajsdklrjsdfwer"
    
    
    # 定义表单的模型类
    class RegisterForm(FlaskForm):
        """自定义的注册表单模型类"""
        username = StringField(label="用户名", validators=[DataRequired("用户名不能为空"), ], )  # DataRequired不能为空
        password = PasswordField(label="密码", validators=[DataRequired("密码不能为空")])
        password2 = PasswordField(label="确认密码",
                                  validators=[DataRequired("确认密码不能为空"),
                                              EqualTo("password", "两次输入的密码不一致")])  # EqualTo和password字段比较是否一致
        submit = SubmitField(label="提交")
    
    
    @app.route("/register", methods=["GET", "POST"])
    def register():
        # 创建表单对象,如果是post请求,前端发送数据,flask会把数据构造form对象的时候存放在对象中
        form = RegisterForm()
    
        # 判断form中的数据是否合理,满足所有返回True
        if form.validate_on_submit():
            # 表示验证合格,提取数据
            username = form.username.data
            password = form.password.data
            password2 = form.password2.data
            print(username, password, password2)
            session["username"] = username
            return redirect(url_for("index"))
    
        return render_template("register.html", form=form)
    
    
    @app.route("/index")
    def index():
        username = session.get("username")
        return "hello %s" % username
    
    
    if __name__ == '__main__':
        app.run()
    
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <form action="" method="post" novalidate>
        {{ form.csrf_token }}
    
        {{ form.username.label }}
        <p>{{ form.username }}</p>
        {% for msg in form.username.errors %}
            <p>{{ msg }}</p>
        {% endfor %}
    
        {{ form.password.label }}
        <p>{{ form.password }}</p>
        {% for msg in form.password.errors %}
            <p>{{ msg }}</p>
        {% endfor %}
    
        {{ form.password2.label }}
        <p>{{ form.password2 }}</p>
        {% for msg in form.password2.errors %}
            <p>{{ msg }}</p>
        {% endfor %}
    
        {{ form.submit }}
    </form>
    
    </body>
    </html>
    

    4、模板宏

    • macro.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    {#定义没有参数的宏#}
    {% macro input() %}
        <input type="text" value="" size="30">
    {% endmacro %}
    {#调用宏#}
    <h1>{{ input() }}</h1>
    
    {#定义有参数的宏#}
    {% macro input2(type, value, size) %}
        <input type="{{ type }}" value="{{ value }}" size="{{ size }}">
    {% endmacro %}
    <h1>{{ input2("password", "", 50) }}</h1>
    
    {#定义有参数的宏,有默认值#}
    {% macro input3(type="text", value="default", size=70) %}
        <input type="{{ type }}" value="{{ value }}" size="{{ size }}">
    {% endmacro %}
    <h1>{{ input3() }}</h1>
    
    {#导入宏#}
    {% import "macro_input.html" as m_input %}
    {{ m_input.input4() }}
    
    </body>
    </html>
    
    • macro_input.html
    {#在外部定义宏#}
    {% macro input4(type="text", value="default", size=70) %}
        <input type="{{ type }}" value="{{ value }}" size="{{ size }}">
    {% endmacro %}
    <h1>{{ input4() }}</h1>
    

    5、模板闪现信息

    from flask import Flask
    from flask import flash
    from flask import render_template
    
    app = Flask(__name__)
    app.config["SECRET_KEY"] = "sdfasdw"
    
    
    @app.route("/index")
    def index():
        # 添加闪现信息
        flash("message01")
        flash("message02")
        flash("message03")
        return render_template("flash.html")
    
    
    if __name__ == '__main__':
        app.run()
    
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <h1>闪现信息</h1>
    {% for msg in get_flashed_messages() %}
        <p>{{ msg }}</p>
    {% endfor %}
    
    </body>
    </html>
    

    十八、数据库扩展sqlalchemy

    • SQLAlchemy是一个关系型数据库框架,它提供了高层的ORM和底层的原生数据库的操作
    • flask-sqlalchemy是一个简化了SQLAlchemy操作的flask扩展
    >pip install flask-sqlalchemy
    >pip install flask-mysqldb
    
    • sqlalchemy配置
    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    
    app = Flask(__name__)
    
    
    # 连接数据的URI
    # app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://flask:flask@192.168.100.1:3306/flask"
    
    # 每次请求接收后自动提交数据到数据库,不建议打开
    # app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True
    
    # 跟踪数据库的修改而修改模型类对象,通常打开
    # app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
    
    # 显示原生SQL语句
    # app.config["SQLALCHEMY_ECHO"] = True
    
    # 使用类定义配置
    class Config(object):
        """配置参数"""
        SQLALCHEMY_DATABASE_URI = "mysql://flask:flask@192.168.100.1:3306/flask"
        SQLALCHEMY_TRACK_MODIFICATIONS = True
        SQLALCHEMY_ECHO = True
    
    
    # 注册配置
    app.config.from_object(Config)
    
    # 创建数据库sqlalchemy工具对象
    db = SQLAlchemy(app)
    

    1、模型类、创建数据表、插入数据

    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    
    app = Flask(__name__)
    
    
    # 使用类定义配置
    class Config(object):
        """配置参数"""
        SQLALCHEMY_DATABASE_URI = "mysql://flask:flask@192.168.100.1:3306/flask"
        SQLALCHEMY_TRACK_MODIFICATIONS = True
        # SQLALCHEMY_ECHO = True
    
    
    # 注册配置
    app.config.from_object(Config)
    
    # 创建数据库sqlalchemy工具对象
    db = SQLAlchemy(app)
    
    
    # 创建模型类
    class Role(db.Model):
        """角色表"""
        __tablename__ = "tbl_roles"
    
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(32), unique=True)
        users = db.relationship("User", backref="role")  # User: 对应模型类关系, backref="role": 可以通过User类反推
    
        def __repr__(self):
            """可以让显示对象的时候更直观"""
            return "Role object: name=%s" % self.name
    
    
    class User(db.Model):
        """用户表"""
        __tablename__ = "tbl_users"  # 指定数据库的表名
    
        id = db.Column(db.Integer, primary_key=True)  # 整型主键会默认自增
        name = db.Column(db.String(64), unique=True)
        email = db.Column(db.String(128), unique=True)
        password = db.Column(db.String(128), nullable=False)
        role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))  # 定义外键, tbl_roles: 表名
    
        def __repr__(self):
            return "User object: name=%s" % self.name
    
    
    if __name__ == '__main__':
        # 通过db方式创建数据库表
        # 清楚数据库里的所有表
        db.drop_all()
    
        # 创建所有的表
        db.create_all()
    
    

    2、插入数据

        # 插入数据
        role1 = Role(name="admin")
        db.session.add(role1)
        db.session.commit()
    
        role2 = Role(name="stuff")
        db.session.add(role2)
        db.session.commit()
    
        us1 = User(name="dongfei", email="dongfei@126.com", password="dongfei", role_id=role1.id)
        us2 = User(name="dongfei2", email="dongfei2@126.com", password="dongfei2", role_id=role2.id)
        db.session.add_all([us1, us2])  # 一次保存多条数据
        db.session.commit()
    

    3、数据库查询

        r_list = Role.query.all()  # 查询所有数据
        print(r_list[0].name)
    
        r_first = Role.query.first()  # 查询第一条数据
        print(r_first.name)
    
        r_get = Role.query.get(2)  # 根据主键id查询
        print(r_get.name)
    
        # 过滤
        user = User.query.filter_by(name="dongfei").first()
        print(user.email)
    
        user = User.query.filter_by(name="dongfei", role_id=2).first()  # 并且关系
        print(type(user))
    
        user = User.query.filter(User.name == "dongfei").first()
        print(user.name)
    
        from sqlalchemy import or_
    
        user_list = User.query.filter(or_(User.name == "dongfei", User.email.endswith("126.com"))).all()  # 或者关系
        print(user_list[1].name)
    
        # User.query.filter().offset().limit().order_by().all()
    
        # 跳过1条,查询1条
        user = User.query.offset(1).limit(1).first()
        print(user.name)
    
        # 倒叙排序
        user = User.query.order_by(User.id.desc()).all()
        print(user)
    
        # 分组
        from sqlalchemy import func
    
        # SELECT role_id, COUNT(role_id) from User group by role_id;
        group = db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_id).all()
        print(group[0])
    
        # 关联查询
        # 从Role往User查询
        ro = Role.query.get(1)
        print(ro.users[0].name)
    
        # 从User往Role查询
        user = User.query.get(1)
        print(user.role.name)
    

    4、更新与删除

        # 修改方式一
        user = User.query.get(1)
        user.name = "jack"
        db.session.add(user)
        db.session.commit()
    
        # 修改方式二
        User.query.filter_by(name="dongfei2").update({"name": "maria", "password": "123456"})
        db.session.commit()
    
        # 删除
        user = User.query.get(1)
        db.session.delete(user)
        db.session.commit()
    

    十九、管理作者和图书的小案例

    • 后端
    from flask import Flask
    from flask import render_template
    from flask_sqlalchemy import SQLAlchemy
    from flask_wtf import FlaskForm
    from wtforms import StringField
    from wtforms.validators import DataRequired
    from wtforms import SubmitField
    from flask import request
    from flask import redirect
    from flask import url_for
    from flask import jsonify
    
    app = Flask(__name__)
    
    
    class Config(object):
        SQLALCHEMY_DATABASE_URI = "mysql://flask:flask@192.168.100.1:3306/author_book"
        SQLALCHEMY_TRACK_MODIFICATIONS = True
        SECRET_KEY = "LKSJLKFJSDIUETR"
    
    
    app.config.from_object(Config)
    db = SQLAlchemy(app)
    
    
    # 创建数据表模型
    class Author(db.Model):
        """作者表"""
        __tablename__ = "tbl_authors"
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(32), unique=True)
        books = db.relationship("Book", backref="author")
    
    
    class Book(db.Model):
        """书籍表"""
        __tablename__ = "tbl_books"
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        author_id = db.Column(db.Integer, db.ForeignKey("tbl_authors.id"))
    
    
    # 创建表单模型类
    class AuthorBookForm(FlaskForm):
        """作者数据表单模型类"""
        author_name = StringField(label="作者", validators=[DataRequired("必填")])
        book_name = StringField(label="书籍", validators=[DataRequired("必填")])
        submit = SubmitField(label="保存")
    
    
    @app.route("/", methods=["GET", "POST"])
    def index():
        # 创建表单对象
        form = AuthorBookForm()
    
        if form.validate_on_submit():
            """表单验证成功"""
            # 提取数据
            author_name = form.author_name.data
            book_name = form.book_name.data
    
            # 保存数据
            author = Author(name=author_name)
            db.session.add(author)
            db.session.commit()
    
            book = Book(name=book_name, author_id=author.id)
            # book = Book(name=book_name, author=author)
            db.session.add(book)
            db.session.commit()
    
        # 查询数据
        authors_list = Author.query.all()
        return render_template("index.html", authors_list=authors_list, form=form)
    
    
    @app.route("/delete_book", methods=["POST"])
    def delete_book():
        """删除数据"""
        # 提取json参数,如果前端发送请求体格式json,get_json()自动解析json成字典
        req_dict = request.get_json()
        book_id = req_dict.get("book_id")
    
        # 删除数据
        book = Book.query.get(book_id)
        db.session.delete(book)
        db.session.commit()
    
        return jsonify(code=0, message="OK")
    
    
    if __name__ == '__main__':
        app.run()
        # db.drop_all()
        # db.create_all()
        #
        # au_jin = Author(name="金庸")
        # au_tian = Author(name="天蚕土豆")
        # au_luo = Author(name="罗贯中")
        # db.session.add_all([au_jin, au_tian, au_luo])
        # db.session.commit()
        #
        # bk_tian = Book(name="天龙八部", author_id=au_jin.id)
        # bk_she = Book(name="射雕三部曲", author_id=au_jin.id)
        # bk_dou = Book(name="斗破苍穹", author_id=au_tian.id)
        # bk_san = Book(name="三国演义", author_id=au_luo.id)
        # db.session.add_all([bk_tian, bk_she, bk_dou, bk_san])
        # db.session.commit()
    
    
    • 前端
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <form action="" method="post" novalidate>
        {{ form.csrf_token }}
    
        {{ form.author_name.label }}
        <p>{{ form.author_name }}</p>
        {% for msg in form.author_name.errors %}
            <p>{{ msg }}</p>
        {% endfor %}
    
        {{ form.book_name.label }}
        <p>{{ form.book_name }}</p>
        {% for msg in form.book_name.errors %}
            <p>{{ msg }}</p>
        {% endfor %}
    
        {{ form.submit }}
    </form>
    
    <br/>
    <ul>
        {% for author in authors_list %}
            <li>作者:{{ author.name }}</li>
            <ul>
                {% for book in author.books %}
                    <li>书籍:{{ book.name }}</li>
                    <a href="javascript:;" book-id={{ book.id }}>删除</a>
                {% endfor %}
            </ul>
        {% endfor %}
    </ul>
    
    
    <script src="/static/js/jquery-3.5.1.js" type="text/javascript"></script>
    
    <script>
        $("a").click(
            function () {
                var data = {
                    book_id: $(this).attr("book-id")
                };
                var req_json = JSON.stringify(data);
                $.ajax({
                    url: "/delete_book",
                    type: "post",
                    data: req_json,
                    contentType: "application/json",
                    dataType: "json",
                    success: function (response) {
                        if (response.code == 0) {
                            alert("删除完成");
                            location.href = "/";
                        }
                    }
                })
            }
        )
    </script>
    
    </body>
    </html>
    

    二十、数据库maigrate扩展

    • 数据库模块类修改后可以通过maigrate同步到数据库
    依赖flask-script
    >pip install flask-migrate
    
    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    from flask_script import Manager
    from flask_migrate import Migrate, MigrateCommand
    
    app = Flask(__name__)
    
    
    # 使用类定义配置
    class Config(object):
        """配置参数"""
        SQLALCHEMY_DATABASE_URI = "mysql://flask:flask@192.168.100.1:3306/flask"
        SQLALCHEMY_TRACK_MODIFICATIONS = True
        # SQLALCHEMY_ECHO = True
    
    
    # 注册配置
    app.config.from_object(Config)
    
    # 创建数据库sqlalchemy工具对象
    db = SQLAlchemy(app)
    
    # 创建flask脚本管理工具对象
    manager = Manager(app)
    
    # 创建数据库迁移工具对象
    Migrate(app, db)
    
    # 向manager对象中添加数据库的操作命令
    manager.add_command("db", MigrateCommand)
    
    
    # 创建模型类
    class Role(db.Model):
        """角色表"""
        __tablename__ = "tbl_roles"
    
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(32), unique=True)
        users = db.relationship("User", backref="role")  # User: 对应模型类关系, backref="role": 可以通过User类反推
    
        def __repr__(self):
            """可以让显示对象的时候更直观"""
            return "Role object: name=%s" % self.name
    
    
    class User(db.Model):
        """用户表"""
        __tablename__ = "tbl_users"  # 指定数据库的表名
    
        id = db.Column(db.Integer, primary_key=True)  # 整型主键会默认自增
        name = db.Column(db.String(64), unique=True)
        age = db.Column(db.Integer, unique=True)
        email = db.Column(db.String(128), unique=True)
        password = db.Column(db.String(128), nullable=False)
        role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))  # 定义外键, tbl_roles: 表名
    
        def __repr__(self):
            return "User object: name=%s" % self.name
    
    
    if __name__ == '__main__':
        # 通过manager对象启动程序
        manager.run()
    
    
    >python 18_数据库maigrate扩展.py db init  # 初始化
    >python 18_数据库maigrate扩展.py db migrate -m 'add age column'  # 生成迁移文件
    >python 18_数据库maigrate扩展.py db upgrade  # 同步数据库
    >python 18_数据库maigrate扩展.py db history  # 查询历史版本
    >python 18_数据库maigrate扩展.py db downgrade 3a5dde35dad1  # 回滚
    

    二十一、蓝图

    1、蓝图的基本使用

    • main.py
    from flask import Flask
    from orders import app_orders
    
    app = Flask(__name__)
    
    # 注册蓝图
    # app.register_blueprint(app_orders)
    
    # 添加前缀 http://127.0.0.1:5000/orders/get_orders
    app.register_blueprint(app_orders, url_prefix="/orders")
    
    if __name__ == '__main__':
        print(app.url_map)
        app.run()
    
    
    • orders.py
    from flask import Blueprint
    
    # 创建一个蓝图的对象
    app_orders = Blueprint("app_orders", __name__)
    
    
    @app_orders.route("/get_orders")
    def get_orders():
        return "orders page"
    
    

    2、以目录形式定义蓝图

    • main.py
    from flask import Flask
    from orders import app_orders
    from cart import app_cart
    
    app = Flask(__name__)
    
    # 注册蓝图
    # app.register_blueprint(app_orders)
    
    # 添加前缀 http://127.0.0.1:5000/orders/get_orders
    app.register_blueprint(app_orders, url_prefix="/orders")
    app.register_blueprint(app_cart, url_prefix="/cart")
    
    
    if __name__ == '__main__':
        print(app.url_map)
        app.run()
    
    
    • cart/__init.py
    from flask import Blueprint
    
    
    app_cart = Blueprint("app_cart", __name__, template_folder="templates")
    
    # 在__init__被执行的时候,把视图加载进来,让蓝图发现views存在
    from .views import get_cart
    
    • cart/views.py
    from . import app_cart
    from flask import render_template
    
    @app_cart.route("get_cart")
    def get_cart():
        return render_template("cart.html")
    

    二十二、单元测试

    1、页面的单元测试

    • login.py
    from flask import Flask
    from flask import request
    from flask import jsonify
    
    app = Flask(__name__)
    
    
    @app.route("/login", methods=["POST"])
    def login():
        # 接收参数
        username = request.form.get("username")
        password = request.form.get("password")
    
        # 参数判断
        if not all([username, password]):  # 列表中的变量都为真,all返回真
            resp = {
                "code": 1,
                "message": "invalid params"
            }
            return jsonify(resp)
    
        if username == "admin" and password == "admin":
            resp = {
                "code": 0,
                "message": "login success"
            }
            return jsonify(resp)
        else:
            resp = {
                "code": 2,
                "message": "wrong useername or password"
            }
            return jsonify(resp)
    
    
    if __name__ == '__main__':
        app.run()
    
    
    • test.py
    import unittest
    from login import app
    import json
    
    
    class LoginTest(unittest.TestCase):
        """构造单元测试案例"""
    
        def setUp(self):
            """在进行测试之前先被执行"""
            # 设置flask工作在测试模式
            # app.config["TESTING"] = True
            app.testing = True
    
            # 创建web请求的客户端
            self.client = app.test_client()
    
        def test_empty_username_password(self):
            """测试用户名密码不完整情况"""
            # 测试用户名密码都不传
            # 利用client模拟发送web请求
            ret = self.client.post("/login", data={})
    
            # ret是视图返回的响应对象,ret.data是响应数据
            resp = ret.data
            resp = json.loads(resp)
    
            # 开始断言测试
            self.assertIn("code", resp)  # "code"是否在resp中
            self.assertEqual(resp["code"], 1)  # resp["code"]是否等于1
    
            # 测试只传用户名
            # 利用client模拟发送web请求
            ret = self.client.post("/login", data={"username": "admin"})
    
            # ret是视图返回的响应对象,ret.data是响应数据
            resp = ret.data
            resp = json.loads(resp)
    
            # 开始断言测试
            self.assertIn("code", resp)  # "code"是否在resp中
            self.assertEqual(resp["code"], 1)  # resp["code"]是否等于1
    
            # 测试只传密码
            # 利用client模拟发送web请求
            ret = self.client.post("/login", data={"password": "admin"})
    
            # ret是视图返回的响应对象,ret.data是响应数据
            resp = ret.data
            resp = json.loads(resp)
    
            # 开始断言测试
            self.assertIn("code", resp)  # "code"是否在resp中
            self.assertEqual(resp["code"], 1)  # resp["code"]是否等于1
    
        def test_wrong_username_password(self):
            """测试用户名或密码错误"""
            # 密码错误的情况
            ret = self.client.post("/login", data={"username": "admin", "password": "123456"})
            resp = ret.data
            resp = json.loads(resp)
            print(resp["code"])
    
            # 断言
            self.assertIn("code", resp)
            self.assertEqual(resp["code"], 2)
    
    
    if __name__ == '__main__':
        unittest.main()  # 执行所有测试案例
    
    

    2、数据库测试

    • db_demo.py
    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    
    app = Flask(__name__)
    
    
    # 使用类定义配置
    class Config(object):
        """配置参数"""
        SQLALCHEMY_DATABASE_URI = "mysql://flask:flask@192.168.100.1:3306/flask"
        SQLALCHEMY_TRACK_MODIFICATIONS = True
        # SQLALCHEMY_ECHO = True
    
    
    # 注册配置
    app.config.from_object(Config)
    
    # 创建数据库sqlalchemy工具对象
    db = SQLAlchemy(app)
    
    
    # 创建模型类
    class Role(db.Model):
        """角色表"""
        __tablename__ = "tbl_roles"
    
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(32), unique=True)
        users = db.relationship("User", backref="role")  # User: 对应模型类关系, backref="role": 可以通过User类反推
    
        def __repr__(self):
            """可以让显示对象的时候更直观"""
            return "Role object: name=%s" % self.name
    
    
    class User(db.Model):
        """用户表"""
        __tablename__ = "tbl_users"  # 指定数据库的表名
    
        id = db.Column(db.Integer, primary_key=True)  # 整型主键会默认自增
        name = db.Column(db.String(64), unique=True)
        email = db.Column(db.String(128), unique=True)
        password = db.Column(db.String(128), nullable=False)
        role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))  # 定义外键, tbl_roles: 表名
    
        def __repr__(self):
            return "User object: name=%s" % self.name
    
    
    • test_db_demo.py
    import unittest
    from db_demo import User, db, app
    
    
    class DatabaseTest(unittest.TestCase):
        def setUp(self):
            app.testing = True
            # 使用测试的数据库测试,不影响生产的数据库
            app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://flask:flask@192.168.100.1:3306/flask_test"
            db.create_all()
    
        def test_add_user(self):
            """添加用户的数据库测试"""
            # 添加数据
            user = User(name="root", email="root@localhost", password="123456")
            db.session.add(user)
            db.session.commit()
    
            import time
            time.sleep(20)
    
            # 查询数据是否存在
            result_user = User.query.filter_by(name="root").first()
            self.assertIsNotNone(result_user)
    
        def tearDown(self):
            """所有的测试执行后执行,通常用来执行清理操作"""
            db.session.remove()
            db.drop_all()
    
    
    if __name__ == '__main__':
        unittest.main()
    
    

    二十三、flask部署

    >pip install gunicorn
    >gunicorn -w 4 -b 127.0.0.1:5000 -D --access-logfile ./logs/access.log main:app
    
    -w : 进程数
    -b : 监听
    -D : 后台运行
    --access-logfile : 日志文件
    main:app : 主文件:app实例
    
  • 相关阅读:
    YbSoftwareFactory 代码生成插件【二十】:DynamicObject的序列化
    YbSoftwareFactory 代码生成插件【十九】:实体类配合数据库表字段进行属性扩展的小技巧
    YbSoftwareFactory 代码生成插件【十八】:树形结构下的查询排序的数据库设计
    YbSoftwareFactory 代码生成插件【十七】:先进的权限模型体系设计
    YbSoftwareFactory 代码生成插件【十六】:Web 下灵活、强大的审批流程实现(含流程控制组件、流程设计器和表单设计器)
    YbSoftwareFactory 代码生成插件【十五】:Show 一下最新的动态属性扩展功能与键值生成器功能
    YbSoftwareFactory 代码生成插件【十四】:通过 DynamicLinq 简单实现 N-Tier 部署下的服务端数据库通用分页
    YbSoftwareFactory 代码生成插件【十三】:Web API 的安全性
    Navicat 密码加密算法
    GitHub开源的超棒后台管理面板
  • 原文地址:https://www.cnblogs.com/L-dongf/p/13132107.html
Copyright © 2020-2023  润新知