7.1 项目结构
该结构是根据<<FlaskWeb开发:基于Python的Web应用开发实战>> 第7章中推荐的项目结构搭建,是一种使用包和模块组织大型程序的方式。
这种结构有4个顶级的文件夹:
- Flask 主程序(功能业务代码)一般都保存在app包中;
- migrations 文件夹包含数据库迁移脚本;
- 单元测试编写在test包中
- venv文件夹包含Python虚拟环境
同时还创建了一些新文件:
- requirements.txt 列出了所有的依赖包,便于在其他业务环境中生成相同的虚拟环境;
- config.py 由于存储配置
- manage.py 用于启动程序及其他的程序任务(本例中加入了Shell,MigrateCommand)
7.2 配置选项
我们给程序设定了多个配置,开发,测试,生产环境。
config.py
1 import os 2 3 basedir = os.path.abspath(os.path.dirname(__file__)) 4 5 6 class Config: 7 SECRET_KEY = os.environ.get('SECRET_KEY') or 'Hard to guess string!' 8 SQLALCHEMY_COMMIT_ON_TEARDOWN = True 9 SQLALCHEMY_TRACK_MODIFICATIONS = True 10 FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]' 11 FLASKY_MAIL_SENDER = 'zhangjin@9158.com' 12 FLASK_ADMIN = os.environ.get('FLASKY_ADMIN') 13 14 @staticmethod 15 def init_app(app): 16 pass 17 18 19 class DevelopmentConfig(Config): 20 DEBUG = True 21 MAIL_SERVER = 'mail.9158.com' 22 MAIL_PORT = 587 23 MAIL_USE_TLS = True 24 MAIL_USERNAME = os.environ.get('MAIL_USERNAME') 25 MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') 26 SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 27 'data-dev.sqlite') 28 29 30 class TestingConfig(Config): 31 TESTING = True 32 SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 33 'data-test.sqlite') 34 35 36 class ProductionConfig(Config): 37 SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 'data.sqlite') 38 39 40 config = { 41 'development': DevelopmentConfig, 42 'testing': TestingConfig, 43 'production': ProductionConfig, 44 'default': DevelopmentConfig 45 }
基类Config包含通用配置,子类分别定义专用的配置。
在3个子类中,SQLALCHEMY_DATABASE_URI变量都被指定了不同的值,这样程序就可以在不同的配置中运行,每个环境都使用指定不同的数据库。
在配置最后,config字典还注册了不同的环境。
7.3 程序包
程序包用来保存所有的代码,模板和静态文件。我们可以把这个包直接成为app,如果有需求也可以使用专有的名字;templates,static,models.py,mail.py都是这个包的一部分。
7.3.1 使用程序工厂函数
app/__init__.py
1 from flask import Flask, render_template 2 from flask_bootstrap import Bootstrap 3 from flask_mail import Mail 4 from flask_moment import Moment 5 from flask_sqlalchemy import SQLAlchemy 6 from config import config 7 8 bootstrap = Bootstrap() 9 mail = Mail() 10 moment = Moment() 11 db = SQLAlchemy() 12 13 14 def create_app(config_name): 15 app = Flask(__name__) 16 app.config.from_object(config[config_name]) 17 config[config_name].init_app(app) 18 19 bootstrap.init_app(app) 20 mail.init_app(app) 21 moment.init_app(app) 22 db.init_app(app) 23 24 from .main import main as main_blueprint 25 app.register_blueprint(main_blueprint) 26 return app
构造文件导入了大多数正在使用的Flask扩展,。由于尚未初始化所需的程序实例,所以没有初始化拓展,创建扩展类时没有传入参数。create_app函数就是程序的工厂函数,接收一个参数,是程序使用的配置名。程序的配置在config.py中定义,可直接使用Flask app.config中提供的from_object()方法直接导入程序。
7.3.2 使用蓝本实现程序功能
蓝本和程序类似,也可以定义路由。不同的是,在蓝本中定义的路由处于休眠状态,直到蓝本被注册到程序上面,录有才真正成为程序的一部分。
创建蓝本:
app/main/__init__.py
from flask import Blueprint main = Blueprint('main', __name__) from . import views, errors
注册蓝本:
app/__init__.py
def create_app(config_name): ......from .main import main as main_blueprint app.register_blueprint(main_blueprint) return app
蓝本中错误处理的程序:
app/main/errors.py
from flask import render_template from . import main @main.app_errorhandler(404) def page_not_found(e): return render_template('404.html'), 404 @main.app_errorhandler(500) def internal_server_error(e): return render_template('500.html'), 500
蓝本中定义的程序路由:
app/main/views.py
1 from datetime import datetime 2 from flask import render_template, session, redirect, url_for, flash 3 4 from . import main 5 from .forms import NameForm 6 from .. import db 7 from ..models import User 8 9 10 @main.route('/', methods = ['GET', 'POST']) 11 def index(): 12 form = NameForm() 13 if form.validate_on_submit(): 14 user = User.query.filter_by(username=form.name.data).first() 15 if user is None: 16 user = User(username=form.name.data) 17 db.session.add(user) 18 session['known'] = False 19 else: 20 session['known'] = True 21 old_name = session.get('name') 22 if old_name is not None and old_name != form.name.data: 23 flash('Look like you have changed your name!') 24 session['name'] = form.name.data 25 form.name.data = '' 26 return redirect(url_for('.index')) 27 all_user = User.query.all() 28 return render_template('index.html', form=form, name=session.get('name'), known=session.get('known', False), 29 current_time=datetime.utcnow(), user_list=all_user)
在蓝本中编写视图函数主要有两点不同:第一,和前面错误处理程序一样,路由装饰器由蓝本提供;第二url_for函数的用法不同,url_for('.index')。
表单对象也移到了蓝本中
app/main/forms.py
from flask_wtf import FlaskForm from wtforms import StringField, SubmitField from wtforms.validators import DataRequired class NameForm(FlaskForm): name = StringField('What is your name?', validators=[DataRequired()]) submit = SubmitField('Submit')
7.4 启动脚本
顶级文件夹中的manage.py用于启动程序。
manage.py
1 import os 2 from app import create_app, db 3 from app.models import User, Role 4 from flask_script import Manager, Shell 5 from flask_migrate import Migrate, MigrateCommand 6 7 app = create_app(os.getenv('FLASK_CONFIG') or 'default') 8 manager = Manager(app) 9 migrate = Migrate(app, db) 10 11 12 def make_shell_context(): 13 return dict(app=app, db=db, User=User, Role=Role) 14 15 16 manager.add_command("shell", Shell(make_shell_context)) 17 manager.add_command("db", MigrateCommand) 18 19 if __name__ == '__main__': 20 manager.run()
这个脚本先创建程序,如果已经定义了环境变量FLASK_CONFIG,则从变量中读取,否则使用'default'配置。
7.5 需求文件
程序中必须包含一个requirements.txt文件,用于记录程序所依赖的包和包的版本号,pip可以使用如下命令生产这个文件:
(venv) pip freeze > requirements.txt
如果要在新机器上传创建这个一样的虚拟环境,可运行以下命令。
(venv)pip install -r requirements.txt
alembic==0.9.9 blinker==1.4 click==6.7 dominate==2.3.1 Flask==0.12.2 Flask-Bootstrap==3.3.7.1 Flask-Mail==0.9.1 Flask-Migrate==2.1.1 Flask-Moment==0.6.0 Flask-Script==2.0.6 Flask-SQLAlchemy==2.3.2 Flask-WTF==0.14.2 itsdangerous==0.24 Jinja2==2.10 Mako==1.0.7 MarkupSafe==1.0 python-dateutil==2.7.2 python-editor==1.0.3 six==1.11.0 SQLAlchemy==1.2.7 visitor==0.1.3 Werkzeug==0.14.1 WTForms==2.1