zoukankan      html  css  js  c++  java
  • day96:flask:flask-migrate&flask-session&蓝图Blueprint&蓝图的运行机制&基于flask仿照django进行项目架构

    目录

    1.flask-migrate

    2.flask-session

    3.蓝图:Blueprint

    4.蓝图的运行机制

    5.基于flask仿照django进行项目架构

      1.准备工作

      2.加载配置文件

      3.生成蓝图

      4.路由-视图&路由分发

      5.模型类和数据库迁移

    1.数据库迁移:flask-migrate

    1.Flask的数据库迁移

    • 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。

    • 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。

    • 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。

    • 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

    2.flask-migrate的安装

    pip install flask-migrate

    3.在flask代码中引入数据库迁移

    from flask import Flask
    from config import Config
    from flask_sqlalchemy import SQLAlchemy
    from flask_migrate import Migrate,MigrateCommand # 1.引入Migrate和MigrateCommand
    from flask_script import Manager,Command
    
    app = Flask(__name__,template_folder='templates')
    app.config.from_object(Config)
    
    manage = Manager(app)
    
    db = SQLAlchemy(app)
    
    
    # 2.创建migrate对象。第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例
    migrate = Migrate(app,db)
    
    # 3.manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
    manage.add_command('db',MigrateCommand)
    
    
    achieve = db.Table('tb_achievement',
        db.Column('student_id', db.Integer, db.ForeignKey('tb_student.id')),
        db.Column('course_id', db.Integer, db.ForeignKey('tb_course.id'))
    )
    
    
    class Course(db.Model):
        __tablename__ = 'tb_course'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        price = db.Column(db.Numeric(6,2))
        teacher_id = db.Column(db.Integer, db.ForeignKey('tb_teacher.id'))
        students = db.relationship('Student', secondary=achieve, backref='courses', lazy='subquery')
    
        def __repr__(self):
            return 'Course:%s'% self.name
    
    class Student(db.Model):
        __tablename__ = 'tb_student'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        email = db.Column(db.String(64),unique=True)
        age = db.Column(db.SmallInteger,nullable=False)
        sex = db.Column(db.Boolean,default=1)
    
        def __repr__(self):
            return 'Student:%s' % self.name
    
    class Teacher(db.Model):
        __tablename__ = 'tb_teacher'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        courses = db.relationship('Course', backref='teacher', lazy='subquery')
    
        def __repr__(self):
            return 'Teacher:%s' % self.name
    
    
    @app.route("/")
    def index():
        return "ok"
    
    if __name__ == '__main__':
        manage.run()

    4.flask中数据库迁移常用的命令

    1.创建迁移版本仓库

    # 这个命令会创建migrations文件夹,所有迁移文件都放在里面。
    python main.py db init

    2.创建迁移版本仓库

    # 这里等同于django里面的 makemigrations,生成迁移版本文件
    python main.py db migrate -m 'initial migration'

    3.升级迁移版本库的版本

    python main.py db upgrade 

    4.降级迁移版本库的版本

    python main.py db downgrade

    5.回滚到指定版本

    python manage.py db downgrade 版本号   # 返回到指定版本号对应的版本

    6.查看数据库迁移历史(可查看数据库迁移版本号)

    python manage.py db history
    
    # 输出格式:<base> ->  版本号 (head), initial migration

    一般数据迁移的步骤是:init-->migrate-->upgrade/downgrade

    2.flask-session

    flask-session:允许设置session到指定存储的空间中

    安装命令: https://pythonhosted.org/Flask-Session/

    1.flask-session的安装和配置

    pip install flask-Session

    使用session之前,必须配置一下配置项:

    SECRET_KEY = "*(%#4sxcz(^(#$#8423" # session秘钥

    2.redis保存session的基本配置

    from flask import Flask,session
    from flask_redis import FlaskRedis
    from flask_session import Session
    app = Flask(__name__)
    redis = FlaskRedis()
    session_store = Session()
    class Config():
        # DEBUG调试模式
        DEBUG = True
    
        # json多字节转unicode编码
        JSON_AS_ASCII = False
    
        # 数据库链接配置
        SECRET_KEY = "*(%#4sxcz(^(#$#8423"
    
        # 1.session存储方式为redis
        SESSION_TYPE = "redis"
    
        # 2.session保存数据到redis时启用的链接对象
        SESSION_REDIS = redis
    
        # 3.如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
        SESSION_PERMANENT = True
    
        # 4.是否对发送到浏览器上session的cookie值进行加密
        SESSION_USE_SIGNER = True
    
        # 5.保存到redis的session数的名称前缀
        SESSION_KEY_PREFIX = "session:"
    
        # 6.redis的链接配置
        REDIS_URL = "redis://localhost:6379/1"
    
    
    app.config.from_object(Config) # 将Config类注册到app上
    redis.init_app(app)  # 将flask-redis对象挂载到app上
    session_store.init_app(app) # 将flask-session对象挂载到app上
    
    
    @app.route("/")
    def index():
        session["username"] = "xiaoming"
        return "Ok"
    
    @app.route("/get_session")
    def get_session():
        print( session["username"] )
        return "ok"
    
    @app.route("/redis1")
    def set_redis():
        # redis给集合数据类型/哈希数据类型设置值
        redis.set("username","xiaohuihui")
        redis.hset("brother","zhangfei","17")
        return "ok"
    
    @app.route("/redis2")
    def get_redis():
        user = redis.get("username").decode()
    
        brother = redis.hgetall("brother")
        print(brother["zhangfei".encode()].decode())
    
        return "ok"
    
    if __name__ == '__main__':
        app.run()

    执行程序,访问127.0.0.1:5000,即可将session值存储到redis中

    如图所示:

    3.SQLAlchemy存储session的基本配置

    from flask import Flask,session
    from flask_redis import FlaskRedis
    from flask_session import Session
    from flask_sqlalchemy import SQLAlchemy
    
    app = Flask(__name__)
    db = SQLAlchemy()
    redis = FlaskRedis()
    session_store = Session()
    
    class Config():
    
        ......
        '''数据库链接配置'''
        # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
        SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4"
        
        # 动态追踪修改设置,如未设置只会提示警告
        SQLALCHEMY_TRACK_MODIFICATIONS = True
        
        # 查询时会显示原始SQL语句
        SQLALCHEMY_ECHO = True
    
        '''数据库保存session'''
        SESSION_TYPE = 'sqlalchemy'  # session类型为sqlalchemy
        SESSION_SQLALCHEMY = db  # SQLAlchemy对象
        SESSION_SQLALCHEMY_TABLE = 'tb_session'  # session要保存的表名称
        SESSION_PERMANENT = True  # 如果设置为True,则关闭浏览器session就失效。
        SESSION_USE_SIGNER = False  # 是否对发送到浏览器上session的cookie值进行加密
        SESSION_KEY_PREFIX = 'session:'  # 保存到session中的值的前缀
    
    db.init_app(app)
    app.config.from_object(Config)
    redis.init_app(app)
    session_store.init_app(app)
    
    @app.route("/")
    def index():
        session["username"] = "xiaohui"
        return "Ok"
    
    @app.route("/get_session")
    def get_session():
        return session["username"]
    
    if __name__ == '__main__':
        # with app.app_context():
        #     db.create_all()
        app.run()

    执行程序,访问127.0.0.1:5000,即可将session值存储到mysql中

    如图所示:

    3.蓝图:Blueprint

    1.蓝图:模块化

    随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过python的模块化管理,于是针对一个简单的flask程序进行模块化处理

    简单来说,Blueprint 是一个存储视图方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。

    Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:

    • 一个项目可以具有多个Blueprint

    • 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名

    • 在一个应用中,一个模块可以注册多次

    • Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的

    • 在一个应用初始化时,就应该要注册需要使用的Blueprint

    但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。

    Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个 蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效.

    2.简单使用蓝图

    1.创建一个蓝图的包,例如users,并在__init__.py文件中创建蓝图对象

    from flask import Blueprint
    # 1. 创建蓝图目录并对蓝图对象进行初始化
    users_blue = Blueprint("users",__name__,template_folder="users_templates",static_folder='users_static',static_url_path="/libs")

    2.在这个蓝图目录下, 创建views.py文件,保存当前蓝图使用的视图函数

    from . import users_blue
    from flask import render_template
    # 2. 编写视图
    @users_blue.route("/")
    def index():
        return render_template("index.html",title="users/index/index.html")
    
    @users_blue.route("/list")
    def list():
        return "users/list"

    3.users/__init__.py中引入views.py中所有的视图函数

    # 3. 注册视图
    from .views import *

    4.在主应用main.py文件中的app对象上注册这个users蓝图对象

    # 4. 注册蓝图
    from users import users_blue
    app.register_blueprint(users_blue,url_prefix="/users")

    当这个应用启动后,通过/users/可以访问到蓝图中定义的视图函数

    3.蓝图的url前缀

    当我们在应用对象上注册一个蓝图时,可以指定一个url_prefix关键字参数(这个参数默认是/)

    • 在应用最终的路由表 url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的URL规则而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可

    • url_for在使用时,如果要生成一个蓝图里面的视图对应的路由地址,则需要声明当前蓝图名称+视图名称

    url_for('users.home') # /users/home

    4.注册蓝图的静态文件的相关路由

    1.static_folder:设置静态文件目录

    和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 static_folder 参数。

    下面的示例将蓝图所在目录下的static_users目录设置为静态目录

    from flask import Blueprint
    # 通过static_folder参数设置静态文件目录
    users_blue = Blueprint("users",__name__,static_folder='users_static')

    2.static_url_path:改变静态文件的路由

    定制静态目录URL规则 :可以在创建蓝图对象时使用 static_url_path 来改变静态目录的路由。

    下面的示例将为 users/static 文件夹的路由设置为 /lib

    from flask import Blueprint
    # 通过static_url_path设置静态文件路由
    users_blue = Blueprint("users",__name__,static_folder='users_static',static_url_path='/lib')

    访问http://127.0.0.1:5000/users/libs/1.jpg 即可查看到图片

    5.设置蓝图中模板的目录

    蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用 template_folder 关键字参数设置模板目录

    users_blue = Blueprint("users",__name__,static_folder='users_static',static_url_path='/libs',template_folder='users_templates')

    注意:如果在 templates 中存在和 templates_users 有同名模板文件时, 则系统会优先使用 templates 中的文件

    4.蓝图的运行机制

    1.蓝图运行机制的简要介绍

    • 蓝图是保存了一组将来可以在应用对象上执行的操作,注册路由就是一种操作

    • 当在app对象上调用 route 装饰器注册路由时,这个操作将修改对象的url_map路由表

    • 然而,蓝图对象根本没有路由表,当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项

    • 当执行app对象的 register_blueprint() 方法时,应用对象将从蓝图对象的 defered_functions 列表中取出每一项,并以自身作为参数执行该匿名函数,即调用应用对象的 add_url_rule() 方法,这将真正的修改应用对象的usr_map路由表

    2.蓝图运行机制的详细介绍

    5. 基于flask仿照django进行项目架构

    1.准备工作

    1.在项目中创建manage.py作为项目的启动文件

    2.确定项目的目录结构

    3.在application的__init__.py中,实例化flask对象和终端脚本管理对象Manager

    并在最后将manager终端管理对象返回出来

    from flask import Flask
    from flask_script import Manager
    
    # 创建应用对象
    app = Flask(__name__)
    
    # 创建终端脚本管理对象
    manager = Manager()
    
    def init_app():
        # 挂载应用对象
        manager.app = app
    
        # 对外暴露终端管理对象
        return manager

    4.在manager.py中,只做一件事:就是运行项目

    from application import init_app
    manager = init_app()
    if __name__ == '__main__':
        manager.run()

    2.加载配置文件

    1.在项目的application文件夹中创建settings文件夹,用来存放配置信息。

    2.我们将一些公共的配置写在settings/__init__.py中

    class InitConfig():
        DEBUG = True
        # 总路由的地址
        URL_PATH = "application.urls"
        # 蓝图列表
        INSTALLED_APPS = [
    
        ]
    
        # 数据库链接配置
        # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
        SQLALCHEMY_DATABASE_URI = ""
        # 动态追踪修改设置,如未设置只会提示警告
        SQLALCHEMY_TRACK_MODIFICATIONS = True
        # 查询时会显示原始SQL语句
        SQLALCHEMY_ECHO = True

    3.将本地开发配置文件写在dev.py中

    创建一个配置类,要继承我们上面写的公共配置类

    from . import InitConfig
    class Config(InitConfig):
        """本地开发配置文件"""
        INSTALLED_APPS = [
            "application.apps.home",
            "application.apps.users",
        ]
        # 数据库链接配置
        SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4"

    4.将线上运营配置文件写在prod.py中

    ---------------------------------

    现在配置文件的目录包括里面的相关内容都已经写好了,那么如何在项目当中使用我们已经写好的配置呢?

    from application import init_app
    # 将我们的dev配置文件加载进来
    manager = init_app("application.settings.dev")
    if __name__ == '__main__':
        manager.run()

    application.settings.dev作为init_app的参数被传了进来,所以我们在application/__init__.py中定义的init_app方法也要添加这个参数

    现在的问题是:我们可不可以基于这个config_path来完成自动加载配置文件的信息呢?

    1.在application中创建utils文件夹,并在utils中的__init__.py定义load_config方法

    def load_config(config_path):
        """自动加载配置"""
        # import_module 根据字符串路径直接进行模块导包,识别模块中的变量成员
        module = import_module(config_path)
    
        # 获取配置文件路径的最后一段:
        '''
        1.如果是settings 加载的就是公共配置类
        2.如果不是settings,那么按照现在来说,必然是dev或者prod
        '''
        config_file_name = config_path.split(".")[-1]
    
        if config_file_name == "settings":
            return module.InitConfig # 后缀是settings,返回公共配置类
        return module.Config # 后缀是dev/prod,返回自己的config类

    2.在application中的__init__.py中增添如下代码,让APP初始化的时候自动将你在manager.py中传入的配置文件中的类加载进去。

    from flask import Flask
    from flask_script import Manager
    
    # 创建应用对象
    app = Flask(__name__)
    
    # 创建终端脚本管理对象
    manager = Manager()
    
    def init_app():
        # 自动加载配置
        Config = load_config(config_path) # 通过自己定义的加载配置类方法,可得到应该加载的配置类是哪个类
        app.config.from_object(Config) # 将配置类注册到app上
        
        # 挂载应用对象
        manager.app = app
    
        # 对外暴露终端管理对象
        return manager

    3.生成蓝图

    1.在application中创建apps,用来存放各个蓝图。

    那么我们如何通过输入命令来自动创建蓝图,并在创建的蓝图中存在默认的model,view,url等文件呢?

    在utils创建command.py文件,定义BluePrintCommand类,来实现通过命令创建蓝图并生成蓝图内部的相关文件

    from flask_script import Command, Option
    import os
    class BluePrintCommand(Command):
    
        name = "blue"  # 命令的调用别名
        option_list = [
            Option("--name","-n",help="蓝图名称")
        ]
    
        def run(self,name=None):
            if name is None:
                print("蓝图名称不能为空!")
                return
            if not os.path.isdir(name):
                os.mkdir(name)
            open("%s/views.py" % name,"w")
            open("%s/models.py" % name,"w")
            with open("%s/urls.py" % name,"w") as f:
                f.write("""from . import views
    urlpatterns = [
    
    ]
    """)

    2.自定义的命令已经写好了,那么如何才能让系统自动加载我们自定义的命令呢?这就需要在util/__init__.py中定义一个自动加载命令的方法了

    def load_command(manager, command_path):
        """自动加载命令"""
        module = import_module(command_path)
        # 搜索当前模块下的所有类
        class_list = inspect.getmembers(module,inspect.isclass)
        for class_name,class_object in class_list:
            if issubclass(class_object,Command) and class_name != "Command":
                manager.add_command(class_object.name, class_object)

    3.在application的__init__.py中,让其自动加载我们自定义的终端命令

    from flask import Flask
    from flask_script import Manager
    
    # 创建应用对象
    app = Flask(__name__)
    
    # 创建终端脚本管理对象
    manager = Manager()
    
    def init_app():
        # 自动加载配置
        Config = load_config(config_path) # 通过自己定义的加载配置类方法,可得到应该加载的配置类是哪个类
        app.config.from_object(Config) # 将配置类注册到app上
        
        # 挂载应用对象
        manager.app = app
        
        # 自动加载终端命令 ***
        load_command(manager,"application.utils.commands")
    
        # 对外暴露终端管理对象
        return manager

    4.现在我们在输入命令:python ../../ manager.py blue -n=home 即可在app目录下创建一个名字为home的蓝图

    4.路由-视图&路由分发

    1.之前我们在django的时候,在urls.py中写上path(路由,视图函数)即可实现路由和视图函数的关联

    那如果自己去手动实现这个功能,应该怎样去做呢?

    1.首先,在urls.py中,是有一个path方法的,里面有两个参数,一个是路由,一个是视图函数

    那我们就在utils的__init__.py中定义一个path方法,path方法只做一件事:接收参数,并返回一个字典。

    def path(url,view_func):
        """注册路由"""
        return {"rule":url, "view_func":view_func}

    2.自动加载蓝图对象

    在自动加载蓝图的方法中,做了如下几件事:

    1.在创建蓝图对象的时候,自动注册路由【通过当前蓝图对象.add_url_rule(url,view_func)来实现】

    def load_blueprint(app,db):
        """自动加载蓝图"""
    
        # 在从配置中根据INSTALLED_APPS进行循环,在循环中进行蓝图的注册
        for blueprint_path in app.config.get("INSTALLED_APPS"):
            # 获取蓝图名称
            blueprint_name = blueprint_path.split(".")[-1]
    
            # 创建蓝图对象
            blue = Blueprint(blueprint_name,blueprint_path)
    
            # 蓝图对象自动注册路由
            blue_urls_module = import_module(blueprint_path+".urls")
            for urls_item in blue_urls_module.urlpatterns:
                blue.add_url_rule(**urls_item)
    
            # 注册蓝图
            app.register_blueprint(blue,url_prefix="")

    2.我们可能会遇到不同蓝图中有相同url路径的情况,为了对他们加以区分,我们应该对url再加上一个前缀

    比如:/home/index和/users/index

    所以需要加载蓝图的时候,自动添加一个前缀

    首先,在utils下的__init__.py中定义include方法,用来进行路由分发

    def include(url, blueprint_path):
        return {"url_prefix":url, "path": blueprint_path}

    然后获取蓝图的url前缀(前缀就是蓝图名称)

    def load_blueprint(app,db):
        """自动加载蓝图"""
        app_url_list = import_module(app.config.get("URL_PATH")).urlpatterns # application.urls.urlspatterns
        # 在从配置中根据INSTALLED_APPS进行循环,在循环中进行蓝图的注册
        for blueprint_path in app.config.get("INSTALLED_APPS"):
            # 获取蓝图名称
            blueprint_name = blueprint_path.split(".")[-1]
    
            for blueprint_url in app_url_list:
                if blueprint_url.get("path") == blueprint_name+".urls":
                    url_prefix = blueprint_url.get("url_prefix")
                    break
    
            # 创建蓝图对象
            # blue = Blueprint(blueprint_name,blueprint_path)
    
            # 蓝图对象自动注册路由
            # blue_urls_module = import_module(blueprint_path+".urls")
            # for urls_item in blue_urls_module.urlpatterns:
                # blue.add_url_rule(**urls_item)
    
            # 注册蓝图
            app.register_blueprint(blue,url_prefix=url_prefix)
    
            # 加载当前蓝图下的模型
            import_module(blueprint_path+".models")

    注意:先提前在配置类中写上这个,这样 app.config.get("URL_PATH") 才能获取到

    class InitConfig():
    
        # 总路由的地址
        URL_PATH = "application.urls"

    5.模型类和数据库迁移

    application/__init__.py

    def init_app(config_path):
    
        ......
        # 数据迁移初始化
        migrate.init_app(app,db)
        manager.add_command("db", MigrateCommand)
    
        return manager

    如果我们在application/apps/home/models.py写入一个模型类,然后执行数据库迁移指令,发现是无法进行数据库迁移的

    原因是识别不到你在蓝图内定义的模型类。那怎样能让其识别你在蓝图中定义的模型类呢?

    我们需要在注册蓝图之后中加载当前蓝图下的模型

    utils/__init__.py

    def load_blueprint(app,db):
        """自动加载蓝图"""
           ......
    
            # 加载当前蓝图下的模型
            import_module(blueprint_path+".models")
  • 相关阅读:
    第二十九课 循环链表的实现
    第二十八课 再论智能指针(下)
    第二十七课 再论智能指针(上)
    第二十六课 典型问题分析(Bugfix)
    普通new和placement new的重载
    leetcode 581. Shortest Unsorted Continuous Subarray
    leetcode 605. Can Place Flowers
    leetcode 219. Contains Duplicate II
    leetcode 283. Move Zeroes
    leetcode 217. Contains Duplicate
  • 原文地址:https://www.cnblogs.com/libolun/p/14033394.html
Copyright © 2011-2022 走看看