Flask是基于Werkzeug工具箱开发的一个轻量级的web开发框架。
Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL
其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。
Flask有两大核心:Werkzeug和Jinja2
- Werkzeug实现路由、调试和Web服务器网关接口
- Jinja2实现了模板。
Werkzeug是一个遵循WSGI协议的python函数库
- 其内部实现了很多Web框架底层的东西,比如request和response对象;
- 与WSGI规范的兼容;支持Unicode;
- 支持基本的会话管理和签名Cookie;
- 集成URL请求路由等。
Werkzeug库的 routing 模块负责实现 URL 解析。不同的 URL 对应不同的视图函数,routing模块会对请求信息的URL进行解析,匹配到URL对应的视图函数,执行该函数以此生成一个响应信息。
routing模块内部有:
- Rule类
用来构造不同的URL模式的对象,路由URL规则 - Map类
存储所有的URL规则和一些配置参数 - BaseConverter的子类
负责定义匹配规则 - MapAdapter类
负责协调Rule做具体的匹配的工作
Flask常用扩展包:
- Flask-SQLalchemy:操作数据库;
- Flask-script:插入脚本;
- Flask-migrate:管理迁移数据库;
- Flask-Session:Session存储方式指定;
- Flask-WTF:表单; Flask-Mail:邮件;
- Flask-Bable:提供国际化和本地化支持,翻译;
- Flask-Login:认证用户状态;
- Flask-OpenID:认证;
- Flask-RESTful:开发REST API的工具;
- Flask-Bootstrap:集成前端Twitter Bootstrap框架;
- Flask-Moment:本地化日期和时间;
- Flask-Admin:简单而可扩展的管理接口的框架
Flask中的路由定义是靠装饰器来定义的:@app.route(’< URL >’) 其中 URL 显式支持 string、int、float、path uuid any 6 种类型,隐式支持正则。
在使用 Flask 写一个接口时候需要给客户端返回 JSON 数据,在 Flask 中可以直接使用 jsonify 生成一个 JSON 的响应:
# 返回JSON
@app.route('/demo4')
def demo4():
json_dict = {
"user_id": 10,
"user_name": "laowang"
}
return jsonify(json_dict)
重定向:
- 可以直接填写自己 url 路径
- 也可以使用 url_for 生成指定视图函数所对应的 url
使用正则匹配路由
1,、导入转换器基类,并自定义转换器
2、添加转换器到默认的转换器字典中,并指定转换器使用时名字为: re
app = Flask(__name__)
# 将自定义转换器添加到转换器字典中,并指定转换器使用时名字为: re
app.url_map.converters['re'] = RegexConverter
3、使用转换器去实现自定义匹配规则
当前此处定义的规则是:3位数字
@app.route('/user/<re("[0-9]{3}"):user_id>')
def user_info(user_id):
return "user_id 为 %s" % user_id
请求勾子:
作用是在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理:
- 在请求开始时,建立数据库连接;
- 在请求开始时,根据需求进行权限校验;
- 在请求结束时,指定数据的交互格式;
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
- before_first_request :在第一次请求时调用
- before_request :在每次请求前执行
- after_request :如果没有抛出错误,在每次请求后执行
- teardown_request :在每次请求后执行
Jinja2模板介绍
模板:其实是一个包含响应文本的文件,其中用占位符(变量)表示动态部分,告诉模板引擎其具体的值需要从使用的数据中获取,使用真实值替换变量,再返回最终得到的字符串,这个过程称为“渲染”。Flask是使用 Jinja2 这个模板引擎来渲染模板。
使用模板的好处:
- 使视图函数只负责业务逻辑和数据处理(业务逻辑方面)
- 而模板则取到视图函数的数据结果进行展示(视图展示方面)
- 代码结构清晰,耦合度低
注:模板的使用前可将其设置为模板文件,在使用时以便有智能提示。
过滤器
过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。
使用方式:变量名 | 过滤器
{{variable | filter_name(*args)}}
jinja2中,过滤器可以支持链式调用。
模板代码复用
在模板中可能会遇到以下情况:
- 多个模板具有完全相同的顶部和底部内容
- 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
- 多个模板中具有完全相同的 html 代码块内容
像这种情况,可以使用Jinja2模板中的 宏、继承、包含 来进行实现
宏
对宏的理解:
- 把它看作 Jinja2 中的一个函数,它会返回一个模板或者 HTML 字符串
- 为了避免反复地编写同样的模板代码,出现代码冗余,可以把他们写成函数以进行重用
- 需要在多处重复使用的模板代码片段可以写入单独的文件,再包含在所有模板中,以避免重复
使用:
- 定义 宏
{% macro input(name,value='',type='text') %}
<input type="{{type}}" name="{{name}}"
value="{{value}}" class="form-control">
{% endmacro %}
- 调用 宏
将 需要的值 传入
{{ input('name' value='zs')}}
- 这样的输出为
<input type="text" name="name"
value="zs" class="form-control">
- 把宏单独抽取出来,封装成html文件,其它模板中导入使用,文件名可以自定义macro.html
{% macro function(type='text', name='', value='') %}
<input type="{{type}}" name="{{name}}"
value="{{value}}" class="form-control">
{% endmacro %}
- 在其它模板文件中先导入,再调用
{% import 'macro.html' as func %}
{% func.function() %}
模板继承
模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
- 相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。
- 子模板使用 extends 指令声明这个模板继承自哪个模板
- 父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()
使用:
父模板:
创建base.html作为父模板,在其中创建具体模板
{% block top %} # top是给模板起的名字
顶部菜单
{% endblock top %}
子模板:
extends指令声明这个模板继承自哪
{% extends 'base.html' %}
{% block top %} # 继承父模板中的top模板
需要填充的内容
{% endblock top %}
包含
Jinja2模板中,除了宏和继承,还支持一种代码重用的功能,叫包含(Include)。它的功能是将另一个模板整个加载到当前模板中,并直接渲染。
- include的使用
{% include 'hello.html' %}
- include 的使用加上关键字ignore missing
{% include 'hello.html' ignore missing %}
注:包含在使用时,如果包含的模板文件不存在时,程序会抛出TemplateNotFound异常,可以加上 ignore missing 关键字。如果包含的模板文件不存在,会忽略这条include语句。
session:在Flask视图中设置session的前提是要设置好secret_key,这是用作生成session的密钥
app.secret_key = "随便写"
视图中:
session["name"] = “laowang” # 此处以neme举例
模板中特有的变量和函数:config 、request 、session 、g变量 、url_for() 、get_flashed_messages() 。
这些变量可以在模板中直接获取而不用传值,这点非常方便。现对于django来说省了序列化等相应操作。
g变量:在视图函数中设置g变量的name属性的值,然后可以在模板中直接取出:
{{ g.name }}
Web表单
Web 表单是 Web 应用程序的基本功能。它是HTML页面中负责数据采集的部件。表单有三个部分组成:表单标签、表单域、表单按钮。表单允许用户输入数据,负责HTML页面数据采集,通过表单将用户输入的数据提交给服务器。
在Flask中,为了处理web表单,我们可以使用 Flask-WTF 扩展,它封装了 WTForms,并且它有验证表单数据的功能
使用Flask-WTF扩展时是将一个表单当做一个对象来处理,使用时导入wtforms中的相应字段以供定义的类使用。
定义:
可以添加使用验证器进行表单验证
from flask_wtf import FlaskForm
#导入自定义表单需要的字段
from wtforms import SubmitField,StringField,PasswordField
#导入wtf扩展提供的表单验证器
from wtforms.validators import DataRequired,EqualTo
#自定义表单类,文本字段、密码字段、提交按钮
class RegisterForm(FlaskForm):
username = StringField("用户名:", validators=[DataRequired("请输入用户名")], render_kw={"placeholder": "请输入用户名"})
password = PasswordField("密码:", validators=[DataRequired("请输入密码")])
password2 = PasswordField("确认密码:", validators=[DataRequired("请输入确认密码"), EqualTo("password", "两次密码不一致")])
submit = SubmitField("注册")
使用:
@app.route('/demo2', methods=["get", "post"])
def demo2():
register_form = RegisterForm()
# 验证表单
if register_form.validate_on_submit():
# 如果代码能走到这个地方,那么就代码表单中所有的数据都能验证成功
username = request.form.get("username")
password = request.form.get("password")
password2 = request.form.get("password2")
# 假装做注册操作
print(username, password, password2)
return "success"
else:
if request.method == "POST":
flash("参数有误或者不完整")
return render_template('temp_register.html', form=register_form)
CSRF:全拼为Cross Site Request Forgery,译为跨站请求伪造。指攻击者盗用了你的身份,以你的名义发送恶意请求。
防止 CSRF 攻击:
- 在客户端向后端请求界面数据的时候,后端会往响应中的 cookie 中设置 csrf_token 的值
- 在 Form 表单中添加一个隐藏的的字段,值也是 csrf_token
- 在用户点击提交的时候,会带上这两个值向后台发起请求
- 后端接受到请求,以会以下几件事件:
- 从 cookie中取出 csrf_token
- 从 表单数据中取出来隐藏的 csrf_token 的值
- 进行对比
- 如果比较之后两值一样,那么代表是正常的请求,如果没取到或者比较不一样,代表不是正常的请求,不执行下一步操作
技巧就是,攻击者可以利用受害者的cookie但不能取得具体cookie,也不知道表单中隐藏你的csrf_token,所以就不能通过csrf校验。
ORM
ORM 全拼Object-Relation Mapping.中文意为 对象-关系映射.主要实现模型对象到关系数据库数据的映射.
SQLALchemy 实际上是对数据库的抽象,让开发者不用直接和 SQL 语句打交道,而是通过 Python 对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升,SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作
flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。
安装:
安装 flask-sqlalchemy: pip install flask-sqlalchemy
如果连接的是 mysql 数据库,需要安装 mysqldb: pip install flask-mysqldb
数据库连接设置:
在 Flask-SQLAlchemy 中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的 SQLALCHEMY_DATABASE_URI 键中
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'
在Flask-SQLAlchemy中,插入、修改、删除操作,均由数据库会话管理。会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 commit() 方法提交会话。在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。
模型之间的关联
一对多:外键设在多的一方
# 一的一方
class Role(db.Model):
...
#关键代码
us = db.relationship('User', backref='role', lazy='dynamic')
...
# 多的一方
class User(db.Model):
...
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
- 其中realtionship描述了Role和User的关系。在此文中,第一个参数为对应参照的类"User"
- 第二个参数backref为类User申明新属性的方法
- 第三个参数lazy决定了什么时候SQLALchemy从数据库中加载数据
- 如果设置为子查询方式(subquery),则会在加载完Role对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
- 设置为 subquery 的话,role.users 返回所有数据列表
- 另外,也可以设置为动态方式(dynamic),这样关联对象会在被使用的时候再进行加载,并且在返回前进行过滤,如果返回的对象数很多,或者未来会变得很多,那最好采用这种方式
- 设置为 dynamic 的话,role.users 返回查询对象,并没有做真正的查询,可以利用查询对象做其他逻辑,比如:先排序再返回结果
- 如果设置为子查询方式(subquery),则会在加载完Role对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
多对多:需要添加单独的一张表去记录两张表之间的对应关系。
- 定义关系表:使用db.table创建一个单独的一张表,这样创建出来就是与映射没有关系,避免错误操作。
tb_student_course = db.Table('tb_student_course',
db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
)
- 定义数据表:使用模型去创建表
class Student(db.Model):
__tablename__ = "students"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
# 核心:将定义的关系表跟数据表联系起来
courses = db.relationship('Course', secondary=tb_student_course,
backref='student',
lazy='dynamic')
class Course(db.Model):
__tablename__ = "courses"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
数据库迁移
使用数据库迁移的目的是为了在修改之后更新数据库而不删除旧表,使用数据库迁移框架可以追踪数据库模式的变化,然后将变动应用到数据库中.
在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
- 安装Flask-Migrate
pip install flask-migrate
- 使用代码:
#coding=utf-8
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate,MigrateCommand
from flask_script import Shell,Manager
app = Flask(__name__)
# 使用flask_script中的Manager创建终端命令对象
manager = Manager(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/Flask_test'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
#第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例 ,使用Maigrate将app与db关联起来
migrate = Migrate(app,db)
#manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
manager.add_command('db',MigrateCommand) # db是自己命名的可随意,在db命令中添加了MigrateCommand命令
- 创建迁移仓库
#这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python database.py db init
- 创建迁移脚本
-m 后面跟的是这一步操作的说明
python database.py db migrate -m 'initial migration'
- 更新数据库:执行迁移
python database.py db upgrade
信号机制
Flask信号(signals, or event hooking)允许特定的发送端通知订阅者发生了什么信号依赖于Blinker库.
信号应用举例:
Flask-User 这个扩展中定义了名为 user_logged_in 的信号,当用户成功登入之后,这个信号会被发送。我们可以订阅该信号去追踪登录次数和登录IP:
Flask-User 这个扩展中定义了名为 user_logged_in 的信号,当用户成功登入之后,这个信号会被发送。我们可以订阅该信号去追踪登录次数和登录IP:
蓝图(Blueprint)
蓝图产生的原因:传统的模块导入对于Flask并不适用,因为Flask中的视图函数都是由app.route进行装饰的,所以仅仅是将模块导入并不能正常运行.故: flask提供一个特有的模块化处理方式,flask内置了一个模块化处理的类,即Blueprint
Blueprint概念: 它是一个存储操作方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。
Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
- 一个应用可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名
- 在一个应用中,一个模块可以注册多次
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个应用初始化时,就应该要注册需要使用的Blueprint
但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
使用蓝图步骤: - 1,创建一个蓝图对象
admin=Blueprint('admin',__name__) # admin为给蓝图起的名字
- 2,在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模板过滤器
@admin.route('/') # 使用蓝图进行装饰路由
def admin_home():
return 'admin_home'
- 3,在应用对象上注册这个蓝图对象
这一步是在main.py主文件上操作的,目的是让蓝图跟app进行关联
app.register_blueprint(admin,url\_prefix='/admin')
当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数
注册静态路由
和应用对象不同,蓝图对象创建是不会默认注册静态目录的路由.需要我们在创建时指定static_folder参数.例;
admin = Blueprint("admin",__name__,static_folder='static_admin')
app.register_blueprint(admin,url_prefix='/admin')
单元测试
单元测试就是开发者编写一小段代码,检验目标代码的功能是否符合预期
在Web开发过程中,单元测试实际上就是一些“断言”(assert)代码。
断言就是判断一个函数或对象的一个方法所产生的结果是否符合你期望的那个结果。 python中assert断言是声明布尔值为真的判定,如果表达式为假会发生异常。单元测试中,一般使用assert来断言结果。