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 -pcreate 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中,经过机密,也是比较安全的;图解:
![image-20210424093103556]()
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 world1
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,中文网
