——————————————————————前言————————————————————————————
尽管在单一脚本文件中编写小型web程序很方便, 但是程序变复杂后, 使用单个大型源码文件会导致很多问题。
Flask并不要求大型项目使用特定的组织方式, 程序结构的组织方式完全由开发者决定。
本节我们介绍一种使用包和模块组织大型程序的方式。
————————————————————————————————————————————————————
|-flasky
|-app/ #web程序相关的内容在app包里,包括以下内容:
|-templates/ #响应返回的模板
|-static/ #响应返回的静态文件
|-main/ #蓝本
|-__init__.py
|-errors.py
|-forms.py
|-views.py
|-__init__.py
|-email.py #发送邮件的函数:发送邮件也是web程序的一个功能
|-models.py #数据库中的表, 也是web程序的一部分
|-migrations/ #迁移仓库和迁移脚本都在该包里
|-tests/ #测试包
|-__init__.py
|-test*.py
|-venv/ #虚拟环境
|-requirements.txt #安装扩展的名称和版本号
|-config.py #程序配置脚本
|-manage.py #程序启动脚本
后面带有'/'的都是文件夹, 在flasky文件夹中包括7部分, 下面我们来逐一介绍:
在之前的hello.py脚本中, 我们是用config字典来存储程序实例的配置, 现在我们改用配置类:
import os
basedir = os.path.abspath(os.path.dirname(__file__)) #返回本脚本所在目录
class Config(object): #配置基类: 通用配置
#设置密钥, web表单防止CSRF攻击
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
#请求后数据库的改动会自动提交
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
#邮件通用设置
FLASKY_MAIL_SUBJECT_PREFIX = '[FLASK]'
FLASKY_MAIL_SENDER = [email protected]
FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
#为不同的开发环境设置不同的数据库
class DevelopmentConfig(Config): #开发配置类
MAIL_SERVER = 'smtp.qq.com'
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USERNAME = '[email protected]'
MAIL_PASSWORD = 'zwfafgudahfalehic'
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
}
from flask import Flask
from config import config
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_moment import Moment
from flask_sqlalchemy import SQLAlchemy
bootstrap = Bootstrap() #先创建扩展的实例
mail = Mail()
moment = Moment()
db = SQLAlchemy()
def create_app(config_name):
app = Flask(__name__) #创建程序实例
app.config.from_object(config[config_name]) #本句作用是设置所有的config变量
config[config_name].init_app(app)
bootstrap.init_app(app) #初始化扩展
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
return app
from flask_mail import Message
from flask import current_app, render_template
from . import mail
def send_email(subject, to, template, **kwargs):
msg =Message(current_app.config['FLASKY_MAIL_SUBJECT_PREFIX'], sender=current_app.config['FLASKY_MAIL_SENDER']), recipients=[to] #创建邮件
msg.body = render_template(template + '.txt', **kwargs) #设置邮件
msg.html = render_template(template + '.html', **kwargs)
mail.send(msg) #发送邮件
from . import db
class Role(db.Model): #角色表
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
users = db.relationship('User', backref='role', lazy='dynamic') #1
def __repr__(self):
return '' %self.name
class User(db.Model): #用户表
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
roles_id = db.Column(db.Integer, db.ForeignKey('roles.id')) #2
def __repr__(self):
return '' %self.username
1, 2两句表示一对多关系, 上节已有介绍。
from flask import Blueprint
main = Blueprint('main', __name__) #定义蓝本, 第一个参数为蓝本名, 第二个参数代码所在模块或者包
from . import views. errors #把蓝本与路由和错误处理程序关联起来
关联起来以后, 再把蓝本注册到程序实例上, 路由就注册到了程序实例上。
和在单一脚本文件中编写小型web程序不同, 我们只有在调用create_app后程序实例app才存在, 此时再用app定义路由显然来不及, 这是我们可以用蓝本定义路由, 路由处于休眠状态, 然后再在create_app函数中把蓝本注册到程序实例app上, 路由就注册到了程序实例上:
def create_app(config_name):
#...
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
from flask import render_template
from . import main
@main.app_errorhandler(404) #404错误处理程序
def page_not_found(e):
return render_template('404.html'), 404
@main.app_errorhandler(500) #500错误处理程序
def internal_server_error(e):
return render_template('500.html'), 500
from . import main
from . forms import NameForm
from ..models import User
from .. import db
from ..email import send_mail
from flask import session, render_template, url_for, redirect, current_app
@main.route('/', methods=['GET', 'POST']) #视图函数
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
if not user:
user = User(form.name.data)
db.session.add(user)
session['known'] = False
if current_app.config['FLASKY_ADMIN']:
send_mail(current_app.config['FLASKY_ADMIN'], 'New User', 'mail/new_user', user=user)
else:
session['known'] = True
session['name'] = form.name.data
form.name.data = ''
return redirect(url_for('main.index'))
return render_template('index.html', name=session.get('name'), known=session.get('known'), form=form)
蓝本定义路由时有两点不同, 一是@main.route('/', methods=['GET', 'POST']), 二是url_for函数的参数, 原来是'index', 现在要加上命名空间——蓝本名(ps:定义蓝本的第一个参数)
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class NameForm(FlaskForm): #web表单定义
name = StringField('what is your name?', validators=[DataRequired()]) #文本框, 第二个参数是该字段的验证函数
submit = SubmitField('submit') #提交按钮
from app import create_app, db
from app.models import User, Role
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand
app = create_app( os.environ.get('FLASK_CONFIG') or 'default' ) #创建程序实例
#初始化扩展
manager = Manager(app)
migrate = Migrate(app, db)
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
#添加脚本命令
manager.add_command('shell', Shell(make_context=make_shell_context)) #自动导入对象
manager.add_command('db', MigrateCommand) #数据库迁移
#添加脚本命令
@manager.command #测试用例
def test():
"""Run the unit test."""
import unittest
tests = unittest.TestLoader.discover('tests')
unittest.TestRunner(verbosity=2).run(tests)
if __name__=='__main__':
manager.run()
&pip freeze >requirements.txt
&pip install -r requirements.txt
由此创建之前虚拟环境的完全副本。
import unittest
from app import create_app, db
from flask import current_app
class BasicsTestCase(unittest.TestCase):
def setUp(self): #测试函数执行前执行:创建程序实例, 推送上下文, 创建数据库
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
def tearDown(self): #测试函数执行完毕后执行, 删除数据库, 退出上下文
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_app_exists(self): #以test开头的都是测试函数
self.assertFalse(current_app is None)
def test_app_is_testing(self):
self.assertTrue(TESTING)
可以为空, 存在是test才是包, 不存在test只是一个普通的文件夹。
& python manage.py test
——————————————————————————结束语———————————————————————
到现在我们已经学到了使用Flask开发Web程序的必备基础知识。
之后我们要解决的问题就是如何把这些知识融贯起来开发一个真正的程序。