137. Flask
第一个flask程序
Flask(__name__)
1 | # 初始化一个Flask对象 |
@app.route()
1 | # @app.route是一个装饰器(可以看成url的后缀:127.0.0.1:5000/login 的 /login) |
app.run()
:
启动一个应用服务器,来接收用户的请求。
类似于:
1 | while (True): |
设置debug模式
- 在
app.run()
中传入一个关键字参数debug
,app.run(debug=True)
, 就设置当前项目为debug
模式 debug
模式的两大功能- 当程序出现问题时,可以在也米那种看到错误信息和出错的位置
- 只要修改了项目中的
Python
文件,程序会自动加载,不需要手动重新启动服务器
使用配置文件
新建一个
.py
文件,这里设置为config.py
然后设置大写,如:
DEBUG=True
与
app
进行关联1
2
3
4# 假设配置文件为config
import config
app.config.from_object(config)还有许多的其他参数,都是放在这个配置文件中,比如
SECRET_KEY
和SQLALCHEMY
这些配置都是在这个文件中
URL
传参
参数的作用:可以在相同的
url
,但是指定不同的参数,来加载不同的数据在flask中如何使用参数
1
2
3
def article(id):
return "您请求的参数是:%s" %id- 参数需要放在两个尖括号中
- 视图函数中需要放和
url
中的参数同名的参数
反转URL
正转
通过url
可以取到视图函数的内容
反转
- 从视图函数到
url
的转换叫做反转url
(通过视图函数的名称,可以得到指向的url
) - 用处
- 在页面重定向的时候,会使用
url
反转 - 在模板中也会使用
url
反转
- 在页面重定向的时候,会使用
函数讲解
url_for()
:params
:视图函数名称, 视图函数对应的参数(如果视图函数有参数的话),如url_for("article", id="123")
return
: 视图函数对应的url
页面跳转和重定向
用处:在用户访问一些需要登录的页面的时候,如果用户没有登录,那么可以让他重定向到登录页面
实现
1
2
3
4
5
6
7
8from flask import Flask, redirect, url_for
def question(isLogin):
if isLogin == '1':
return "这是发布问答页面"
else:
return redirect(url_for("login"))
函数讲解
redirect()
params
:url
,如redirect("/login123/")
或者redirect(url_for("login"))
,建议使用第二种,只要视图函数名字不变,@app.route(/login123/)
里面的login123
怎么变都没关系
模板渲染和参数
如何渲染模板
模板放在
templates
文件夹下从
flask
中导入render_template
函数在试图函数中,使用
render_template
函数渲染模板。注意:只需要填写模板的名字,不要填写
templates
这个文件夹的路径。如果在templates
文件夹下创建了一个新的文件夹,那么就要添加上文件名(render_template()
函数的文件名是相对路径)
模板传参
- 如果只有一个或者少量参数,直接在
render_template()
函数中添加关键字参数就可以了 - 如果有多个参数的时候,那么可以先把所有的参数放在字典中,然后在
render_template
中,使用**字典名
把字典转换为关键参数传递进去,这样的代码更方便管理和应用
函数讲解
render_template()
params
: 渲染文件名(渲染文件放在template文件夹下),username = 用户名
(username
保证和渲染文件中的变量名保持一致), … 。如render_template("index.html", username = "Qeuroal")
,- 如果参数特别多的话,可以先定义一个字典(
context
),然后把字典传入函数内,如render_template("index.html", **contet)
模板访问属性和字典
在模板中如果要使用一个变量,语法是 {{ params }}
- 访问类的属性:
- 和在Python文件中一致
- 或者类似于这样:
Person["name"]
- 访问字典:
- 和
py文件
一样:dict[key]
dict.key
- 和
实现
template1.py
1 |
|
index.html
1 |
|
HTML
if判断
语法
1 | {% if xxx and xxx %} |
if的使用和python中相差无几。
for循环遍历列表和字典
字典遍历
字典的遍历,语法和
python
一样,可以使用items()
,keys()
,values()
,iteritems()
,iterkeys()
,itervalues()
1 | {% for k,v in user.items() %} |
列表遍历
语法和
python
一样
1 | {% for website in websites %} |
过滤器
作用对象是模板中的变量:
![]()
介绍和语法
介绍:过滤器可以处理变量,把原始的遍历经过处理后再展示出来。作用的对象是变量
语法:
1
{{ avator|default("xxx")}}
default过滤器
如果当前变量不存在,这时候可以指定默认值。
length过滤器
求列表,字符串,字典,元组的长度。类似于:python
中的 len()
常用的过滤器
abs(value)
default(value, defaultValue, boolean=false)
escape(value)
first(value)
format(value, *args, **kwargs)
last(value)
length(value)
join(value, d="")
safe(value)
继承和block
继承
作用:可以把一些公共的代码放在父模板中,避免每个模板写同样的代码
语法:
1
{% extends "base.html" %}
如果想在子模板中实现自己的内容:需要定义接口,类似于
java
的接口:html子模板需要实现自己内容,必须要放在父模板定义的接口里面,即block,不能在接口外面写代码
block
- 作用:可以让子模板实现一些自己的需求。父模板需要提前定义好。
- 注意点:
- 子模板中的代码,必须放在block块中;
- 可以定义多个
block
URL链接
使用 url_for(视图函数名称)
可以反转成url。
加载静态文件
1 | <style> |
样式代码一般不会写在模板中,因为:维护性差,一般样式文件抽取成 css
文件(static
文件夹中是存放静态文件的)
语法
url_for("static", filename="路径")
如:
url_for("static", filename="css/index.css")
静态文件,flask会从
static
文件夹中开始寻找,所以不需要写static
这个路径了可以加载
css
文件,js
文件,image
文件。
示例代码
1 | {# 加载css文件 #} |
注意点
装饰器
- 装饰器为
@app.route('/login/')
,网址可以是:http://127.0.0.1:5000/login
和http://127.0.0.1:5000/login/
- 装饰器为
@app.route('/login')
,网址只能是:http://127.0.0.1:5000/login/
MySQL
安装(win10)
我选择的是
Server only
设置密码:
打开命令行,并输入上一步设置好的密码
一般直接下一步
设置初始密码命令:
1
mysqladmin -uroot password [password]
如果没有安装
.net Framework 4
,就在那个提示框中,找到下载的url
下载;如果没有安装
Microsoft visual C++ x64
,那么就需要谷歌或者百度下载这个软件进行安装即可。
MySQL-python中间件介绍与安装
linux, macos
- 进入虚拟环境
- 输入
pip install mysql-python
windows系统
windows
在这里下载,看python
安装的是32位还是64位,对应下载- 进入虚拟环境:
conda activate 虚拟环境名
- 安装:
pip install mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl
Flask-SQLAlchemy的介绍与安装
ORM
:Object Relationship Mapping
(关系模型映射);flask-sqlalchemy
是一套ORM
框架模型关系映射
delete(article1)
:把article
表中和article1
属性对应相同的那条数据删除。- 好处:ORM使得我们操作数据库非常简单,就像操作对象是一样的,非常方便。因为一个表就抽象成了一个类,一条数据就抽象成了该类的一个对象。
安装:
pip install flask-sqlalhemy
,如果是在macos
或者linux
中没有权限的话这样安装:sudo pip install flask-sqlalhemy
Flask-SQLAlchemy的使用
初始化和设置数据库配置信息:
使用
flask_sqlalchemy
中的SQLAlchemy
进行初始化1
2
3from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
设置配置信息:在
config.py
文件中添加以下配置信息1
2
3
4
5
6
7
8
9
10
11
12# dialect+driver://username:password@host:port/database
DIALECT = "mysql"
DRIVER = "mysqldb"
USERNAME = "root"
PASSWORD = "toor"
HOST = "127.0.0.1"
PORT = "3306"
DATABASE = "dbDemo1"
SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
# 去除警告
SQLALCHEMY_TRACK_MODIFICATIONS = False添加配置文件
1
2
3import config
app.config.from_object(config)测试是否连接成功
1
db.create_all()
如果没有报错,说明配置没有问题,如果有错误,可以根据错误进行修改。
建表
cmd
进入mysql
:mysql -uroot -p
create database dbDemo1 charset utf8
SQLAlchemy创建模型与表的映射
步骤
模型需要继承自
db.Model
,然后需要映射到表中的属性,必须写成db.Column()
的数据类型数据类型:
db.Integer
: 整型db.Sting
:varchar
,char
,需要指定最长的长度db.Text
:text
其他参数
primary_key
: 将这个字段设置为主键autoincrement
: 这个主键为自增长nullable
: 这个字段是否可以为空,默认为空,可以将这个值设置为False
,在数据库中,这个值就不能为空了
最后需要调用
db.create_all()
来将模型真正的创建到数据库中,db.create_all()
只会映射一次。
实例
1 | from flask import Flask, render_template |
SQLAlchemy数据增删改查
增
1 | # 增加 |
查
1 | # 返回的是Query(<==> list); .all()查找所有数据, .first()返回的第一条数据,如果没有,返回null |
改
1 | # 1. 先把要修改的地方查找出来 |
删
1 | # 1. 把需要删除的数据查找出来 |
注意
- 如果把增删改查放在
@app.route("/")
类似的视图函数中,访问一次url
,就会执行一遍相应的增删改查
Flask-SQLAlchemy外键及其关系
外键
1 | """ |
解释
authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
:- 给
Article
这个模型添加一个author
属性,可以访问这篇文章的作者的数据,像访问普通模型一样 backref
:定义反向引用,可以通过User.articles
这个模型访问这个模型缩写的所有文章
- 给
补充
添加数据(方法二):
1
2
3
4article = Article(title="aaa", content="bbb")
article.author = User.query.filter(User.id==1).first()
db.session.add(article)
db.session.commit()
多对多
例子

在这张图中:

使用
多对多的关系,要通过一个中间表进行关联;
中间表,不能通过
class
的方式实现,只能通过db.Table
的方式实现设置关联:
1
tags = db.relationship("Tag", secondary=articleTagRelation, backref=db.backref("articles"))
需要使用一个关键字参数
secondary=中间表
进行关联访问和添加可以通过以下方式操作:
添加数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16article1 = Article(title="aaa")
article2 = Article(title="bbb")
tag1 = Tag(name="111")
tag2 = Tag(name="222")
article1.tags.append(tag1)
article1.tags.append(tag2)
article2.tags.append(tag1)
article2.tags.append(tag2)
db.session.add(article1)
db.session.add(article2)
db.session.add(tag1)
db.session.add(tag2)
db.session.commit()访问数据
1
2
3
4article1 = Article.query.filter(Article.title=="aaa").first()
tags = article1.tags
for tag in tags:
print(tag.name)
Flask-Script的介绍和安装
介绍
Flask-Script
的作用是可以通过命令行的形式来操作 Flask
。例如通过命令跑一个开发版本的服务器、设置数据库、定时任务。
安装
- 进入到虚拟环境中
pip install flask-script
进行安装
使用
如果直接在主
manage.py
中写命令,那么在终端就只需要python manage.py commandName
就可以了。如果把一些命令集中在一个文件中,那么在终端就需要输入一个父命令,如
python manage.py db init
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from flask_script import Manager
from flaskScriptDemo import app
from dbScript import dbManager
manager = Manager(app)
def runserver():
print("服务器跑起来了")
# params: 前缀(类似于别名),dbManager
# 使用:python manage.py db init
manager.add_command("db", dbManager)
if __name__ == '__main__':
manager.run()有子命令的例子:
1
2
3
4
5
6
7
8
9
10
11from flask_script import Manager
dbManager = Manager()
def init():
print("数据库初始化完成")
def migrate():
print("数据表迁移成功")
分开 models
及解决循环引用
循环引用

解决办法:

把
db
放在一个单独的文件中,切断循环引用的线条就可以了。
分开 models
- 目的:为了让代码更加方便的管理
注意:
使用了
db.init_app(app)
,就不能直接使用db.create_all()
了,会报错。1
2
3app = Flask(__name__)
app.config.from_object(modelSepConfig)
db.init_app(app)因为涉及到了上下文的问题:
解决办法看
flask-migrate
flask-migrate
db.init_app(app)
不能直接使用 db.create_all()
解决办法
手动推动
app
到app栈
中
介绍
因为采用 db.create_all()
在后期修改字段的时候,不会自动的映射到数据库中,必须删除表,然后重新运行 db.create_all()
才会重新映射,这样不符合我们的需求。因此 flask-migrate
就是为了解决这个问题,它可以在每次修改模型后,可将修改的东西映射到数据库中
安装
- 进入虚拟环境
pip install flask-migrate
使用
使用 flask_migrate
必须借助 flask-scripts
,这个包的 MigrateCommand
中包含了所有和数据库相关的命令。
相关命令
python manage.py db init
: 初始化一个迁移脚本的环境,只需要执行一次;python manage.py db migrate
: 将模型生成迁移文件,只要模型更改了,就需要执行一遍这个命令;python manage.py db upgrade
: 将掐你文件真正映射到数据库中,每次运行了migrate
命令后,就记得要运行这个命令
注意点
- 如果需要将你想要映射到数据库中的模型,都要导入到
manage.py
文件中,如果没有导入进去,就不会映射到数据库中
manage.py
的相关代码
1 | from flask_script import Manager |
cookie和session
cookie
出现的原因:在网站中,
http
请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不知道当前请求是哪个用户。cookie
的出现就是我了解决这个问题,第一次登录后服务器返回一些数据(cookie
)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie
数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是那个了;图解:
如果服务器返回了
cookie
给浏览器,那么浏览器下次再请求相同的服务器的时候,就会自动的把cookie
发送给服务器,这个过程,用户根本不需要管;cookie
是保存在浏览器中的,相对对的是浏览器;
session
介绍:
session
和cookie
的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie
是存储在本地服务器的,而session
存储在服务器。存储在服务器的数据会更加安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session
信息还是绰绰有余的。图解:
使用
session
的好处:- 敏感数据不是直接发送给服务器,而是发送回一个
session_id
,服务器将session_id
和敏感数据做一个映射存储在session
(服务器上面)中,更加安全; session
可以设置过期时间,也可以从另外一方面,保证用户的账号安全。
- 敏感数据不是直接发送给服务器,而是发送回一个
注意
session_id
:返回给浏览器的时候,是放在cookie
中的。
flask中session工作机制
讲解:把敏感数据经过加密后放入
session
中,然后再把session
存放到cookie
中,下次请求的时候,再从浏览器发送过来的cookie
中读取session
,然后再从session
中读取敏感数据,并进行解密,获取最终的用户数据;flask
的这种session
机制,可以节省服务器的开销,以内把所有的信息都存储到了客户端(浏览器);安全是相对的,没有绝对的安全,把
session
放到cookie
中,经过机密,也是比较安全的;图解:
flask操作session
session的操作方式:
- 使用
session
需要从flask
中导入session
,以后所有和session
相关的操作都是通过这个变量来的; - 使用
session
需要设置SECRET_KEY
,用来作为加密用的。并且这个SECRET_KEY
如果每次服务器启动后都变化的话,那么之前的session
就不能通过当前这个SECRET_KEY
进行解密了; - 操作
session
,跟操作字典是一样的; - 添加
session
:session[key]=value
- 删除:
session.pop(key)
或者del session[key]
- 清除所有
session
:session.clear()
- 获取
session
:session.get(key)
设置session的过期时间
如果没有指定
session
的过期时间,那么默认是浏览器关闭后就自动结束如果设置了
session
的permanent
属性为True
,那么过期时间是31天可以通过给
app.config
设置PERMANENT_SESSION_LIFETIME
来更改过期时间,这个值的数据类型是datatime.timedelay
类型:1
2
3from datetime import timedelta
PERMANENT_SESSION_LIFETIME = timedelta(days=7)
get请求和post请求
get请求
- 使用场景:如果只对服务器获取数据,并没有对服务器产生任何影响,那么这时候使用
get
请求 - 传参:
get
请求传参是防砸url
中,并且是通过?
的形式来指定key
和value
的
post请求
- 使用场景:如果要对服务器产生影响,那么使用post请求,如: 登录:服务器要记录用户什么时候登陆过,要把数据保存在本地;
- 传参:post请求传参不是放在
url
中,而是通过form data
的形式发送给服务器的
获取参数
get
请求:通过flask.request.args
来获取post
请求:通过flask.request.form
来获取
注意
post
请求在模板中要注意几点:input
标签中,要写name
来标识这个value
的key
,方便后台获取在写
form
表单的时候,要指定method='post'
,并且要指定action='/login/'
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<form action="{{ url_for("login") }}" method="post">
<table>
<tbody>
<tr>
<td>用户名</td>
<td><input type="text" placeholder="请输入用户名" name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="text" placeholder="请输入密码" name="password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" placeholder="登录"></td>
</tr>
</tbody>
</table>
</form>
保存全局遍历的g属性
g
: global
g对象
是专门用来保存用户的数据的g对象
在一次请求中的所有的代码的地方,都是可以使用的
钩子函数(hook)
钩子函数

正常执行情况:先执行A,然后再执行B。
钩子函数:可以在运行时,把C直接插入到AB之间
before_request
before_request
:在请求之前执行的函数,且每次请求都会执行一遍before_request
是在视图函数执行之前执行的before_request
这个函数只是一个装饰器,他可以把需要设置为钩子函数的代码放到视图函数执行之前来执行
context_processor
(上下文处理器)
- 出现的原因:多个不同的页面需要相同的参数,如:
username
- 上下文处理器应该返回一个字典,字典中的
key
会被模板当成变量来渲染 - 上下文处理器中返回的字典,在所有页面中都是可用的。
- 被这个装饰器修饰的钩子函数,必须要返回一个字典,即使为空,也要返回
装饰器
需求1:在打印
run
之前,先要打印hello world
1
2
3
4
5def run():
print("hello world")
print("run")
run()需求2:在所有函数执行之前,都要打印一个
hello world
方法一:
1
2
3
4
5
6
7
8
9def run():
print("hello world")
print("run")
def run1():
print("hello world")
print("run1")
run()方法二(如果成千上万个函数,比较麻烦,且难以维护):装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27# 装饰器的两个特别之处:
# 1. 参数是一个函数
# 2. 返回值是一个函数
def myLog(func):
def wrapper():
print("hello world")
func()
"""
补充:
wrapper: 函数体
wrapper(): 执行wrapper这个函数
"""
return wrapper
# 如何用
def run():
print("run")
"""
等价于上面
run = myLog(run) <==> wrapper
run() <==> wrapper() <==> print("hello world"); func() <==> print("hello world"); print("run")
理解:func() 执行的才是真的 run() 方法
"""
run()
讲解
- 装饰器的两个特别之处:
- 参数是一个函数
- 返回值是一个函数
- 上面的
myLog()
是无参情况下的装饰器
进阶:有参情况下的装饰器
需求:在所有函数执行之前,都要打印一个 hello world
1 | # 装饰器的两个特别之处: |
再进阶:有无参同时存在情况下的装饰器
解决办法:
*args, **kwargs
:可以表示任何参数*args
: 位置参数,如:add(a, b)
中的ab
,参数是一一对应的**kwargs
:关键字参数(key=value
),如:add(a=abc)
的a
就是传入的abc
1
2
3
4
5
6def myLog(func):
def wrapper(*args, **kwargs):
print("hello world")
func(*args, **kwargs)
return wrapperrun()
,add()
同上
再进阶
问题:加完装饰器后,函数名被偷偷更改掉了,因此如何保留住原来的函数的名字(把名字改掉是很危险的行为)?
没有装饰器的情况
1
2
3def run():
print("run")
print(run.__name__) # run.__name__代表的是run这个函数的名称输出:
run
有装饰器的情况
1
2
3
4
def run():
print("run")
print(run.__name__)输出:
wrapper
解决办法:
1
2
3
4
5
6
7
8
9from functools import wraps
def myLog(func):
def wrapper(*args, **kwargs):
print("hello world")
func(*args, **kwargs)
return wrapper没加
@wraps
的情况
run() <=> myLog(run)() <=> wrapper()
加了
@wraps(func)
的情况run <=> myLog(run) <=> @wraps(func)装饰的wrapper <=> wraps(wrapper) <=> wrapsFunction(这个函数是透明的,即看不到的,但是不管怎么样,返回的函数的__name__是run) => wrapsFunction.__name__ == "run"
- ``run() <=> myLog(run)() <=> @wraps装饰的wrapper() <=> wraps(wrapper)() <=> wrapsFunction()`
再进阶
需求:如果
run()
有返回值,该怎么办?解决办法:在
wrapper()
中return func(*args, **kwargs)
1
2
3
4
5
6
7
8
9
10
11
12
13from functools import wraps
def myLog(func):
def wrapper(*args, **kwargs):
print("hello world")
return func(*args, **kwargs)
return wrapper
def run():
return 1+1讲解:
1
2
3
4
5run = myLog(run) = wrapper
==>(推出,下同) run = wrapper
==> run() = wrapper() = print("hello world"); return run() = print("hello world"); return 1+1
因为run()需要返回1+1,所以wrapper()也需要返回1+1
如果wrapper没有return,那么func(*args, **kwargs),就只是执行了一下1+1,别的什么都没有了
小结
- 装饰器的使用是通过
@
符号,放在函数上面; - 装饰器中定义的函数,要使用
*args
和**kwargs
两对兄弟的组合,并且在这个函数中执行原始函数的时候也要把*args
和**kwargs
传进去; - 需要使用
functools.wraps
在装饰器中的函数上把传进来的这个函数进行一个包裹,这样就不会丢失原来的函数__name__
等属性
实践
- 框架:
bootstrap
,中文网