Flask是Python编写的一款轻量级Web应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2。Flask使用 BSD 授权。其中两个环境依赖是Werkzeug和jinja2,这意味着它不需要依赖外部库。正因如此,我们将其称为轻量级框架。
Flask会话使用签名cookie让用户查看和修改会话内容。它会记录从一个请求到另一个请求的信息。不过,要想修改会话,用户必须有密钥Flask.secret_key。
基本流程
from flask import Flask
In [5]: app.url_map
Out[5]:
Map([<Rule '/' (HEAD, OPTIONS, GET) -> hello_world>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
In [4]: app.url_map.converters
Out[4]:
{'any': werkzeug.routing.AnyConverter,
'default': werkzeug.routing.UnicodeConverter,
'float': werkzeug.routing.FloatConverter,
'int': werkzeug.routing.IntegerConverter,
'path': werkzeug.routing.PathConverter,
'string': werkzeug.routing.UnicodeConverter,
'uuid': werkzeug.routing.UUIDConverter}
路由视图函数
正则匹配路由
int
@app.route('/<int:age>')
def age(age):
return 'here age is %d'%age
float
@app.route('/<float:hight>')
def age(hight):
return 'here hight is %f'%hight
path
@app.route('/index/<path:name>')
def str(name):
return 'welcome %s'%name
1 自定义转换器类,继承自BaseConverter
class MyRegexConverter(BaseConverter):
2 重写init方法
def __init__(self,map,regex):
3 初始化父类空间,子类空间(规则)
super(MyRegexConverter, self).__init__(map) 在父类的init中就有map参数,
完成了父类初始化成员变量
self.regex = regex
4 将自定义的转换器添加到系统默认的转换器列表中,创建一个键值对添加
app.url_map.converters["re"] = MyRegexConverter
#url_map当成第一个参数map传入到init中
#输出系统默认的转换器列表
print(app.url_map.converters)
#使用自定义的转换器
#接收三位整数
#使用re(规则),调用init的时候,实际上是传递了两个参数,
参数1: app.url_map,
参数2: \d{3}规则
@app.route('/<re("\d{3}"):number>')
def get_three_number(number):
return "the three number is %s"%number
@app.route('/',methods=['POST'])
def main():
return '<h1>this is main page</h1>'
视图函数
make_response()函数可以接受1/2/3个参数,跟视图函数的返回值一样, (响应内容,状态码,首部字典)
并返回一个response对象。
from flask import make_respoons
@app.route('/')
def main():
se = make_response("hello world")
se.status = "888"
# se.headers是一个字典
se.headers["Content-Type"]="application/json"
return se
dict = {
"name":"laowang",
"age":13
}
se = jsonify(dict)
return se
重定向
@app.route('/demo5')
defdemo5():
return redirect('http://www.itheima.com')
@app.route('/'):
return redirect(url_for('index'))
url_for
from flask import Flask,url_for,redirect
app = Flask(__name__)
@app.route('/jingdong')
def jingdong():
print(url_for("taobao",token=100)) #得到是taobao视图函数的地址
return redirect(url_for("taobao",token=100))
@app.route('/suning')
def suning():
print(url_for("taobao",token=200)) #得到是taobao视图函数的地址
return redirect(url_for("taobao",token=200))
@app.route('/taobao/<int:token>')
def taobao(token):
#判断重定向过来的token值
if token == 100:
return "欢迎京东客户,给你打骨折"
elif token == 200:
return "欢迎苏宁客户,给你打9折"
else:
return "欢迎其他网站客户,给你9.9折"
if __name__ == '__main__':
app.run()
生成连接程序被不同路由的链接时,使用 相对地址足够
如果要生成在浏览器之外使用的链接,必须使用绝对路径。
from flask import Flask, abort
app = Flask(__name__)
@app.route('/<int:age>')
def play_game(age):
#判断玩家年龄
if age > 18:
return "英雄联盟"
elif age > 16:
return "暴力摩托"
else:
abort(404)
#捕捉404异常
@app.errorhandler(404)
def page_not_found(e):
print(e)
return "<h1 style='color:red'>游戏服务器搬家了</h1>"
if __name__ == '__main__':
app.run(debug=True)
自定义错误页面
@app.errorhandle(404):
return render_templtate('404.html'),404
@app.errorhandle(500):
return render_templtate('500.html'),500
如统一处理状态码为500的错误
@app.errorhandler(500)
def internal_server_error(e):
return'服务器搬家了'
捕获指定异常
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
return'除数不能为0'
request对象
from flask import request
请求钩子
@app.before_first_request
def xxx():
在第一次访问时做的准备工作
@app.before_request
def before_request():
age = request.args.get('age')
if int(age) >18:
return 'welcome'
else:
return 'age not enough'
对于页面请求发送的拼接数据,进行判断和后续操作,
一旦在这里有return,后面的@app.route()将不会执行,页面显示在判断中的return值
@app.route('/')
def hello():
return 'main page'
@app.after_request
def after_request(rawreturn):
print(rawreturn) >><se 7 bytes [200 OK]>
rawreturn.status='444 not found'
resp.headers["Content-Type"] = "application/json"
return rawreturn
@app.teardown_request
def over(e):
print(e)
print('it\'s over time work')
在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量 g
例如:
before_request 处理程序可以从数据库中加载已登录用户,并将其保存到g.user中
随后调用视图函数时,视图函数再使用g.uesr获取用户
状态保持
http是一种无状态协议,浏览器请求服务器是无状态的。
协议对于事务处理没有记忆能力
对同一个 url 请求没有上下文关系
每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况
服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器
无状态:指一次用户请求时,浏览器、服务器不知道之前这个用户做过什么,每次请求都是一次新的请求。
无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。
有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
实现状态保持的两种方式
在客户端存储信息使用Cookie
cookie
指某些网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据(通常经过加密)。
Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。
Cookie的key/value可以由服务器端自己定义。
复数形式Cookies。
Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。
应用是判定注册用户是否已经登录网站
网站的广告推送
Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用
Cookie基于域名安全,不同域名的Cookie是不能互相访问的 基于浏览器的同源策略
当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息
# 设置cookie
@app.route('/set_cookie')
def set_cooke():
# 获取响应体的对象
res = make_response('set_cookie')
# 设置cookie
res.set_cookie('name','cookievalue1')
res.set_cookie('name1', 'cookievalue2',600)
return res
# 获取cookie
@app.route('/get_cookie')
def get_cookie():
value1 = request.cookies.get('name')
value2 = request.cookies['name1']
return 'cookie one is %s,cookie two is %s'%(value1,value2)
在服务器端存储信息使用Session
session
对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息
在服务器端进行状态保持的方案就是Session
Session依赖于Cookie
app.config["SECRET_KEY"]='asdasdsad'
# 设置session
@app.route('/set_session/<name>')
def set_session(name):
session['name']=name
return '设置session'
# 获取session
@app.route('/get_session')
def get_session():
name = session.get('name')
return 'session name is %s'%name
cookie session本质都是一个个的键值对
上下文
相当于一个容器,保存了flask程序运行过程中的一些信息
request 不可能是全局变量, 在多线程服务器中,多个线程同时处理不同客户端发送的不同请求时,每个线程看到的request对象必然是不同,
Flask 使用上下文让特定的变量在一个线程中全局可访问,与此同时不会干扰其他线程
线程是可单独管理的最小指令集,
进程经常使用多个活动线程,有时还会共享内存或文件句柄等资源
多线程web服务器会会创建一个线程池,在从线程池中选择一个线程用于处理接受到的请求
app.config.get('DEBUG')
等同于
current_app.config.get('DEBUG')
g对象
@app.before_request
def before_request():
g.name = "zhangsan"
@app.route('/')
def main():
return g.name
jinja2模板引擎
Jinja2:是 Python 下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。
模板语言:是一种被设计来自动生成文档的简单文本格式,在模板语言中,一般都会把一些变量传给模板,替换模板的特定位置上预先定义好的占位变量名。
flask的 render_template 函数吧 Jinja2模板引擎集成到程序中,
第一个参数模板的文件名,随后的参数都是键值对,表示模板中变量对应的真实值。
my_list = [
{
"id": 1,
"value": "我爱工作"
},
return render_template("1e.html",my_list=my_list)
控制结构
@app.route('/')
def hello_world():
num = 10
str = "隔壁老王在练腰"
tuple = (1,2,3,4)
list = [5,6,7,8,9]
dict = {
"name":"123",
"age":29
}
#携带数据渲染页面
return render_template("2.html",num=num,str=str,tuple=tuple,list=list,dict=dict)
前后端的数据交互 小胡子写法
在HTML显示后端发过来的数据 格式。
<h2>获取整数: {{num + 100}}</h2>
<h2>获取字符串: {{str}}</h2>
<h2>获取元祖: {{tuple}}, 分开获取:{{ tuple[0] }}, {{ tuple.1 }}</h2>
<h2>获取列表: {{list}},分开获取:{{ list.0 }}, {{ list.1 }}</h2>
<h2>获取字典: {{dict}},分开获取:{{ dict.name }},{{ dict["age"] }} </h2>
用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句
<h1>2.取出元祖中所有偶数</h1>
{% for item in tuple %}
{% if item %2 == 0 %}
<h2>{{ item }}</h2>
{% endif %}
{% endfor %}
loop
{'age': 123, 'name': 'yy'}
{% for key in dict %}
{% if loop.index==2%}
>>loop.index 从1开始迭代,
选择第二次迭代的值。
<h1>{{ dict[key] }}</h1> >>yy
{% elif loop.index0==0 %}
>> loop.index0 从0开始迭代,
选择第一次迭代的值
<h6>{{ dict[key] }}</h6> >> 123
{% endif %}
{% endfor %}
从1开始迭代
{% for item in list %}
{% if loop.index == 1 %}
<p>{{ item }}</p> >>获取第一次迭代获取到的值
{% elif loop.index == 2 %}
<p>{{ item }}</p> >>获取第两次迭代获取到的值
{% elif loop.index == 3 %}
<p>{{ item }}</p> >>获取第三次迭代获取到的值
{% else %}
<p>{{ item }}</p>
{% endif %}
{% endfor %}
{% for item in list if item !=9 %}
给循环加先前条件,值循环满足条件的列表
{{ item }}
{% endfor %}
<h1>3.遍历字典</h1>
{% for key in dict %}
{# dict.key中的key是一个字符串, dict[key]中的key是个变量 #}
<h2>{{ key }} = {{ dict[key] }}</h2>
{% endfor %}
循环遍历用的是一层 {} 中间添加%%来控制程序块
过滤器
safe
默认情况下,出于安全考虑,
Jinja2会转义所有变量。
例如 一个变量的值为 '<h1> hello </h1>'
jinja2会将其渲染为 '<h1> hello </&h1>
浏览器只能显示这个h1元素,但不会进行解释。
很多情况下需要显示变量中存储的HTML代码,这时就可使用safe过滤器
别再不可信的值上使用safe过滤器,例如用户再表单中输入的文本
自定义过滤器
```需求:
1.求列表中所有偶数和
2.实现列表反转
from flask import Flask,render_template
app = Flask(__name__)
方法1
先定义函数,再将函数添加到过滤器列表中
def oushu_sum(list):
sum = 0
for item in list:
if item %2 == 0:
sum += item
return sum
app.add_template_filter(oushu_sum,"OUSHU_SUM")
方法2
定义函数的时候, 就使用过滤器装饰
@app.template_filter("my_reverse")
def reverse_function(list):
list.reverse()
return list
@app.route('/')
def hello_world():
return render_template("file04custom_filter.html")
if __name__ == '__main__':
app.run(debug=True)
HTML文件
<h2>原列表: {{ [1,2,3,4,5,6,7] }}</h2>
<h2>原列表偶数和: {{ [1,2,3,4,5,6,7] | OUSHU_SUM}}</h2>
<h2>原列表反转: {{ [1,2,3,4,5,6,7] | my_reverse}}</h2>
{# 使用系统提供的过滤器和自定义过滤器实现降序输出 #}
<h3> 升序: {{ [5,8,1,3,9] | sort}} </h3>
<h3> 降序: {{ [5,8,1,3,9] | sort | my_reverse}} </h3>
模板代码复用
{% macro inputmode(name,password) %}
<p>username: <input type="text" name="username" value="{{ name }}"></p>
<p>password: <input type="password" name="password" value="{{ password }}"></p>
<p><input type="submit" value="提交按钮"></p>
{% endmacro %}
其他文件使用宏
其他文件other_macro.html
{% macro input(name,password) %}
<label>{{ name }}:</label><input type="text" name="username"><br>
<label>{{ password }}:</label><input type="password" name="username"><br>
{% endmacro %}
从other_macro.html引入宏
{% import 'other_macro.html' as other_macro %}
{{ other_macro.input2('用户名',"密码") }}
{{ other_macro.input3() }}
{% block contentmodel %}
<h1>静夜思</h1>
<h2>床前我明月光</h2>
<h2>疑似他地上霜</h2>
<h2>举头那望明月</h2>
<h2>低头他思故乡</h2>
<h3>作者: 李白</h3>
{% endblock %}
super
{% block contentmodel %}
{{ super()}}
<h1>这是重写的父类模板</h1>
{% endblock %}
模板特有的变量和函数
@app.route('/index2/<int:num>')
def index2(num):
return num
flask-moment 本地化日期和时间
CSRF
csrf_token阻止CSRF攻击
实现csrf 保护, flask-WTF需要程序设置一个秘钥,
flask-WTF使用这个秘钥生成加密令牌,再用领跑验证请求中表单数据的真伪,
流程
ajax 给表单设置一个事件
监听submit
数据库操作
orm
图示
流程
CURD
通过数据库管理对数据库所做的改动,
在flask-SQLAlchemy中,会话由 db.session表示
准备把对象写入数据库之前,先要将其添加到会话中
这里的session 和 上下文中的session没有关系,
数据库会话也称作为事务
数据库会话能保证数据库的一致性。
提交操作使用原子方式把会话中的对象全部写入数据库。
如果在写入回话的过程中发生了错误,整个会话都会失效。
如果始终把相关改动放在会话中提交,就能避免因为部分更新导致的数据库不一致性,
在准备把数据写入数据库前,先将数据添加到会话中然后调用 db.session.commit() 方法提交会话。
通过 query 对象操作数据。
最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询
当 模型.query 的时候,就已经把所有数据都给取出来了,
再通过过滤器,执行,得出想要的数据。
过滤器
filter()
把过滤器添加到原查询上,返回一个新查询
filter_by()
把等值过滤器添加到原查询上,返回一个新查询
limit
使用指定的值限定原查询返回的结果
offset()
偏移原查询返回的结果,返回一个新查询
order_by()
根据指定条件对原查询结果进行排序,返回一个新查询
group_by()
根据指定条件对原查询结果进行分组,返回一个新查询
执行器
all()
以列表形式返回查询的所有结果
first()
返回查询的第一个结果,如果未查到,返回None
first_or_404()
返回查询的第一个结果,如果未查到,终止请求、返回404错误响应
get()
返回指定主键对应的行,如不存在,返回None
get_or_404()
返回指定主键对应的行,如不存在,返回404
count()
返回查询结果的数量
paginate()
返回一个Paginate对象,它包含指定范围内的结果
查询数据后删除
user = User.query.first()
db.session.delete(user)
db.session.commit()
User.query.all()
更新数据
user = User.query.first()
user.name = 'dong'
db.session.commit()
User.query.first()
str
srt(User.query.filter_by(role = user_role))
SELECT users.id AS users_id, users_id,users.username AS users_username,
users.role_id AS users_role_id FROM users WHERE : param_1 = users.role_id
Role
class Role(db.Model):
__tablename__ = "roles"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(16),unique=True)
users = db.relationship("User",backref="role")
def __repr__(self):
return "<Role:%s>"%self.name
User
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(16),unique=True)
role_id = db.Column(db.Integer,db.ForeignKey(Role.id))
执行 role.users表达式的时候,隐含的查询会调用 all()返回一个用户列表
query对象是隐藏的,因此无法指定更精确的查询过滤器。
是通过过滤器和执行器的搭配来实现,过滤器可以没有,但一定要执行
表关系
数据库迁移
追踪数据库模式的变化,然后把变动应用到数据库中
为了备份表结构
迁移流程
使用命令进行迁移操作
from flask.ext.script import Shell
def make_shell_context():
return dict(app=app,db=db.User=User,Role=Role)
manager.add_command('shell',Shell(make_context = make_shell_context))
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand
app = Flask(__name__)
#2 设置数据库配置信息,关联app对象
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:mysql@localhost:3306/day04'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
#3使用migrate关联app和db
Migrate(app,db)
#4使用manager 管理app
manager = Manager(app)
#5使用migratecommand 给manager添加操作命令
manager.add_command('db',MigrateCommand)
#6 创建模型类
class Student(db.Model):
__tablename__ = 'student1'
id = db.Column(db.Integer,primary_key=1)
name = db.Column(db.String(16))
@app.route('/')
def hello():
return 'hello'
if __name__ == '__main__':
manager.run()
蓝图 Blueprint
方法1:先在其他文件中定义好方法,再到启动入口装饰视图函数,缺点:耦合性太强,一个地方改动,其他也得动,所以引入了蓝图
方法2: flask中提供的类,不需要安装就可以使用
使用蓝图对象装饰视图函数
文件1
#1 导入蓝图类
from flask import Blueprint
#2 创建蓝图对象
product_blue = Blueprint('blue',__name__)
blue: 该蓝图对象装饰的视图函数到时候都在blue地下,
#3 使用蓝图装饰视图函数
@product_blue.route('/')
def hello():
return 'hello'
@product_blue.route('/index')
def index():
return 'index'
将蓝图注册到app对象中
app.register_blueprint(blue_product)
#1 导入类
from flask import Blueprint
#2 创建蓝图对象
blue_product = Blueprint('blue',__name__)
#4 引入试图函数
from day4.user import view
(此时不存在循环导入蓝图对象的问题,Python是弱类型语言,只要在内存中存在之后,就不再引入
)
views.py
from day4.user import blue_product
引入蓝图对象来装饰
#3 用蓝图装饰视图函数
@blue_product.route('/')
def hello():
return 'hello'
@blue_product.route('/')
def index():
return 'index'
@blue_product.route('/')
def error():
return 'error'
入口文件
from flask import Flask
from day4.user import blue_product
引入蓝图对象
app = Flask(__name__)
#5 将蓝图对象注册到app中
app.register_blueprint(blue_product)
if __name__ == '__main__':
print(app.url_map)
app.run(debug=True)
app.url_map >>
Map([<Rule '/' (HEAD, GET, OPTIONS) -> blue.hello>,
<Rule '/' (HEAD, GET, OPTIONS) -> blue.index>,
<Rule '/' (HEAD, GET, OPTIONS) -> blue.error>,
<Rule '/static/<filename>'
(HEAD, GET, OPTIONS) -> static>])
from unittest import TestCase
#1 自定义类,继承TestCase
class Mytest(TestCase):
#2 两个固定方法 setup teardown
def setUp(self):
print('setup')
def tearDown(self):
print('teardown')
#3 编写测试方法
def test_data(self):
print('testtest')
self.assert...(boolean,msg)
断言
直接右键执行,开始测试
编写测试方法
def test_app(self):
self.assert....()
常见的断言方法
assertEqual 如果两个值相等,则pass
assertNotEqual 如果两个值不相等,则pass
assertTrue 判断bool值为True,则pass
assertFalse 判断bool值为False,则pass
assertIsNone 不存在,则pass
assertIsNotNone 存在,则pass
#coding=utf-8
import unittest
from author_book import *
#自定义测试类,setUp方法和tearDown方法会分别在测试前后执行。以test_开头的函数就是具体的测试代码。
classDatabaseTestCase(unittest.TestCase):
defsetUp(self):
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@localhost/test0'
self.app = app
db.create_all()
deftearDown(self):
db.session.remove()
db.drop_all()
#测试代码
deftest_append_data(self):
au = Author(name='itcast')
bk = Book(info='python')
db.session.add_all([au,bk])
db.session.commit()
author = Author.query.filter_by(name='itcast').first()
book = Book.query.filter_by(info='python').first()
#断言数据存在
self.assertIsNotNone(author)
self.assertIsNotNone(book)