Flask基础二
蓝图的引入
虽然一个py文件确实能够写完所有的flask功能,从路由到视图到数据库,但是实际上并没有任何实际项目这么做,原因在于管理起来非常不方便,作为一个成熟的框架怎么可能会存在管理混乱的现象,所以我们先简单的看视图py文件和运行py文件进行分离的问题
运行py文件: main_func文件
from flask import Flask
app = Flask(__name__)
import the_view # 导入的时候就会执行
if __name__ == '__main__':
app.run()
上面是主运行文件,创建一个app后应该绑定视图的,但是这既然要视图和主程序进行分离,则将视图写成一个新的py文件进行导入
视图文件: the_view
from other_file.main_func import app
@app.route('/')
def show():
return '显示'
视图函数中没有app,则需要从主文件中进行导入,看起来没问题,导入之后就能够进行注册视图
实际上启动之后并不会成功显示,main_func运行的时候,创建了一个app对象后继续向下运行,运行到import the_view语句的时候就将视图文件的代码加载进来并执行
此时执行视图文件,那么视图文件的第一条就是导入主文件,好的,在视图文件执行第一行的时候就把主文件加载进来执行,此时主文件又执行
from flask import Flask
app = Flask(__name__)
那么问题就出现了,这里再一次实例化了一个app对象,当然还没有执行完,后面继续执行导入视图文件,但是因为python在底层就已经设定了防止循环导入的设置,所以种里导入操作不成功,执行完成,所以种类就算是把视图文件的第一行执行完了,接下来视图文件就用新的app对象进行注册函数
视图文件执行完成后,主文件的导入视图文件语句执行完成,继续向下执行,当执行到app.run()的时候问题就来了,主文件中的app实际上与在导入视图文件时注册的app并不是同一个,那么用这个app启动的服务器并没有把视图注册进来,从而不管怎么调整都是404
这里就是因为
app = Flask(__name__)
属于全局变量,从而导致只要这个py文件被调用,这个对象就会重新创建,所以就导致注册的app与启动服务器的app并不一致,flask为了解决这种问题使用了蓝图
蓝图
蓝图的使用分为三步,第一步导入库,第二步实例化蓝图对象,第三步绑定对应的视图
那么如果是出现功能分离,不能在一个页面中进行显示的话,那么蓝图也会被其他py文件进行导入,这种导入就是单向的导入
主py文件
from flask import Flask
app = Flask(__name__)
# 主应用中进行蓝图的注册
from other_file.the_view import bp # 把对应的蓝图进行导入,因为我是放在了other_file文件里面
app.register_blueprint(bp)
if __name__ == '__main__':
app.run()
视图py文件
from flask import Blueprint
bp = Blueprint('the_view', __name__)
@bp.route('/')
def show():
return '显示'
这样就能实现的是单向导入,能够避免两个py文件进行相互调用而出现app重复建立的情况
以上的蓝图建立步骤在实际的工程中并不是这样的,视图文件单独存放在一个包中,这个包用于应用进行调用,而如果每一个视图中进行执行导包再实例化会非常繁琐,所以在包的init文件中进行统一注册,然后视图py文件再导进来,这个才是真实工程中的结构
管理方法
对于一个工程而言,实际上对框架的调用等等是服务器这边找对应的接口文件,接口文件找到我们的代码,为了方便管理,我们需要将代码个构建表现的非常清晰,这有助于我们管理以及后续的修改
回想在django中在各种表的迁移的时候是直接进入manager进行敲命令的,在flask中没有这样的名利,flask引入的是别人的框架进行完成这个过程,需要安装一个包flask-script
from flask import Flask
from flask_script import Manager # 导入应用
# 实例化
app = Flask(__name__)
# 实例化manager
manager = Manager(app=app)
@app.route('/')
def show():
return '响应内容'
if __name__ == '__main__':
manager.run()
这么写之后就不能进行直接run了,需要在命令窗口进行跑,这里就很像是模拟了在服务器上的运行一样,我们的项目只需要提供对应的接口就行了
这个模块可以进行交互式的操作
实际项目会存在两个py文件进行总的主文件作为执行,一个是用于开发人员进行开发调试用的,另外一个是用来进行服务器调用的,整个项目结构层层嵌套,flask是一个松散的结构,所以为了自己学习,最初还是进行自己设计这些结构,理解其中的关系
结构
对比一下django中,一旦建立了项目,pycharm会自动生成对应的一大堆文件,那么在与项目同名的主文件中我们就能看到settings这种配置文件,同时我们还能看到url的路由文件,model的模型文件,以及form表单验证的文件等等,当然flask也可以有这么多,只是为了理解透彻我们现在自己来搭建
首先明确项目创立后需要两个py文件进行对应的开发调试人员用的接口和运维调试人员用的接口,这两个接口分别对应了不同的应用场景,这里都是入口文件
入口文件需要实现的功能就像上面的py文件一样,需要将app注册成交互式的管理模式,并且能够在入口文件进行run,然而app对象并不是在这个入口文件进行创建的,应用文件是单独的一个文件夹,在项目给别人看的时候实际上不能给别人一看就是一大堆文件,找半天找不到入口
入口文件自身不创建app,那么app就应该是其他py文件创建的,那么这里需要将app导入进来
# 这个接口是对开发人员进行使用
from apps import create_cms_app
from flask_script import Manager
app = create_cms_app('apps.configs.CMSDevConfig') # 导入并执行了配置文件配置,并接受创建的app对象
# 因为这个是对外的接口所以使用的不再是app.run()的方式
manager = Manager(app=app)
if __name__ == '__main__':
print(app.url_map)
manager.run() # 调试阶段则在朋友车门的运行中加上runserver这样方便些,不然一直是命令行
apps是自己建立的文件夹,文件夹下面有一个init的py文件,文件中定义了一个create_cms_app方法,这个方法返回的就是用Flask()创建好的对象,同时这里传入的参数实际上是一个导包路径,传给自定义的create_cms_app方法进行执行并配置相应的配置文件
# 在这里创建工厂,这里就像是控制室一样,能够对相关的大功能进行调度
from flask import Flask
# 蓝图有很多,而且可能有顺序的关系,所以这里并不能直接写在最上方导入,封装成一个函数,在需要注册蓝图的时候进行调用
def register_bp(app: Flask): # 必须传入app对象不然蓝图注册给谁
# 导入蓝图并注册
from apps.cms import cms_bp # 导入对应的蓝图
app.register_blueprint(cms_bp) # 用app把蓝图注册了
# 创建对应的函数用来调用子路由
def create_cms_app(config_obj: str): # 这里留下接口,在这个函数被调用的时候就将配置文件执行
app = Flask(__name__)
app.config.from_object(config_obj) # 注册对传入的配置文件
# 数据库的配置,数据库一定是先于应用蓝图导入的,不然视图已经开始执行数据库操作的时候发现没有表
# 将留下对应的接口进行对应的蓝图注册
register_bp(app)
return app
apps文件下应该放着对应的各种功能应用文件夹,比如建立数据库表的模型,以及一些大的功能应用,都在这个文件夹下进行分类,这里存放着templates以及放置静态文件的static文件,但是这个apps文件夹本身就会被当做一个包,包下面的初始化py文件inti中就会定义好创建对象的create_cms_app方法,而很多应用的蓝图也是在这里进行注册给app对象的,所以注册蓝图也作为单独定义的一个方法
配置文件需要放在apps文件夹下面,从传进来的参数就可以看出,之所以把配置文件单独出来,这种优势在于分离不同人员对项目的管理,运维人员如果想修改端口或者重启配置数据库引擎什么的,他们是不会在代码中去找,只会对配置文件进行更改,如果不写配置文件,那么找起来非常麻烦,而且对后期项目上线后的更新和维护会非常不利
下面是配置文件
# 这个是配置文件,配置信息最好写成类的方式,方便不同需求的人进行管理和调度
# 先确定一个所有配置的基类
class BaseConfig():
DEBUG = True # 在实际调用的时候常常是类似字典方式,这里直接写成类属性
class CMSDevConfig(BaseConfig):
pass
class APIDevConfig(BaseConfig):
pass
之所以写个基类,有些配置不管是运维还是开发都需要的,没有必要写重复的代码,直接使用一个基类进行继承就可以了,这种方式比较方便
那么真正的功能文件夹实际上也可以看做一个包,这个包就非常漂亮的也会有一个init的py文件,导入的时候就会自动执行这个文件,这种包下面就存放着各个大功能下面的小功能py文件,说白了就是在这里面写视图py文件,前面说过蓝图的使用分为几个步骤,很显然使用包的init文件就应该创建蓝图,所有的视图py文件只要导入这个包就能导入里面的蓝图,蓝图的注册交给视图py文件去执行
下面是init的写法
# 这个是后台管理的功能模块 蓝图在这里进行创建
from flask import Blueprint
cms_bp = Blueprint('cms',__name__) # 绑定的别名最好使用应用名
# 这个应用下面可能存在很多子应用或者功能,所以绑定蓝图是在编写视图的时候写的
# 下面是导入各种视图文件,相当于django中配置子路由,导入的目的是执行对应文件中的绑定
from apps.cms import the_view
试想当这个包被外面的接口文件调用的时候必须是已经绑定好了蓝图,外面的app对象才能注册蓝图,init文件只负责创建蓝图,绑定蓝图实际上交给the_view视图py文件去执行,这里直接导入这个py文件,就能将py文件内部全部执行一遍,而the_view.py文件中蓝图都写成函数绑定,这样只要这个包被调用,蓝图从创建到绑定都完成了,注册就是交给前面apps里面init文件去完成
下面是视图py文件
# 因为蓝图在__init__的py文件中已经注册了,这里需要导进来
from apps.cms import cms_bp
from flask import render_template, request, redirect
# 导入对应的蓝图之后就可以进行视图绑定
@cms_bp.route('/', endpoint='index', methods=['GET', 'POST'])
def index():
# 获取主页
html = render_template('todo_list/todo_main.html')
return html
# 登录功能
@cms_bp.route('/login/', endpoint='login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# 如果登录成功则进行跳转
username = request.form.get('username', None)
passwords = request.form.get('passwords', None)
# 判定
if data[f'{username}'] == passwords:
# 验证通过
return redirect('/')
else:
# 显示错误
return '用户或密码错误'
else:
# 显示页面
t = render_template('todo_list/log_in.html')
return t
# 注册功能
@cms_bp.route('/register/', endpoint='register', methods=['GET','POST'])
def register():
if request.method == 'POST':
global data # 声明全局变量
# 从表单中获取对应的数据
username = request.form.get('username', None)
pwd = request.form.get('pwd', None)
data[username] = pwd
# 注册完成之后跳转到主页
return redirect('/')
else:
# 显示注册页面
t = render_template('todo_list/reg.html')
return t
# 没有数据库先自己构建一个字典充当数据库
data = {
'root': 'root'
}
当然描述的很抽象,这个过程需要自己去多配置几遍,慢慢就能理解,flask本身一个py文件就可以搞定所有,只是这样做的话并不方便管理
下面是文件树,里面的other_file是我写的其他文件,这里用不着,就当它不存在