Flask框架

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.accessed:
        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.command
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()
posted @ 2022-08-05 20:48  Yume_Minami  阅读(212)  评论(0编辑  收藏  举报