尽管在单一脚本中编写小型WEB程序很方便,但是这种方法并不能广泛使用。程序变复杂之后,使用单个大型源码文件会导致后期的维护及扩展困难。但是不同于其它的WEB框架,Flask并不强制要求大型项目使用特定的组织方式,程序结构的组织方式完全由开发者决定。
项目的结构
ousimd/
|-- flask/
|-- <python虚拟环境>
|-- app/ <项目的模块名称>
|-- static/ <静态文件夹>
|-- templates/ <HTML模板文件夹>
|-- main/ <前端蓝图>
|-- __init__.py
|-- views.py <路由和视图函数文件>
|-- forms.py <表单类文件, wtforms插件必须项>
|-- admin/ <后台管理蓝图>
|-- __init__.py
|-- views.py <路由和视图函数文件>
|-- forms.py <表单类文件, wtforms插件必须项>
|-- __init__.py
|-- another.py <可选项,根据需要增加>
|-- models.py <数据库模型文件>
|-- migrations/ <数据库表关系文件夹,Flask-Migrate迁移数据库时使用>
|-- tests/
|-- __init__.py
|-- test*.py <测试文件>
|-- requeirements.txt <插件依赖包>
|-- config.py <项目的配置文件>
|-- manage.py <用于启动程序以及其它程序任务>
配置选项
在项目中配置config.py
# -*- coding:utf-8 -*-
__author__ = u'东方鹗'
import os
import hashlib
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or hashlib.new(name='md5', string='ousikeji@#').hexdigest()
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data_dev.sqlite')
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
基类Config中包含通用配置,子类分别定义专用的配置。如果需要,你还可以添加其它配置类。
为了让配置方式更加灵活且安全,某些配置可以从环境变量中导入。例如,SECRET_KEY的值,这是个敏感信息,可以在环境中设定,但系统也提供了一个默认值,以防环境中没有定义。
在3个子类中,SQLALCHEMY_DATABASE_URI变量都被指定了不同的值,这样程序就可以在不同的配置环境中运行,每个环境都使用不同的数据库。其中SQLALCHEMY_DATABASE_URI变量对应的数据库引擎在 ** 《第七章 使用 Flask 扩展管理数据库》 ** 有详细介绍。
配置类可以定义init_app()类方法,其参数是程序实例。在这个方法中,可以执行对当前环境的配置初始化。现在,基类Config中的init_app()方法为空。
程序包
程序包是用来保存程序的所有代码、模板和静态文件。我们可以把这个包直接成为app(应用),如果有需求,也可使用一个程序专用名字。templates和static文件夹是程序包的一部分,因此这两个文件夹被移到了app中。数据库模型app/models.py
和其他专用支持函数也被移到这个包中。
使用工厂函数
构造文件导入了大多数正在使用的Flask扩展。由于尚未初始化所需的程序实例,所以没有初始化扩展,创建扩展类时没有向构造函数传入参数。create_app()函数就是程序的工厂函数,接受一个参数,是程序使用的配置名。配置类在config.py
文件中定义,其中保存的配置可以使用Flask的app.config配置对象提供的from_object()方法直接导入程序。至于配置对象,测可以通过名字从config字典中选择。程序创建并配置好后,接能初始化扩展了。在之前创建的扩展对象上调用init_app()可以完成初始化过程。
程序包的构造文件app/__init__.py
# -*- coding:utf-8 -*-
__author__ = '东方鹗'
from flask import Flask
from config import config
# db = SQLAlchemy()
def create_app(config_name):
""" 使用工厂函数初始化程序实例"""
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app=app)
# db.init_app(app=app)
return app
工厂函数返回创建的程序示例,不过要注意,现在工厂函数创建的程序还不完成,在今后会陆续添加和完善。
使用蓝图
为了获得最大的灵活性,程序包中创建一个子包,用于保存蓝图。
创建 蓝图main和蓝图admin
app/main/__init__.py
# -*- coding:utf-8 -*-
__author__ = '东方鹗'
from flask import Blueprint
main = Blueprint('main', __name__)
# 在末尾导入相关模块,是为了避免循环导入依赖,因为在下面的模块中还要导入蓝本main
from . import views, errors
app/admin/__init__.py
# -*- coding:utf-8 -*-
__author__ = '东方鹗'
from flask import Blueprint
admin = Blueprint('admin', __name__)
# 在末尾导入相关模块,是为了避免循环导入依赖,因为在下面的模块中还要导入蓝本main
from . import views, errors
通过实例化一个Blueprint类对象可以创建蓝图。这个构造函数有两个必须指定的参数:蓝图的名字和蓝图所在的包或模块。和程序一样,大多数情况下第二个参数使用Python的__name__
变量即可。
蓝图在工厂函数create_app()
中注册
在app/__init__.py
中注册蓝图
def create_app(config_name):
# ...
# 注册蓝本 main
from .main import main as main_blueprint
app.register_blueprint(main_blueprint, url_prefix='/main')
# 注册蓝本 admin
from .admin import admin as admin_blueprint
app.register_blueprint(admin_blueprint, url_prefix='/admin')
return app
蓝图的错误处理(以蓝图main为例)
app/main/errors.py
# -*- coding:utf-8 -*-
__author__ = '东方鹗'
from flask import render_template, request, jsonify
from . import main
@main.app_errorhandler(403)
def forbidden(e):
if request.accept_mimetypes.accept_json and \
not request.accept_mimetypes.accept_html:
response = jsonify({'error': 'forbidden'})
response.status_code = 403
return response
return render_template('main/errors/403.html'), 403
@main.app_errorhandler(404)
def page_not_found(e):
if request.accept_mimetypes.accept_json and \
not request.accept_mimetypes.accept_html:
response = jsonify({'error': 'not found'})
response.status_code = 404
return response
return render_template('main/errors/404.html'), 404
@main.app_errorhandler(500)
def internal_server_error(e):
if request.accept_mimetypes.accept_json and \
not request.accept_mimetypes.accept_html:
response = jsonify({'error': 'internal server error'})
response.status_code = 500
return response
return render_template('main/errors/500.html'), 500
在蓝图中编写错误处理程序少有不同,如果使用errorhandler修时期,那么只有蓝图中的错误才能触发处理程序。要想注册程序全局的错误处理程序,必须使用app_errorhandler。当然,示例还提供基于json的错误提示编写方法。
蓝图中定义程序路由(以蓝图main为例)
app/main/views.py
# -*- coding:utf-8 -*-
__author__ = '东方鹗'
from flask import render_template
from . import main
@main.route('/', methods=['GET', 'POST'])
def index():
return render_template('main/index.html')
templates/main/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>蓝图main欢迎您!</h1>
</body>
</html>
在蓝图中编写视图函数主要有两点不同:第一,和前面的错误处理程序一样,路由修饰器由蓝图提供;第二,url_for()函数的用法不同。在蓝图中,Flask会为蓝图中的全部端点加上一个 ** 命名空间 ** ,这样就可以在不同的蓝图中使用相同的端点名定义视图函数,而不会产生冲突。命名空间就是蓝图的名字(Blueprint构造函数的第一个参数),所以视图函数index()注册的端点名是main.index,其URL使用url_for('main.index')获取。
启动脚本
manage.py
# -*- coding:utf-8 -*-
__author__ = '东方鹗'
import os
from app import create_app, db
from flask_script import Manager, Shell
# from flask_migrate import Migrate, MigrateCommand
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app=app)
# migrate = Migrate(app=app, db=db)
def make_shell_context():
return dict(app=app, db=db)
manager.add_command("shell", Shell(make_context=make_shell_context))
# manager.add_command('db', MigrateCommand)
@manager.command
def test():
""" 单元测试 """
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(test=tests)
if __name__ == '__main__':
manager.run()
需求文件
程序中必须包含一个requirements.txt文件,用于记录所有依赖包及其精确的版本号。如果要在另一台电脑上重新生成虚拟环境,这个文件的重要性就体现出来了,例如部署程序时使用的电脑。pip可以使用如下命令自动生成这个文件:
(flask)$ pip freeze > requirements.txt
安装或升级包后,最好更新这个文件。
如果你要创建这个虚拟环境的完全副本,你可以创建一个心的虚拟环境,并在其上运行以下命令:
(flask)$ pip install -r requirements.txt
如果在今后由于升级程序包遇到问题,你可以随时换回这个需求文件中的版本,因为这些版本和程序兼容。
结果展示
启动程序
(flask)$ python manage.py runserver --host 0.0.0.0
在浏览器中输入http://localhost:5000/main
,显示如下内容。
[图片上传失败...(image-8ff1f3-1509964243171)]
OK,到此一个按照大型项目结构组织的项目已经完成了基础的构造。