本文共 7607 字,大约阅读时间需要 25 分钟。
——————————————————————前言————————————————————————————
尽管在单一脚本文件中编写小型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 osbasedir = 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 = 1660705191@qq.com FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')#为不同的开发环境设置不同的数据库class DevelopmentConfig(Config): #开发配置类 MAIL_SERVER = 'smtp.qq.com' MAIL_PORT = 587 MAIL_USE_TLS = True MAIL_USERNAME = '1896785231@qq.com' 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 Flaskfrom config import configfrom flask_bootstrap import Bootstrapfrom flask_mail import Mailfrom flask_moment import Momentfrom flask_sqlalchemy import SQLAlchemybootstrap = 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 Messagefrom flask import current_app, render_templatefrom . import maildef 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 dbclass 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.nameclass 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 Blueprintmain = 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_templatefrom . 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 mainfrom . forms import NameFormfrom ..models import Userfrom .. import dbfrom ..email import send_mailfrom 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 FlaskFormfrom wtforms import StringField, SubmitFieldfrom wtforms.validators import DataRequiredclass NameForm(FlaskForm): #web表单定义 name = StringField('what is your name?', validators=[DataRequired()]) #文本框, 第二个参数是该字段的验证函数 submit = SubmitField('submit') #提交按钮
from app import create_app, dbfrom app.models import User, Rolefrom flask_script import Manager, Shellfrom flask_migrate import Migrate, MigrateCommandapp = 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 unittestfrom app import create_app, dbfrom flask import current_appclass 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程序的必备基础知识。
之后我们要解决的问题就是如何把这些知识融贯起来开发一个真正的程序。