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实例
posted @ 2020-06-15 17:19  生生不息.连绵不绝  阅读(362)  评论(0编辑  收藏  举报