Flask03--flask-session、dbutils、信号、flask-script、flask_sqlalchemy、flask-migrate、wtfroms

1 flask-session

# 原生的session 把数据加密后放到了cookie中

# 如何将session值 放进数据库中,redis,文件中...
  两种方式:
    1.指定session类接口:自定义session类  或者 flask-session的 RedisSessionInterface
    2.第三方session库: flask-session 配置  (通用方案)

        
# 方式一:指定session类接口
# 先自定义session类 有:save_session 和 open_session---》操作redis存储和获取
conn = redis.Redis()
# 指定操作session的类--自定义session类 或 第三方 现成的redis-session类
app.session_interface = RedisSessionInterface(conn, 'lqz')  # 参数:redis连接对象,redis中key值的前缀



# 方式二:本质和方式一 一样,看源码  
   通用方案:flask集成第三方库的通用方案  基本也是实例化第三方的类,再包装下Flask对象

# 安装:pip install flask-session
# 区别:使用第三方以后,用法跟之前一样,只是在项目启动是,执行一些代码

from flask_session import Session

# 配置文件  (直接写 实际要写在配置文件的类中设置)
app.config['SESSION_TYPE'] = 'redis'  # 可以指定存储到其他地方
app.config['SESSION_KEY_PREFIX'] = 'lqz'  # redis中key值的前缀
app.config['SESSION_REDIS'] = redis.Redis()

Session(app)  
# 源码在这---》内部就是做了 app.session_interface=RedisSessionInterface(app.config['SESSION_REDIS'])


# 设置session的过期时间,在配置文件中设置
app.config['PERMANENT_SESSION_LIFETIME']=timedelta(seconds=7)


# RedisSessionInterface源码分析
    -save_session
    -open_session	

2 数据库连接池dbutils

# 直接使用pymysql 操作数据库时,需要链接池dbutils

# 为什么要有数据库连接池
  -随着并发量的越来越大,mysql的连接数也会增大
  -我们创建出连接池后,每次从池中获取连接使用,能够避免并发量过大,导致数据库崩掉的危险
    
# 使用DBUtils 
  DBUtils是Python的一个用于实现数据库连接池的模块
    
# 安装: pip install dbutils

import pymysql
from dbutils.pooled_db import PooledDB


# 获得一个数据库连接池对象    实际生产中:链接池 需要做成单例模型 (放进py文件中,作为模块导入)
POOL = PooledDB(
    creator=pymysql,  # 使用链接数据库的模块
    maxconnections=6,  # 连接池允许的最大连接数,0和None表示不限制连接数
    mincached=2,  # 初始化时,链接池中至少创建的空闲链接,0表示不创建
    maxcached=5,  # 链接池中最多的闲置链接,0和None不限制
    maxshared=3,  # 链接池中最多共享的链接数量,0和None表示全部共享
    # PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
    
    blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
    maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
    setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
    ping=0,
    # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
    
    host='127.0.0.1',
    port=3306,
    user='root',
    password='111',
    database='cnblogs',
    charset='utf8'
)


# 从池中拿出连接
def func():
    conn = POOL.connection()  # 从链接池中 获取一个连接对象
    cursor = conn.cursor()
    cursor.execute('select * from user')
    result = cursor.fetchall()
    print(result)
    conn.close()

if __name__ == '__main__':
    func()

3 wtfroms(了解)

# 等同于django的forms,表单验证、模板渲染

# 安装:pip install wtfroms


# 能看懂案例,复制修改即可

3.1 登录案例

from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets

app = Flask(__name__)

app.debug = True

# 写了个form类
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'}
    )



@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)

if __name__ == '__main__':
    app.run()
<!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>

3.2 注册案例

from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets

app = Flask(__name__, template_folder='templates')
app.debug = True



