flask框架
蓝图
随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过python的模块化管理,于是针对一个简单的flask程序进行模块化处理
Blueprint概念
简单来说,Blueprint 是一个存储操作方法的容器,
这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,
Flask 可以通过Blueprint来组织URL以及处理请求。在Flask中Blueprint具有的属性
- Flask使用Blueprint让应用实现模块化
- 一个应用可以具有多个Blueprint
- 在一个应用中,一个模块可以注册多次
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法
- 在一个应用初始化时,就应该要注册需要使用的Blueprint
一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
使用蓝图的步骤
蓝图/Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个 蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效
- 1,创建一个蓝图对象
admin=Blueprint('admin',name)- 蓝图对象中的参数
- 蓝图的url前缀
- 当我们在应用对象上注册一个蓝图时,可以指定一个url_prefix关键字参数(这个参数默认是/)
- 在应用最终的路由表 url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的URL规则而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可
- url_for
- 蓝图的url前缀
- 蓝图对象中的参数
url_for('admin.index') # /admin/
* 注册静态路由
* 和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 static_folder 参数。
* 下面的示例将蓝图所在目录下的static_admin目录设置为静态目录
admin = Blueprint("admin",name,static_folder='static_admin')
app.register_blueprint(admin,url_prefix='/admin')
* 现在就可以使用/admin/static_admin/ 访问static_admin目录下的静态文件了
* 定制静态目录URL规则 :可以在创建蓝图对象时使用 static_url_path 来改变静态目录的路由。下面的示例将为 static_admin 文件夹的路由设置为 /lib
* admin = Blueprint("admin",name,static_folder='static_admin',static_url_path='/lib')
app.register_blueprint(admin,url_prefix='/admin')
* 设置模版目录
* 蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用 template_folder 关键字参数设置模板目录
admin = Blueprint('admin',name,template_folder='my_templates')
* 如果在 templates 中存在和 my_templates 同名文件,则系统会优先使用 templates 中的文件
- 2,在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
@admin.route('/')
def admin_home():
return 'admin_home' - 3,在应用对象上注册这个蓝图对象
app.register_blueprint(admin,url_prefix='/admin')- 这里的前缀和蓝图对象的前缀一样的作用
当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数
虚拟环境的
为甚麽搭建虚拟环境
在实际开发中,多个程序的运行可以能需要调试各种版本的不同环境,比如不同的python解释器,不同的flask版本
问题描述:如果直接进行多个版本安装,会导致后安装的内容覆盖先安装的.
解决办法:使用虚拟环境,不同的程序选择不同的环境,互不影响
搭建步骤
- 1.先查看当前电脑中是否有虚拟环境命令
virtualenv --version - 2.[可选]安装虚拟环境的命令:
sudo pip install virtualenv
sudo pip install virtualenvwrapper 3.查看是否有mkvirtualenv创建虚拟环境指令
mkvirtualenv --version- 4.[可选]安装完虚拟环境后,如果提示找不到mkvirtualenv命令,须配置环境变量
- 4.1、创建目录用来存放虚拟环境
mkdir $HOME/.virtualenvs - 4.2、打开~/.bashrc文件,并添加如下:
export WORKON_HOME=$HOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh - 4.3、运行
source ~/.bashrc
- 4.1、创建目录用来存放虚拟环境
- 5.创建虚拟环境的命令 :
mkvirtualenv 虚拟环境名称(默认python2.x)
例: mkvirtualenv py_flaskmkvirtualenv -p python3 虚拟环境名称(指定python3.x)
例 :mkvirtualenv -p python3 py3_flask如何使用虚拟环境
1.查看虚拟环境的命令 :
workon 两次tab键 或者 workon 回车2.使用虚拟环境的命令 :
workon 虚拟环境名称
例 :workon py_flask
例 :workon py3_flask3.退出虚拟环境的命令 :
deactivate4.删除虚拟环境的命令(需要先退出):
rmvirtualenv 虚拟环境名称
例 :删除虚拟环境py3_flask
先退出:deactivate
再删除:rmvirtualenv py3_flask如何在虚拟环境中安装工具包
- 1.安装flask-0.10.1的包:
pip install 包名称
例 : 安装flask-0.10.1的包
pip install flask==0.10.1 - 2.查看虚拟环境中安装的包 :
pip freeze - 工具包安装的位置
- python2版本下:
~/.virtualenvs/py_flask/lib/python2.7/site-packages/ python3版本下:
~/.virtualenvs/py3_flask/lib/python3.5/site-packagesweb应用程序交互流程
flask
核心
- python2版本下:
- jinja2
- 模板
- 拓展包
- flask_script
- 命令行方式启动
python hello.py runserver -host ip地址 -
from flask import Flask
-
1.从flask_script中导入Manager类
-
from flask_script import Manager
-
-
app = Flask(name)
-
-
2.使用Manager管理app对象
-
manager = Manager(app)
-
-
@app.route('/')
-
def hello_world():
-
return "helloworld"
-
-
if name == 'main':
-
manager.run()
- 命令行方式启动
- flask_wtf
- 后台CSRF校验机制
-
from flask import Flask,render_template
-
from flask_wtf import CSRFProtect
-
-
app = Flask(name)
-
-
设置SECRET_KEY
-
app.config["SECRET_KEY"] = "fjkdjfkdfjdk"
-
-
保护应用程序
-
CSRFProtect(app)
-
-
@app.route('/')
-
def show_page():
-
-
return render_template('file01csrf.html')
-
-
@app.route('/add_data',methods=["POST"])
-
def add_data():
-
-
return "登陆成功"
-
-
if name == 'main':
-
app.run(debug=True)
- 前端隐藏表单
-
-
{#设置隐藏的csrf_token,使用了CSRFProtect保护app之后,即可使用csrf_token()方法#}
-
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
-
-
<label>用户名:</label> <input type="text" name="username"><br>
-
<label>密码:</label> <input type="text" name="username"><br>
-
<input type="submit" value="登陆">
-
- flask_migrate
- 数据库迁移
- flask_sqlalchemy
- flask操作数据库
- flask_script
- werkzeug
- 路由模块
from werkzeug.route import Converter
自定义转换器转换器最简单的flaskweb
- 新建文件helloworld.py文件
1.导入Flask类
from flask import Flask
2.创建Flask对象接收一个参数__name__,它会指向程序所在的包
app = Flask(name)3.装饰器的作用是将路由映射到视图函数index
@app.route('/')
def index():
return 'Hello World'4.Flask应用程序实例的run方法,启动WEB服务器
if name == 'main':
app.run()
- 路由模块
加载应用程序的配置方式
- from_pyfile
- 从配置文件中添加
在同级创建配置文件.ini
配置内容每行一个 -
从配置文件中加载配置
-
app.config.from_pyfile('config.ini')
- 从配置文件中添加
- from_object
- 从配置对象中添加
-
配置对象,里面定义需要给 APP 添加的一系列配置
-
class Config(object):
-
DEBUG = True
-
-
从配置对象中加载配置
-
app.config.from_object(Config)
- from_enver
- 从环境变量中添加
-
加载指定环境变量名称所对应的相关配置
-
app.config.from_envvar('FLASKCONFIG')
Flask()创建时的参数
- name
- 当前程序运行__main__
-
Flask程序所在的包(模块),传 name 就可以
-
其可以决定 Flask 在访问静态文件时查找的路径
- static_folder
- 静态文件存储的文件夹,默认为static
- static_url_path
- 静态文件访问路径,默认为 /static
- template_folder
模板文件存储的文件夹,默认为 template
app.run()传递的参数
- host
- port
debug
路由的定义
指定请求方式
- methods
-
@app.route('/demo2', methods=['GET', 'POST'])
-
def demo2():
-
# 直接从请求中取到请求方式并返回
-
return request.method
- 配合postman软件开发
-
Postman功能(https://www.getpostman.com/features)
-
主要用于模拟网络请求包
-
快速创建请求
-
回放、管理请求
-
快速设置网络代理
指定请求参数
路由传递参数,整数
@app.route('/user/')
def user_info(user_id):
return 'the num is %d' % user_id
- 不填转换器名称默认是字符串
-
- 默认的就是UnicodeConverter, 所以不填转换器名称, 或者使用default、string都是一样的. - 默认将参数当做普通字符串对待. DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
- 自定义转换器
-
from flask import Flask
-
导入基类转换器
-
from werkzeug.routing import BaseConverter
-
-
app = Flask(name)
-
-
1.自定义类,继承自BaseConverter
-
class MyRegexConverter(BaseConverter):
-
-
# 2.编写初始化方法, init方法, 接收两个参数, url_map, regex, 并初始化父类空间和子类空间
-
def __init__(self,url_map,regex):
-
super(MyRegexConverter, self).__init__(url_map)
-
self.regex = regex
-
-
3.将自定义转换器类,添加到默认的转换列表中
-
app.url_map.converters['re'] = MyRegexConverter
-
-
使用自定义转换器
-
接收3位整数
-
@app.route('/<re("\d{3}"):num>')
-
def hello_world(num):
-
-
print("num = %s"%num)
-
-
return "the num is %s"%num
-
-
接收一个手机号
-
@app.route('/<re("1[345678]\d{9}"):mobile>')
-
def get_phone_number(mobile):
-
-
return "the mobile is %s"%mobile
-
-
if name == 'main':
-
app.run()
- 继承与BaseCoverter
- 可以直接指定regex值,重写init方法,传递正则规则
-
class BaseConverter(object):
-
-
"""Base class for all converters."""
-
regex = '[^/]+'
-
weight = 100
-
-
def __init__(self, map):
-
self.map = map
- 也可以不直接指定regex值,在使用自定义转换器的时候指定正则规则
-
class MyConverter(BaseConverter):
-
def __init__(self,url_map,regex):
-
super(MyConverter, self).__init__(url_map)
-
self.regex = regex
-
app.url_map.converters['re'] = MyConverter
-
@app.route('/<re("\d{3}"):num>')
-
def index(num):
-
return num
- 自定义转换器类中的其他方法
-
class MyConverter(BaseConverter):
-
def __init__(self,url_map,regex):
-
super(MyConverter, self).__init__(url_map)
-
self.regex = regex
-
def to_python(self, value):
-
return value
-
def to_url(self, value):
-
return value
-
-
app.url_map.converters['re'] = MyConverter
- to_python
- 匹配成功后被调用,可以对匹配到的参数进行处理,比如转换匹配到的数据的类型,在正则匹配完成之后,调用视图函数之前,可以对数据进行最后的处理
- to_url
在正则匹配之前调用执行,并传入参数,调用完成之后才开始真正的路由匹配,不关心是否匹配成功,可以通过此函数修正要传入路由中的参数,方便更好的正则匹配
重定向
重定向
@app.route('/demo5')
def demo5():
# 使用 url_for 生成指定视图函数所对应的 url
return redirect(url_for('user_info', user_id=100))
- to_python
- redirect('路径')
- 通过指定路径重定向到函数
- url_for('函数名',key=value)
通过指定函数,返回路径,可以附带附带请求参数
返回json
- 注意contentType
-
contentType: 告诉服务器,我要发什么类型的数据
-
dataType:告诉服务器,我要想什么类型的数据,如果没有指定,那么会自动推断是返回 XML,还是JSON,还是script,还是String。
- json和dict转换
- dict->json
- json = json.dumps(dict)
- json = jsonity(key=value)
- json->dict
- dict = json.loads(json)
- dict->json
- jsonify
- 自定义状态码
- 手动指定(响应体——状态码+响应头)
- make_response()
-
@app.route('/')
-
def hello_world():
-
-
# 1.直接返回, 响应体 + 状态码
-
# return "hello",666
-
# return "hello","666 BIGERROR"
-
-
# 2.手动创建响应体对象
-
response = make_response("hello")
-
response.status = "888 XIAGAO"
-
return response
- status
headers
上下文
相当于一个容器,保存了 Flask 程序运行过程中的一些信息。
请求上下文
保存了客户端和服务器交互的数据
- request
session
应用上下文
flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等
- current_app
-
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
-
-
应用的启动脚本是哪个文件,启动时指定了哪些参数
-
加载了哪些配置文件,导入了哪些配置
-
连了哪个数据库
-
有哪些public的工具类、常量
-
应用跑再哪个机器上,IP多少,内存多大
-
current_app.name
-
current_app.test_value='value'
- g
-
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
-
-
g.name='abc'
-
注意:不同的请求,会有不同的全局变量
状态保持
http是一种无状态协议,浏览器请求服务器是无状态的。
无状态:指一次用户请求时,浏览器、服务器不知道之前这个用户做过什么,每次请求都是一次新的请求。
无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。
有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
实现状态保持主要有两种方式:
在客户端存储信息使用Cookie
在服务器端存储信息使用Session
二.生活状态举例:
Cookie
Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用
Cookie基于域名安全,不同域名的Cookie是不能互相访问的
应用场景:广告推送;商城结账提取商品信息
- 获取与设置cookie
-
from flask import Flask, make_response, request
-
-
app = Flask(name)
-
-
设置cookie值
-
@app.route('/set_cookie')
-
def set_cookie():
-
-
response = make_response("set cookie")
-
response.set_cookie("name","zhangsan")
-
response.set_cookie("age","13",10) #10秒有效期
-
-
return response
-
-
获取cookie
-
@app.route('/get_cookie')
-
def get_cookie():
-
-
#获取cookie,可以根据cookie的内容来推荐商品信息
-
# name = request.cookies['haha']
-
name = request.cookies.get('name')
-
age = request.cookies.get('age')
-
-
return "获取cookie,name is %s, age is %s"%(name,age)
-
-
if name == 'main':
-
app.run(debug=True)
Session
对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息,所以可以使用session进行保存
在服务器端进行状态保持的方案就是Session
- 依赖cookie
- 获取和设置session
-
from flask import Flask,session
-
-
app = Flask(name)
-
-
设置SECRET_KEY
-
app.config["SECRET_KEY"] = "fhdk^fk#djefkj&&&"
-
-
设置session
-
@app.route('/set_session/')
-
def set_session(name):
-
-
session["name"] = name
-
session["age"] = "13"
-
-
return "set session"
-
-
获取session内容
-
@app.route('/get_session')
-
def get_session():
-
-
name = session.get('name')
-
age = session.get('age')
-
-
return "name is %s, age is %s"%(name,age)
-
-
if name == 'main':
-
app.run(debug=True)
SECRET_KEY
请求钩子
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
在请求开始时,建立数据库连接;
在请求开始时,根据需求进行权限校验;
在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。
before_first_request
在第一次请求之前调用,可以在此方法内部做一些初始化操作
如:数据库的连接
before_request
在每次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验
如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数
@app.before_request
def before_request():
print("before_request")
# if 请求不符合条件:
# return "laowang"
after_request
在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
@app.after_request
def after_request(response):
print("after_request")
response.headers["Content-Type"] = "application/json"
return response
teardown_request
请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
@app.teardown_request
def teardown_request(e):
print("teardown_request")
当前Request对象
request 就是flask中代表当前请求的 request 对象,其中一个请求上下文变量(理解成全局变量,在视图函数中直接使用可以取到当前本次请求)
args
地址栏数据,问号后面
www.baidu.com?key=value&key=valuedata
json/xml,post提交的请求
form
表单
cookie
记录请求中的cookie信息
files
文件内容
headers
记录请求中的报文头
method
记录请求中使用的HTTP方法
url
记录请求的url地址
路由和视图函数在定义和匹配过程中关键类
BaseConverter
记录匹配规则
Rule
记录视图函数与路由映射关系
Map
路由与视图函数关系列表
MapAdapter
做具体匹配工作
Flask_script
属于flask的扩展包,通过使用Flask-Script扩展,我们可以在Flask服务器启动的时候,通过命令行的方式传入参数。而不仅仅通过app.run()方法中传参,比如我们可以通过:
继承脚本扩展,动态运行程序,指定IP,PORT
python hello.py runserver -host ip地址
Manager(app)
from flask import Flask
1.从flask_script中导入Manager类
from flask_script import Manager
app = Flask(name)
2.使用Manager管理app对象
manager = Manager(app)
@app.route('/')
def hello_world():
return "helloworld"if name == 'main':
manager.run()
数据库
- ORM
-
ORM 全拼Object-Relation Mapping. 称为对象-关系映射
-
主要实现模型对象到关系数据库数据的映射.
- 优点
- 对数据库的操作都转化成对类,属性和方法的操作.
不用编写各种数据库的sql语句.
不在关注,使用的是mysql、oracle...等数据库
- 对数据库的操作都转化成对类,属性和方法的操作.
- 缺点
- 相比较直接使用SQL语句操作数据库,有性能损失.
- 优点
- Flask-SQLAlchemy
-
SQLALchemy 实际上是对数据库的抽象,让开发者不用直接和 SQL 语句打交道,而是通过 Python 对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升
-
SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。
- 安装
- pip install flask-sqlalchemy
- 如果连接的是 mysql 数据库,需要安装 mysqldb
pip install flask-mysqldb - 提示:如果flask-mysqldb安装不上,安装, pip install pymysql
- 数据库连接设置
- 数据库链接地址
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'- 格式:mysql://:@:/数据库名称
- 动态追踪修改设置,如未设置只会提示警告
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True - 有时候需要查看映射的sql语句
- app.config['SQLALCHEMY_ECHO'] = True
- 配置完成需要去 MySQL 中创建项目所使用的数据库
$ mysql -uroot -pmysql
$ create database test charset utf8; - 配置信息汇总
- SQLALCHEMY_BINDS 一个映射 binds 到连接 URI 的字典。更多 binds 的信息见用 Binds 操作多个数据库。
- SQLALCHEMY_ECHO 如果设置为Ture, SQLAlchemy 会记录所有 发给 stderr 的语句,这对调试有用。(打印sql语句)
- SQLALCHEMY_RECORD_QUERIES 可以用于显式地禁用或启用查询记录。查询记录 在调试或测试模式自动启用。更多信息见get_debug_queries()。
- SQLALCHEMY_NATIVE_UNICODE 可以用于显式禁用原生 unicode 支持。当使用 不合适的指定无编码的数据库默认值时,这对于 一些数据库适配器是必须的(比如 Ubuntu 上 某些版本的 PostgreSQL )。
- SQLALCHEMY_POOL_SIZE 数据库连接池的大小。默认是引擎默认值(通常 是 5 )
- SQLALCHEMY_POOL_TIMEOUT 设定连接池的连接超时时间。默认是 10 。
- SQLALCHEMY_POOL_RECYCLE 多少秒后自动回收连接。这对 MySQL 是必要的, 它默认移除闲置多于 8 小时的连接。注意如果 使用了 MySQL , Flask-SQLALchemy 自动设定 这个值为 2 小时。
- 连接其他数据库
- Postgres:
postgresql://scott:tiger@localhost/mydatabase - MySQL:
mysql://scott:tiger@localhost/mydatabase - Oracle:
- Postgres:
- 数据库链接地址
- 安装
- oracle://scott:tiger@127.0.0.1:1521/sidname
* SQLite (注意开头的四个斜线):
sqlite:absolute/path/to/foo.db- 创建sqlalchemy对象
创建SQLAlchemy对象
db = SQLAlchemy(app)
- 使用模型类创建数据表
- 一对多关系
- 一方
- 创建模型类(继承自db.Model)
-
class Student(db.Model):
-
__tablename__ = "students"
-
id = db.Column(db.Integer,primary_key=True)
-
name = db.Column(db.String(64),unique=True,nullable=False)
-
-
#关系属性
-
courses = db.relationship("Course",backref="students",secondary=tb_student_course)
- 默认表名为模型类的名字
__tablename__指定表名- class Student(db.Model):
tablename = "students"
- class Student(db.Model):
- 类属性名表示的字段名
-
定义列对象
-
变量作为字段名
-
括号内是字段类型和字段约束
- id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(64),unique=True,nullable=False)
- id = db.Column(db.Integer,primary_key=True)
- 关系属性
-
关系属性方便查询使用
-
通过 table_name.courses可以访问到‘Course’模型类定义的数据表
-
‘Course’模型类定义的数据表,通过Course.students可以访问到本数据表
- courses = db.relationship("Course",backref="students")
- lazy='dynamic'
-
第三个参数lazy决定了什么时候SQLALchemy从数据库中加载数据
-
如果设置为子查询方式(subquery),则会在加载完Role对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
-
设置为 subquery 的话,role.users 返回所有数据列表
-
另外,也可以设置为动态方式(dynamic),这样关联对象会在被使用的时候再进行加载,并且在返回前进行过滤,如果返回的对象数很多,或者未来会变得很多,那最好采用这种方式
- 关系属性在任何一方设置都行
- 默认表名为模型类的名字
- 多方
- 设置外键
- role_id = db.Column(db.Integer,db.ForeignKey(Role.id))
- 设置外键
- 一方
- 多对多关系
- 创建一张关联表
关联表数据分别和两张表设置外键- 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'))
)
- tb_student_course = db.Table('tb_student_course',
- 关系属性设置在任何一方都行
- courses = db.relationship('Course', secondary=tb_student_course,
backref='student',
lazy='dynamic')
- courses = db.relationship('Course', secondary=tb_student_course,
- 创建一张关联表
- 一对多关系
- 向数据表中添加数据
- 在
if name == main:
之后- 一对多
- 创建模型类对象
- stu1 = Student(name='张三')
- cou1 = Course(name='物理')
- 将模型类对象添加进会话
- db.session.add(obj) 添加对象
db.session.add_all([obj1,obj2,..]) 添加多个对象 - db.session.delete(obj) 删除对象
- db.session.add(obj) 添加对象
- 提交会话
- db.session.commit() 提交会话
- db.session.rollback() 回滚
- db.session.remove() 移除会话
- 创建模型类对象
- 多对多
- 创建模型类对象
- stu1 = Student(name='张三')
stu2 = Student(name='李四')
stu3 = Student(name='王五') - cou1 = Course(name='物理')
cou2 = Course(name='化学')
cou3 = Course(name='生物')
- stu1 = Student(name='张三')
- 关联表添加关联数据
- stu1.courses = [cou2, cou3]
stu2.courses = [cou2]
stu3.courses = [cou1, cou2, cou3]
- stu1.courses = [cou2, cou3]
- 将模型类对象添加进会话
- db.session.add(obj) 添加对象
db.session.add_all([obj1,obj2,..]) 添加多个对象 - db.session.delete(obj) 删除对象
- db.session.add(obj) 添加对象
- 提交会话
- db.session.commit() 提交会话
- db.session.rollback() 回滚
- db.session.remove() 移除会话
- 创建模型类对象
- 一对多
- 在
- app.run()
- 创建sqlalchemy对象
- 数据库的迁移
-
在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
-
为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。
- 虚拟环境中安装Flask-Migrate
- pip install flask-migrate
- 导入相应的包
- from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate,MigrateCommand
from flask_script import Shell,Manager
- from flask_sqlalchemy import SQLAlchemy
- 创建Flask对象
- app = Flask(name)
- 使用flask-script的manager接管app对象
- manager = Manager(app)
- 设置app连接数据库的配置信息
- 创建SQLAlchemy对象,读取app中的配置信息
- db = SQLAlchemy(app)
- 使用数据库迁移框架
- migrate = Migrate(app,db)
第一个参数是Flask实例,第二个参数是sqlalchemy数据库实例
- migrate = Migrate(app,db)
- 使用Flask_script中的manager动态运行程序
manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
manager.add_command('db',MigrateCommand)
- 虚拟环境中安装Flask-Migrate
数据库迁移
配合flask_migrate
pip install flask-migrate-
from flask import Flask
-
-
from flask_sqlalchemy import SQLAlchemy
-
from flask_migrate import Migrate,MigrateCommand
-
from flask_script import Shell,Manager
-
-
app = Flask(name)
-
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数据库实例
-
migrate = Migrate(app,db)
-
-
manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
-
manager.add_command('db',MigrateCommand)
-
-
定义模型Role
-
class Role(db.Model):
-
# 定义表名
-
__tablename__ = 'roles'
-
# 定义列对象
-
id = db.Column(db.Integer, primary_key=True)
-
name = db.Column(db.String(64), unique=True)
-
user = db.relationship('User', backref='role')
-
-
#repr()方法显示一个可读字符串,
-
def __repr__(self):
-
return 'Role:'.format(self.name)
-
-
定义用户
-
class User(db.Model):
-
__talbe__ = 'users'
-
id = db.Column(db.Integer, primary_key=True)
-
username = db.Column(db.String(64), unique=True, index=True)
-
#设置外键
-
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
-
-
def __repr__(self):
-
return 'User:'.format(self.username)
-
-
-
if name == 'main':
-
manager.run()
- app = Flask(name)
- manager = Manager(app)
- db = SQLAlchemy(app)
migrate = Migrate(app,db)
- manager.add_command('db',MigrateCommand)
- manager.run()
- 创建迁移仓库
这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python database.py db init
- 创建迁移脚本
- 自动创建迁移脚本有两个函数
upgrade():函数把迁移中的改动应用到数据库中。
downgrade():函数则将改动删除。
自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。
对比不一定完全正确,有可能会遗漏一些细节,需要进行检查
python database.py db migrate -m 'initial migration'
- 自动创建迁移脚本有两个函数
- 更新数据库
- python database.py db upgrade
- 返回以前的版本
- 可以根据history命令找到版本号,然后传给downgrade命令:
python app.py db history
输出格式: -> 版本号 (head), initial migration
* 回滚到指定版本
python app.py db downgrade 版本号
- 实际操作顺序
实际操作顺序:
1.python 文件 db init
2.python 文件 db migrate -m"版本名(注释)"
3.python 文件 db upgrade 然后观察表结构
4.根据需求修改模型
5.python 文件 db migrate -m"新版本名(注释)"
6.python 文件 db upgrade 然后观察表结构
7.若返回版本,则利用 python 文件 db history查看版本号
8.python 文件 db downgrade(upgrade) 版本号模板
Jinja2模板
1.获取各种变量的值
<h3>整数: {{ my_num + 20}}</h3> <h3>字符串: {{ my_str + " python" }}</h3> <h3>元组: {{ my_tuple }}, 分开获取:{{ my_tuple[0] }}, {{ my_tuple[1] }}</h3> <h3>列表: {{ my_list }}, 分开获取:{{ my_list[0] }}, {{ my_list[1] }}</h3> <h3>字典: {{ my_dict }},分开获取:{{ my_dict.name }}, {{ my_dict[age] }}</h3> <h2>2.遍历元祖中所有的元素</h2> {% for item in my_tuple %} <li>{{ item }}</li> {% endfor %} <h2>3.取出列表中所有偶数</h2> {% for item in my_list %} {% if item %2 == 0 %} {{ item }} {% endif %} {% endfor %} <h2>4.遍历字典内容</h2> {% for key in my_dict %} {# 如果直接是mydict.key ,那么这个key是一个字符串, 如果是 mydict[key], 那么key当成变量 #} <li>{{ key }} = {{ my_dict[key] }}</li> {% endfor %}
- Jinja2模板概述
-
用来展示数据的html页面,这个过程也通常称为渲染,属于Jinja2的功能 使用模板的好处:
-
-
视图函数只负责业务逻辑和数据处理(业务逻辑方面)
-
而模板则取到视图函数的数据结果进行展示(视图展示方面)
-
代码结构清晰,耦合度低
- Jinja2特点
-
Jinja2:是 Python 下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。
-
模板语言:是一种被设计来自动生成文档的简单文本格式,在模板语言中,一般都会把一些变量传给模板,替换模板的特定位置上预先定义好的占位变量名。
-
使用render_template函数封装模板引擎
- python代码实现
- 仿照django实现模板语言
- 过滤器支持链式调用{{
str|lower|reverse }} - 直接在模板中支持运算
- Jinja2模板语法
- 获取变量值
整数:{ {number} }
元祖:{ {tuple[0]} }
列表:{ { list[0] } }
字典:{ { dict['key'] } }
- 分支语句
- { % if 条件 % }
语句1
{ % else % }
语句2
{ % endif % }
- { % if 条件 % }
- for循环
- {% for 变量 in 容器 %}
语句
{% endfor%}- for循环中可以访问i的特殊变量
- loop.index 当前循环迭代的次数(从 1 开始)
- loop.index0 当前循环迭代的次数(从 0 开始)
- loop.revindex 到循环结束需要迭代的次数(从 1 开始)
- loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
- loop.first 如果是第一次迭代,为 True 。
- loop.last 如果是最后一次迭代,为 True 。
- loop.length 序列中的项目数。
- loop.cycle 在一串序列间期取值的辅助函数。见下面示例程序。
- for循环中可以访问i的特殊变量
- {% for 变量 in 容器 %}
- 注释
- {# 注释内容 #}
- 获取变量值
- Jinja2模板过滤器
- Jinja2模板自带的两种过滤器
-
过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。
- 字符串过滤器
- 使用格式:{{ 字符串 | 字符串过滤器 }}
* safe:禁用转义{{ 'hello' | safe }}
* capitalize:把变量值的首字母转成大写,其余字母转小写{{ 'hello' | capitalize }}
* lower:把值转成小写{{ 'HELLO' | lower }}
* upper:把值转成大写{{ 'hello' | upper }}
* title:把值中的每个单词的首字母都转成大写{{ 'hello' | title }}
* reverse:字符串反转{{ 'olleh' | reverse }}
* format:格式化输出{{ '%s is %d' | format('name',17) }}
* striptags:渲染之前把值中所有的HTML标签都删掉{{ 'hello' | striptags }}
* 列表过滤器 - 使用格式:{{ 列表 | 列表过滤器 }}
* first:取第一个元素{{ [1,2,3,4,5,6] | first }}
* last:取最后一个元素{{ [1,2,3,4,5,6] | last }}
* length:获取列表长度{{ [1,2,3,4,5,6] | length }}
* sum:列表求和{{ [1,2,3,4,5,6] | sum }}
* sort:列表排序{{ [6,2,3,1,5,4] | sort }}
* 使用过滤器的其他操作语句
* 语句块操作
{% filter upper %}
#一大堆文字#
{% endfilter %}
* 链式调用
{{ "hello world" | reverse | upper }}- Jinja2自定义过滤器的两种方式
- 先定义函数(一个形参接受原值)
- 添加到过滤器列表,app.add_template_filter('函数名',‘过滤器名称’)
-
def do_listreverse(li):
-
# 通过原列表创建一个新列表
-
temp_li = list(li)
-
# 将新列表进行返转
-
temp_li.reverse()
-
return temp_li
-
-
app.add_template_filter(do_listreverse,'lireverse')
- 定义函数,直接使用过滤器列表装饰@app.teemplate_filter('过滤器名称')
-
@app.template_filter('lireverse')
-
def do_listreverse(li):
-
# 通过原列表创建一个新列表
-
temp_li = list(li)
-
# 将新列表进行返转
-
temp_li.reverse()
-
return temp_li
- 先定义函数(一个形参接受原值)
- Jinja2自定义过滤器的两种方式
- 模板代码的复用
-
在模板中,可能会遇到以下情况:
-
-
多个模板具有完全相同的顶部和底部内容
-
多个模板中具有相同的模板代码内容,但是内容中部分值不一样
-
多个模板中具有完全相同的 html 代码块内容
-
像遇到这种情况,可以使用 JinJa2 模板中的 宏、继承、包含来进行实现
- 宏
-
宏是Jinja2中的函数,调用后,直接返回一个模板,或者字符串
-
当模板中出现大量重复功能代码的时候,可以使用宏来进行封装
- 定义宏
可以在当前文件,也可以在其他文件 -
{% macro input(name,value='',type='text') %}
-
<input type="{{type}}" name="{{name}}" value="{{value}}">
-
{% endmacro %}
- 使用当前文件的宏
-
{{ input('name' value='zs')}}
- 使用其他文件的宏
-
{%import 'filename.html' as 别名%}
-
{%别名.函数名(参数)%}
- 定义宏
- 继承
-
将公共的内容抽取到父类模板,共子类使用的形式称为继承.
-
一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
- 父模板
-
父模板中使用多个block组成
- {% block top %}
顶部菜单
{% endblock top %}
- {% block top %}
{% block content %}
正文内容
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
* 子模版
* 继承后,子类完全拥有父类内容,并且子类可以进行重写,如果写保留父类内容使用: super()
* {% extends 'base.html' %}
{% block content %}
需要填充的内容
{% endblock content %}
* 模板继承的注意点
* 不支持多继承
为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
不能在一个模板文件中定义多个相同名字的block标签。
定义block模板的时候,一定要加上endblock结束标记
* 包含
* > Jinja2模板中,除了宏和继承,还支持一种代码重用的功能,叫包含(Include)。它的功能是将另一个模板整个加载到当前模板中,并直接渲染。
* 格式:
{% include 'hello.html' %}
或者
{% include 'hello.html' ignore missing %}
提示: ignore missing 加上后如果文件不存在,不会报错
* 总结
* 宏(Macro)、继承(Block)、包含(include)均能实现代码的复用。
继承(Block)的本质是代码替换,一般用来实现多个页面中重复不变的区域。
宏(Macro)的功能类似函数,可以传入参数,需要定义、调用。
包含(include)是直接将目标模板文件整个渲染出来。
- 模板特有的变量和函数
-
你可以在自己的模板中访问一些 Flask 默认内置的函数和对象
- config
- 你可以从模板中直接访问Flask当前的config对象:
- config
{{config.DEBUG}}
输出:True
* request
* 就是flask中代表当前请求的request对象:
{{request.url}}
输出:http://127.0.0.1
* g变量
* 在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出
{{ g.name }}
* url_for()
* url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不比担心模板中渲染出错的链接:
{{url_for('home')}}
* 如果我们定义的路由URL是带有参数的,则可以把它们作为关键字参数传入url_for(),Flask会把他们填充进最终生成的URL中:
{{ url_for('post', post_id=1)}}
/post/1
* get_flashed_messages()
* 这个函数会返回之前在flask中通过flask()传入的消息的列表,flash函数的作用很简单,可以把由Python字符串表示的消息加入一个消息队列中,再使用get_flashed_message()函数取出它们并消费掉:
{%for message in get_flashed_messages()%}
{{message}}
{%endfor%}
- Flask_WTF表单
- 使用代码定义表单
- 在模板中进行表单渲染
- 提供一系列的验证器,对表单提交的数据进行验证
- 流程
- 定义类继承子FlaskForm
- 在类中编写字段内容,验证函数
- 创建,渲染到页面
- csrf
- flask_wtf模块提供了csrf攻击的保护
- 使用流程:
from flask_wtf.csrf import CSRFProtect
CSRFProtect(app)
* CSRFProtect(app)保护原理:
* > 对应用程序app中的post,put,dispatch,delete, 4种类型的请求做保护,因为这些类型的请求是用于更改服务器的资源
* > 当以上面4种类型的请求,操作服务器资源的时候,会校验cookie中的csrf_token, 表单中的csrf_token信息
* > 只有上面二者的值相等的时候,那么校验则通过,可以操作服务器资源
* csrf_token值的生成需要加密, 所以设置SECRET_KEY
* 前后端代码
* 后端代码
* > from flask_wtf import CSRFProtect
* > app.config["SECRET_KEY"] = "fjkdjfkdfjdk"
* > CSRFProtect(app)
* 前端代码
* > {#设置隐藏的csrf_token,使用了CSRFProtect保护app之后,即可使用csrf_token()方法#}
* > <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
render_template()函数
from flask import Flask,render_template
app = Flask(name) #默认省略了三个参数,static_url_path, static_folder, template_folders@app.route('/')
def hello_world():
#定义数据,整数,字符串,元祖,列表,字典
num = 10
str = "hello"
tuple = (1,2,3,4)
list = [5,6,7,8]
dict = {
"name":"张三",
"age":13
}return render_template('file01.html',my_num=num,my_str=str,my_tuple=tuple,my_list=list,my_dict=dict)
if name == 'main':
app.run(debug=True)
- 将数据传递给模板
return render_template('模板文件名',key=value)
XMind: ZEN - Trial Version