目录
请求与响应
请求
响应
登录案例
配置文件写法
路由系统
路由写法
转换器
CBV
session的使用和原理
flask-session的使用
闪现flash
请求扩展
g对象
蓝图
小型蓝图
大型蓝图
数据库连接池
wtforms
信号
内置信号
自定义信号
flask-script
Flask介绍和安装
python web框架
同步框架:
- Django:大而全(3.x以后支持异步)
- flask:小而精,插件的支持
异步框架:Sanic、FastAPI
flask介绍
Flask是一个基于Python开发并且依赖jinja2模板(DTL)和Werkzeug WSGI(符合wsgi协议的web服务器,wsgiref)服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
flask安装
pip install flask
简单使用
from flask import Flask
# 名字随意
app = Flask('main')
# 路由
@app.route('/')
def index():
return 'hello world'
if __name__ == '__main__':
# 运行
app.run()
访问:
http://127.0.0.1:5000/
请求与响应
请求
flask中获取请求的数据都是通过全局的模块:request。虽然是全局,但是用起来确实各自独立的。
from flask import request
请求方法 | 作用 |
---|---|
request.method | 请求方式 |
request.args | get 请求参数 |
request.form | post 请求参数 |
request.values | get,post请求的参数总和 |
request.files | 文件 |
request.cookies | 请求cookies |
request.headers | 请求头 |
request.path | 服务器地址后面的路径(不带get请求参数) |
request.full_path | 服务器地址后面的路径(带get请求参数) |
request.url | 带服务器地址的路径(带get请求参数) |
request.base_url | 带服务器地址的路径(不带get请求参数) |
request.url_root | 服务器IP地址:端口 |
request.host_url | 服务器IP地址:端口 |
request.host | 服务器IP地址:端口(不带http) |
响应
返回字符串
类比django中HttpResponse('字符串')
from flask import Flask
# 名字随意
app = Flask('main')
# 路由
@app.route('/')
def index():
return '字符串'
if __name__ == '__main__':
# 运行
app.run()
返回模板(网页)
类比django中Render(request,模板,{})
from flask import Flask, render_template
# 名字随意
app = Flask('main')
# 路由
@app.route('/')
def index():
return render_template(模板, 参数1=?, 参数2=?)
if __name__ == '__main__':
# 运行
app.run()
返回重定向
类比django中Redirect('路由')
from flask import Flask, redirect
# 名字随意
app = Flask('main')
# 路由
@app.route('/')
def index():
return redirect('路由')
if __name__ == '__main__':
# 运行
app.run()
返回json
类比django中JsonResponse
from flask import Flask, jsonify
# 名字随意
app = Flask('main')
# 路由
@app.route('/')
def index():
return jsonify(字典或列表..)
if __name__ == '__main__':
# 运行
app.run()
响应包装
方法:
from flask import make_response
使用:
res = make_response(render_template('home.html'))
# 写入cookie
res.set_cookie('name','lqz')
# 删除cookie
res.delete_cookie('name')
# 添加响应头
res.headers[''] = ''
# 返回
return res
登录案例
功能:登录成功cookie保存,没有登录时无法访问特定页面。
目录结构:
项目名
├── templates -- 网页文件夹,必须叫templates
├ ├── detail.html -- 单用户详情页
├ ├── index.html -- 用户列表
├ └── login.html -- 登录页面
└── login.py -- 功能实现
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
{% for k,v in user_dict.items() %}
<tr>
<td>{{ k }}</td>
<td>{{ v.name }}</td>
<td>{{ v['name']}}</td>
<td>{{ v.get('name')}}</td>
<td><a href="/detail/{{ k }}">查看详细</a></td>
</tr>
{% endfor %}
</table>
</body>
</html>
detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>详细信息 {{ info.name }}</h1>
<div>
{{ info.text }}
</div>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
用户名<input type="text" name="username">
密码<input type="password" name="password">
<input type="submit" value="登录">
<p>{{ error }}</p>
</form>
</body>
</html>
login.py
from flask import Flask, render_template, redirect, jsonify, request, session
from functools import wraps
app = Flask('main')
app.secret_key = 'abcdd' # 如果要使用session,必须写秘钥
# 用户信息,暂时用全局变量存储
USERS = {
1: {'name': '张三', 'age': 18, 'gender': '男', 'text': "道路千万条"},
2: {'name': '李四', 'age': 28, 'gender': '男', 'text': "安全第一条"},
3: {'name': '王五', 'age': 18, 'gender': '女', 'text': "行车不规范"},
}
# 登录装饰器,判断是否登录
def login_decorator(func):
@wraps(func) # 装饰器修复技术
def inner(*args, **kwargs):
username = session.get('username')
if username:
return func(*args, **kwargs)
else:
# 没有登录返回登录页面
return redirect('/login')
return inner
@app.route('/')
@login_decorator
def index():
return render_template('index.html', user_dict=USERS)
@app.route('/detail/<int:pk>')
@login_decorator
def detail(pk):
return render_template('detail.html', info=USERS.get(pk))
# 只接受get和post请求
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
username = request.form.get('username')
password = request.form.get('password')
# 判断是否登录成功
if username == 'tom' and password == '123':
# cookie保存
session['username'] = 'tom'
# 成功跳转到http://127.0.0.1:5000/
return redirect('/')
else:
# 登录失败给页面传参显示错误信息
return render_template('login.html', error='用户名或密码错误')
if __name__ == '__main__':
# 运行
app.run()
配置文件写法
第一种:
app.secret_key='asfasdf'
app.debug=True
第二种:app所有的配置项都在app.config这个字典中
app.config['DEBUG']=True
第三种:仿django的settings.py 写法(导入)
app.config.from_pyfile('settings.py')
# settings.py
DEBUG=False
NAME='lqz'
第四种:通过类的方式(导入)
app.config.from_object('settings.DevelopmentConfig')
class Config(object):
DEBUG = False
DATABASE_URI = 'sqlite://:memory:'
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
class DevelopmentConfig(Config):
DEBUG = True
其他
app.config.from_envvar("环境变量名称")
app.config.from_json("json文件名称")
路由系统
路由写法
1.使用装饰器
@app.route('/index', endpoint='index')
def index():
return 'hello'
2.使用方法
def index():
return 'hello'
app.add_url_rule('/index', view_func=index)
app.add_url_rule('/index', view_func=index),路由有个别名,如果不写endpoint,会以函数名作为endpoint。
add_url_rule参数
rule:请求的路径,可以使用转换器
endpoint:别名--》反向解析
view_func:[视图类.as_view(name='xx')]或者[视图函数内存地址]
methods:允许的请求方式
# ---------以下不重要-----
defaults:字典,给视图函数传默认值
strict_slashes:对URL最后的 / 符号是否严格要求
redirect_to:重定向到
本质
这两种写法的本质是一样,装饰器的源码如下:
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
显然,装饰器内部调用了add_url_rule的方法。
转换器
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
使用
@app.route('/index/<int:pk>', endpoint='index')
def index(pk):
print(pk)
return 'hello'
访问:
http://127.0.0.1:5000/index/11
参数pk值为11
CBV
基于函数的视图叫FBV,基于类的视图叫CBV。
写法:继承MethodView,跟django很像
from flask.views import MethodView
class Home(MethodView):
# cbv只允许某个请求方式
methods=['POST', 'GET']
# cbv加装饰器
# decorators = (装饰器名字,装饰器名字2)
def get(self):
return 'home'
def post(self):
return 'home-post'
# as_view(name='home'),name就是路径的别名——endpoint
app.add_url_rule('/home', view_func=Home.as_view(name='home'))
源码剖析
Home.as_view()执行时,由于Home类和父类MethodView没有as_view()方法,所以这执行的其实是MethodView类的父类View中的方法。这个方法执行时返回了如下函数:
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue:
self = view.view_class(*class_args, **class_kwargs) # type: ignore
return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs)
这里用到了self.dispatch_request,而这个dispatch_request用的是MethodView类中的:
def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None)
if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}"
return current_app.ensure_sync(meth)(*args, **kwargs)
本质与django一样,利用反射去调用类中的方法。
session的使用和原理
在使用session之前必须现在设置一下密钥
app.secret_key="asdas" # 值随便
使用
from flask import Flask, session
app = Flask('main')
app.secret_key = 'abcdd' # 如果要使用session,必须写密钥
@app.route('/')
def index():
session['name'] = 'tom'
return 'index'
if __name__ == '__main__':
app.run()
原理
flask在设置session时,会将session对象序列化成字符串,然后放到cookie中,保存在客户端中。
获取session时,从cookie中取出字符串反序列化,得到session对象,在获取指定值。
源码分析
app.session_interface = SecureCookieSessionInterface()
session对象就是SecureCookieSessionInterface类实例化得到的。这个类中有两个方法:
open_session:当有请求来时,从cookie中取出字符串反序列化得到session对象。
def open_session(self, app, request):
s = self.get_signing_serializer(app)
if s is None:
return None
# 从cookie中获取字符串
val = request.cookies.get(self.get_cookie_name(app))
if not val:
return self.session_class()
max_age = int(app.permanent_session_lifetime.total_seconds())
try:
# 把字符串反序列化得到session对象
data = s.loads(val, max_age=max_age)
return self.session_class(data)
except BadSignature:
return self.session_class()
save_session:响应时,session对象序列化成字符串,然后放到cookie中,保存在客户端中
def save_session(self, app, session, response):
name = self.get_cookie_name(app)
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
# 如果session为空,直接返回,不设置cookie了
if not session:
# 如果session被修改为空,删除cookie
if session.modified:
response.delete_cookie(
name, domain=domain, path=path, secure=secure, samesite=samesite
)
return
if session.a***essed:
response.vary.add("Cookie")
if not self.should_set_cookie(app, session):
return
httponly = self.get_cookie_httponly(app)
# 过期时间
expires = self.get_expiration_time(app, session)
# 把session对象序列化成字符串
val = self.get_signing_serializer(app).dumps(dict(session))
# 设置到cookie中
response.set_cookie(
name,
val,
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
)
flask-session的使用
session默认以cookie的形式放到客户端中,如果想把session放到服务端保存(redis、mysql...),需要写一个类,重写open_session和save_session方法。而flask-session就已经帮我们做好了。
如果没有,安装:
pip install flask-session
存到服务端就不需要写密钥了。
方式一:
from flask import Flask, session
from flask_session import RedisSessionInterface
app = Flask('main')
app.session_interface = RedisSessionInterface(redis=None, key_prefix='index_')
"""
redis:Redis对象,为None默认为Redis()
key_prefix:存到redis中键名的前缀
"""
@app.route('/')
def index():
session['name'] = 'tom'
return 'index'
if __name__ == '__main__':
app.run()
方式二:通用方案
from flask import Flask, session
from redis import Redis
from flask_session import Session
app = Flask('main')
app.config['SESSION_TYPE'] = 'redis' # session存到哪里
app.config['SESSION_KEY_PREFIX'] = 'index_' # 存到redis中键名的前缀
app.config['SESSION_REDIS'] = Redis(host='127.0.0.1', port='6379') # Redis对象
Session(app)
@app.route('/')
def index():
session['name'] = 'tom'
return 'index'
if __name__ == '__main__':
app.run()
闪现flash
假设在a页面操作出错,跳转到b页面,想要在b页面显示a页面的错误信息,这时候就可以使用flash。
在一次请求中,把一些数据放在flash中,下次请求就可以从flash中取出来,取一次就没了。
本质其实就是放到session中。
放值:
from flask import flash
# 普通
flash(msg)
# 分类
flash(msg, category='err')
取值:
from flask import get_flashed_messages
# 普通取值
msg = get_flashed_messages()
# 分类取值
msg = get_flashed_messages(category_filter=['err'])
无论什么取值方式,取一次就没了(一个请求可以多次取值)
举例:
from flask import Flask, flash, get_flashed_messages,jsonify
app = Flask('main')
app.secret_key = 'abcdd' # 本质是使用session,所以需要密钥
@app.route('/')
def index():
flash('from index')
flash('from index xx', category='xx')
return 'index'
@app.route('/home')
def home():
msg_xx = get_flashed_messages(category_filter='xx')
return jsonify(msg_xx)
if __name__ == '__main__':
app.run()
请求扩展
请求扩展是使用装饰器装饰函数,让程序执行时某个时候会执行被装饰的函数。
1.before_request:有请求来时,会执行,如果有多个,从上往下依次执行。
如果被装饰的函数return None,继续走下一个请求扩展;如果return的是响应对象,直接返回,不继续往下走了。
@app.before_request
def expand():
print('请求来了')
@app.route('/')
def index():
print('index执行')
return 'index'
2.after_request:请求走了,会执行,如果有多个,从下往上依次执行。
必须返回响应对象。
@app.after_request
def expand(response):
print('请求走了')
return response
@app.route('/')
def index():
print('index执行')
return 'index'
3.before_first_request:项目启动后,第一次请求访问,会执行。
返回值不会影响程序。
@app.before_first_request
def expand():
print('第一次请求')
@app.route('/')
def index():
print('index执行')
return 'index'
4.teardown_request:无论程序是否出异常,都会触发它。
用于记录错误日志,app.debug = False模式才会触发
@app.teardown_request
def expand(e):
if e:
print('程序报错:', e)
@app.route('/')
def index():
print('index执行')
l = []
print(l[0])
return 'index'
5.errorhandler:监听某个状态码,如果是这个状态码的错误,就会触发它的执行。
如果被装饰的函数return None,继续走下一个请求扩展;如果return的是响应对象,直接返回,不继续往下走了。
@app.errorhandler(404)
def expand(e):
print(e)
return '页面不存在'
6.template_global:标签,用于模板
@app.template_global()
def add(a1, a2):
return a1 + a2
# {{ add(1,2) }}
7.template_filter:过滤器,用于模板
@app.template_filter()
def add(a1, a2, a3):
return a1 + a2 + a3
# {{ 1|add(2,3) }}
g对象
flask中有个 g 对象,在一次请求中,可以赋值,取值。只对当次请求有效。
from flask import Flask, g
app = Flask('main')
@app.before_request
def expand():
g.a1 = 'asd'
g.a2 = 'bbb'
def add():
return g.a1 + g.a2
@app.route('/')
def index():
return add()
if __name__ == '__main__':
app.run()
request也可以实现一样的功能,但是并不推荐,因为设置值时可能把一些已有的值覆盖。
蓝图
目前使用flask时,都是在一个py文件中编写,如果代码过多,就很不方便,所以可以使用蓝图来划分目录。
蓝图的使用步骤:
1.实例化得到蓝图对象,可以传一些参数
index = Blueprint(
'index',
__name__,
templates,
static
)
2.把蓝图对象在app中注册
app.register_blueprint(index, 路由前缀)
3.使用蓝图对象,注册路由
@index.route('/')
def get_index():
return ''
4.蓝图有自己的请求扩展,每个蓝图有自己的请求扩展
小型蓝图
项目名
├── 项目包
├ ├── static -- 存放静态文件(js、css、img..)
├ ├── templates -- 存放模板文件(html)
├ ├ └── index.html -- index页面
├ ├── views -- 存放视图的文件夹
├ ├ └── index.py -- index相关视图
├ └── __init__.py -- 包的init文件
└── manage.py -- 项目的启动文件
__init__.py
from flask import Flask
app = Flask(
__name__,
template_folder='templates', # 模板文件的文件夹名称,不写也行,默认就是这个值
static_folder='static', # 静态文件的文件夹名称,不写也行,默认就是这个值
static_url_path='/static' # 静态文件路径
)
from .views.index import index
# 注册蓝图,注册进app中
app.register_blueprint(index)
index.py
from flask import Blueprint
from flask import render_template
# 实例化得到蓝图对象
index = Blueprint('index', __name__)
# 通过蓝图对象编写路由
@index.route('/get_index')
def get_index():
return render_template('index.html')
manage.py
# 导入__init__.py中的app对象
from 项目包 import app
if __name__ == '__main__':
app.run()
html文件引入静态文件
/static/..
大型蓝图
项目名
├── 项目包
├ ├── index -- 一个应用一个包
├ ├── static -- 存放静态文件(js、css、img..)
├ ├── templates -- 存放模板文件(html)
├ ├ └── index.html -- index页面
├ ├── views.py -- 应用自己的视图函数
├ └── __init__.py -- 包的init文件
├ ├── home -- 一个应用一个包
├ ├── static -- 存放静态文件(js、css、img..)
├ ├── templates -- 存放模板文件(html)
├ ├ └── home.html -- home页面
├ ├── views.py -- 应用自己的视图函数
├ └── __init__.py -- 包的init文件
├ └── __init__.py -- 项目包的init文件
└── manage.py -- 项目的启动文件
index应用包下的__init__.py
from flask import Blueprint
# 初始化蓝图
index = Blueprint(
'index',
__name__,
template_folder='templates', # 指定该蓝图对象自己的模板文件路径
static_folder='static' # 指定该蓝图对象自己的静态文件路径
)
# 需要放在下面导入
from . import views
index应用包下的views.py
from . import index
from flask import render_template
# 使用蓝图注册路由
@index.route('/get_index')
def get_index():
return render_template('index.html')
项目包下的__init__.py
from flask import Flask
from .index import index
app = Flask(__name__)
app.debug = True
# 在app中注册蓝图
app.register_blueprint(index, url_prefix='/index') # 访问这个app的前缀,等同于include,/index/get_index
manage.py
# 导入__init__.py中的app对象
from 项目包 import app
if __name__ == '__main__':
app.run()
html文件引入静态文件
/index/static/..
数据库连接池
多个线程使用同一个连接对象,会导致数据错乱;每个线程使用一个连接,又会导致mysql连接数过大。
所以需要使用数据库连接池,让线程去连接池中拿连接,固定了连接数,也不会导致数据错乱。
借助于第三方模块,实现数据库连接池:
pip install dbutils
新建py文件 pool.py存放连接池对象
pool.py
from dbutils.pooled_db import PooledDB
import pymysql
POOL = PooledDB(
creator=pymysql, # 使用连接数据库的模块
maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数
mincached=2, # 初始化时,连接池中至少创建的空闲的连接,0表示不创建
maxcached=5, # 连接池中最多闲置的连接,0和None不限制
blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
maxusage=None, # 一个连接最多被重复使用的次数,None表示无限制
setsession=[], # 开始会话前执行的命令列表。
ping=0,
# ping MySQL服务端,检查是否服务可用。
host='127.0.0.1',
port=3306,
user='root',
password='123', # 密码
database='db_name', # 数据库名称
charset='utf8' # 编码
)
flask
from flask import Flask
from pool import POOL
app = Flask(__name__)
@app.route('/index')
def index():
# 从池中拿链接,创建出cursor对象
conn = POOL.connection()
cursor = conn.cursor()
# 数据库查询
cursor.execute('select * from table_name1 ')
print(cursor.fetchall())
return 'hello'
@app.route('/home')
def home():
# 从池中拿链接,创建出cursor对象
conn = POOL.connection()
cursor = conn.cursor()
# 数据库查询
cursor.execute('select * from table_name2')
print(cursor.fetchall())
return 'hello'
if __name__ == '__main__':
app.run()
压力测试
import requests
from threading import Thread
def task():
res = requests.get('http://127.0.0.1:5000/index')
print(res.text)
res = requests.get('http://127.0.0.1:5000/home')
print(res.text)
if __name__ == '__main__':
for i in range(10000):
t = Thread(target=task)
t.start()
"""
查看当前有多个个连接数 show status like 'Threads%'
"""
wtforms
wtforms类似于django前后端混合项目中使用的forms组件。
pip install wtforms
flask
from flask import Flask, request, render_template
app = Flask(__name__)
from wtforms.fields import core
from wtforms import Form
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
class LoginForm(Form):
# 字段(内部包含正则表达式)
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired(message='用户名不能为空.'),
validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
],
widget=widgets.TextInput(), # 页面上显示的插件
render_kw={'class': 'form-control'}
)
# 字段(内部包含正则表达式)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.'),
validators.Length(min=8, message='用户名长度必须大于%(min)d'),
validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",
message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
class RegisterForm(Form):
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired()
],
widget=widgets.TextInput(),
render_kw={'class': 'form-control'},
default='alex'
)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
pwd_confirm = simple.PasswordField(
label='重复密码',
validators=[
validators.DataRequired(message='重复密码不能为空.'),
validators.EqualTo('pwd', message="两次密码输入不一致")
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
email = html5.EmailField(
label='邮箱',
validators=[
validators.DataRequired(message='邮箱不能为空.'),
validators.Email(message='邮箱格式错误')
],
widget=widgets.TextInput(input_type='email'),
render_kw={'class': 'form-control'}
)
gender = core.RadioField(
label='性别',
choices=(
(1, '男'),
(2, '女'),
),
coerce=int # “1” “2”
)
city = core.SelectField(
label='城市',
choices=(
('bj', '北京'),
('sh', '上海'),
)
)
hobby = core.SelectMultipleField(
label='爱好',
choices=(
(1, '篮球'),
(2, '足球'),
),
coerce=int
)
favor = core.SelectMultipleField(
label='喜好',
choices=(
(1, '篮球'),
(2, '足球'),
),
widget=widgets.ListWidget(prefix_label=False),
option_widget=widgets.CheckboxInput(),
coerce=int,
default=[1, 2]
)
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))
def validate_pwd_confirm(self, field):
"""
自定义pwd_confirm字段规则,例:与pwd字段是否一致
:param field:
:return:
"""
# 最开始初始化时,self.data中已经有所有的值
if field.data != self.data['pwd']:
# raise validators.ValidationError("密码不一致") # 继续后续验证
raise validators.StopValidation("密码不一致") # 不再继续后续验证
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
form = LoginForm()
return render_template('login.html', form=form)
else:
form = LoginForm(formdata=request.form)
if form.validate():
print('用户提交数据通过格式验证,提交的值为:', form.data)
else:
print(form.errors)
return render_template('login.html', form=form)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
form = RegisterForm(data={'gender': 2, 'hobby': [1, ]}) # initial
return render_template('register.html', form=form)
else:
form = RegisterForm(formdata=request.form)
if form.validate():
print('用户提交数据通过格式验证,提交的值为:', form.data)
else:
print(form.errors)
return render_template('register.html', form=form)
if __name__ == '__main__':
app.run()
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post">
<p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
<p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
<input type="submit" value="提交">
</form>
</body>
</html>
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form method="post" novalidate style="padding:0 50px">
{% for field in form %}
<p>{{field.label}}: {{field}} {{field.errors[0] }}</p>
{% endfor %}
<input type="submit" value="提交">
</form>
</body>
</html>
信号
信号:某种情况下会触发某个函数执行。
flask中帮我们集成了信号的使用,但还是需要我们安装一个模块。
安装模块:
pip install blinker
内置信号
from flask import signals
request_started = _signals.signal('request-started') # 请求到来前执行
request_finished = _signals.signal('request-finished') # 请求结束后执行
before_render_template = _signals.signal('before-render-template') # 模板渲染前执行
template_rendered = _signals.signal('template-rendered') # 模板渲染后执行
got_request_exception = _signals.signal('got-request-exception') # 请求执行出现异常时执行
request_tearing_down = _signals.signal('request-tearing-down') # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down') # 应用上下文执行完毕后自动执行(无论成功与否)
appcontext_pushed = _signals.signal('appcontext-pushed') # 应用上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped') # 应用上下文pop时执行
message_flashed = _signals.signal('message-flashed') # 调用flask在其中添加数据时,自动触发
使用:
from flask import Flask, render_template
from flask import signals
app = Flask(__name__)
# 内置信号使用步骤
# 第一步:写个函数
def render_before(*args, **kwargs):
print(args)
print(kwargs)
print('模板要渲染了')
# 第二步:跟信号绑定
signals.before_render_template.connect(render_before)
# 第三步:触发信号 (内置信号,会自动触发)
@app.route('/')
def index():
print('index 执行了')
return render_template('index.html')
if __name__ == '__main__':
app.run()
自定义信号
from flask import Flask
from flask.signals import _signals
app = Flask(__name__)
# 第一步:定义信号
xxx = _signals.signal('xxx')
# 第二步:写个函数
def add(args):
print(args)
print('add执行了')
# 第三步:跟信号绑定
xxx.connect(add)
@app.route('/')
def index():
# 第四步:触发信号
xxx.send()
print('index 执行了')
return 'index'
if __name__ == '__main__':
app.run()
flask-script
djagno中执行djagno程序,通过命令:python manage.py runserver。
如果想让flask也能通过执行python manage.py runserver启动flask项目,或者一些其他的命令完成其他操作。
安装:
pip install flask-script
基本使用
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
manager = Manager(app)
@app.route('/')
def index():
return 'index'
if __name__ == '__main__':
# 需要run一下
manager.run()
终端输入命令:
python py文件名 runserver
自定义命令
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
manager = Manager(app)
@manager.***mand
def init():
print("初始化")
if __name__ == '__main__':
# 需要run一下
manager.run()
终端输入命令:
python py文件名 init
自定义高级命令
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
manager = Manager(app)
@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def cmd(name, url):
"""
自定义命令(-n也可以写成--name)
执行: python manage.py cmd -n tom -u xxx
执行: python manage.py cmd --name tony --url abc
"""
print(name, url)
if __name__ == '__main__':
# 需要run一下
manager.run()