class RegisterForm(Form):
    name = simple.StringField(
        label='用户名',
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={'class': 'form-control'},
        default='egon'
    )

    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('/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()
<!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>

4 信号(重要)

# 信号  
  在视图任务的某些关键位置,绑定上信号函数,视图任务运行到该位置时,会触发执行
    
  # 好处:
    eg: 数据库新增成功后,都记录一条日志  
    原方法:需要在视图里每个操作完数据库之后,手动记录日志
    使用信号:只需要定义一个函数,信号在关键位置,都可以记录日志

        
# 注意:
  信号 和 并发编程中 信号量 不是同一个东西!!!


# flask内置信号   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在其中添加数据时,自动触发

4.1 内置信号使用

# 内置信号执行使用步骤:

# eg:
  模板渲染后信号执行
  功能:home页面被渲染后,记录一条日志


from flask import signals

# 第一步:写一个函数
def template_test(*args,**kwargs):
    print(args)
    print(kwargs)
    print('模板渲染完了')
    
# 第二步:注册到内置信号:template_rendered
signals.template_rendered.connect(template_test)

# 第三步:触发信号执行(不需要咱们操作)

4.2 自定义信号

from flask import signals

# 第一步:自定义一个信号
xxxxx =  signals._signals.signal('xxxxx')

# 第二步:写一个函数
def xxx_test(*args, **kwargs):
    print(args)
    print(kwargs)
    print('xxx信号触发了')


# 第二步:注册到自定义信号:xxxxx
xxxxx.connect(xxx_test)


# 第三步:触发信号执行   (手动触发,在某个位置触发,比如视图函数中)
@app.route('/')
def index():
    xxxxx.send('123123', k1='v1')  # 触发信号
    return 'hello'

5 flask-script 自定义命令

# 实现类似于这样的命令:python manage.py runserver

##### flask 自定义命令
pip3 install flask-script
  -自带一个runserver 命令
  -支持自定制命令

# 自定制命令 (数据库初始化,超级用户的创建)
  -eg:
    启动celery: 脚本文件里:使用subprocess('系统cmd或shell命令') 执行系统命令
    清空某个表的所有记录  # ptmysql操作数据库
    自动发布项目   # 执行一些 系统命令
        
  -作用:自己写了一些 比如项目准备的小脚本,eg: 创建库、数据库测试数据等函数
        然后用 自定制命令的形式  执行脚本 
        python  manage.py  命令函数  参数


# eg:
  把excel的数据导入数据库 定制个命令 去执行


    
##### django 自定义命令
  详见:https://pythondjango.cn/django/advanced/11-django-admin-commands/

app01/
    __init__.py
    models.py
    management/
        __init__.py
        commands/
            __init__.py
            _private.py  # 以下划线开头文件不能用作管理命令
            initdb.py # 这个就是自定义的管理命令脚本,文件名即为命令名
    tests.py
    views.py

    
# 步骤
  1.在app内创建一个management的包
  2.在management目录里面创建commands的包
  3.在commands文件夹下创建任意py文件
    # 一般建议每个python脚本文件对应一条管理命令

  4.命令文件内部
    from django.core.management.base import BaseCommand

    class Command(BaseCommand):
        # 帮助文本, 一般备注命令的用途及如何使用。
        help = 'Some help texts'

        # 处理命令行参数,可选
        def add_arguments(self, parser):
            pass

        # 核心业务逻辑
        def handle(self, *args, **options):
            pass


  # 执行自定义命令    
    python manage.py initdb xx.xsl article  
    # 只要执行这个命令,就向数据库的article表中写入xx.xsl的数据

5.1 基本使用

from flask import Flask

# 第一步 导入
from flask_script import Manager

app = Flask(__name__)

# 第二步 实例化Manger对象
manager = Manager(app)


@app.route('/')
def index():
    return 'hello'


if __name__ == '__main__':
    # 第三步 使用
    manager.run()
        
        
# 此时不支持 文件-右键执行了

# 采用命令行的形式运行
 python  文件名  runserver   
    
 # 文件名 改为:manage.py
   就变成 python manage.py runserver 

5.2 自定义命令

from flask import Flask
from flask_script import Manager

app = Flask(__name__)

manager = Manager(app)


# 自定义命令
@manager.command
def custom(arg):
    """
    自定义命令:python manage.py custom 123
    """
    print(arg)


@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def cmd(name, url):
    """
    自定义命令(-n也可以写成--name)
    执行: python manage.py  cmd -n lqz -u http://www.oldboyedu.com
    执行: python manage.py  cmd --name lqz --url http://www.oldboyedu.com
    """
    print(name, url)
    
    
if __name__ == '__main__':
    manager.run()

6 flask_sqlalchemy使用

# flask集成sqlalchemy模块 操作orm

# 方式一:
  采用scoped_session() ,全部导入sqlalchemy模板 来直接书写
    
# 方式二:
  采用第三方模块 flask_sqlalchemy  
    
    
# flask_sqlalchemy使用步骤

# setting.py 配置文件中  数据库的配置信息需要添加进去
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@127.0.0.1:3306/db1?charset=utf8"
    SQLALCHEMY_POOL_SIZE = 5
    SQLALCHEMY_POOL_TIMEOUT = 30
    SQLALCHEMY_POOL_RECYCLE = -1
    # 追踪对象的修改并且发送信号
    SQLALCHEMY_TRACK_MODIFICATIONS = False


# __init__.py中
1.导入from flask_sqlalchemy import SQLAlchemy 

2.实例化得到db对象
  db = SQLALchemy()    # 以后session和表的基类等所有的 都是在db对象中获取

3.在app中注册   
  db.init_app(app)

    
# models.py中
4.表模型继承 db.Model


# views中
5.session是db.session
  db.session使用即可
    
        
# 存在问题
  1 表迁移麻烦
  2 不支持字段的动态修改

7 flask-migrate使用

# 像djagno一样,执行两条迁移命令,实现数据库的动态迁移

# 安装
  pip install flask-migrate    # 该模块是基于 flask-script 自定义脚本命令的形式

        
# 使用步骤
1.导入
  from flask_script import Manager
  from flask_migrate import Migrate, MigrateCommand
    
2.执行
manager = Manager(app)  # 先实例化 Manager类

Migrate(app, db)
manager.add_command('db', MigrateCommand)  # 用Manager类注册自定义命令


3.命令行执行命令
python manage.py db init  # 初始化:只执行一次,会生成一个migrations文件夹

# 以后直接新建表,新建/修改字段,执行命令,就会自动同步
python manage.py db migrate   # 等同于 makemigartions
python manage.py db upgrade   # 等同于 migrate


# 它是如何实现的呢?
  在migrations-->versions目录里面,有一个xx.py,它记录的models.py的修改
  那么它和django也是同样,有一个文件记录变化
posted @ 2022-08-11 16:13  Edmond辉仔  阅读(33)  评论(0编辑  收藏  举报