尽管在单一脚本中编写小型Web 程序很方便,但这种方法并不能广泛使用。程序变复杂后,使用单个大型源码文件会导致很多问题。不同于大多数其他的Web 框架,Flask 并不强制要求大型项目使用特定的组织方式,程序结构的组织方式完全由开发者决定。在本节,我们将介绍一种使用包和模块组织大型程序的方式。

一.项目结构

  Flask 程序的基本结构如下所示:  

|-blogs
|-app/
|-templates/
|-static/
|-main/
|-__init__.py
|-errors.py
|-forms.py
|-views.py
|-__init__.py
|-email.py
|-models.py
|-migrations/
|-tests/
|-__init__.py
|-test*.py
|-venv/
|-requirements.txt
|-config.py
|-manage.py

  这种结构有4 个顶级文件夹:

  •   Flask程序一般都保存在名为app 的包中
  •   和之前一样,migrations文件夹包含数据库迁移脚本
  •   单元测试编写在tests包中
  •   和之前一样,venv 文件夹包含Python 虚拟环境

  同时还创建了一些新文件:

  •   requirements.txt列出了所有依赖包,便于在其他电脑中重新生成相同的虚拟环境
  •   config.py 存储配置
  •   manage.py用于启动程序以及其他的程序任务

  为了帮助你完全理解这个结构,下面几节讲解把前面介绍的hello.py 程序转换成这种结构的过程

二.配置选项

  程序经常需要设定多个配置。这方面最好的例子就是开发、测试和生产环境要使用不同的数据库,这样才不会彼此影响。我们不再使用hello.py 中简单的字典状结构配置,而使用层次结构的配置类。config.py 文件的内容如下示例所示:

import os
basedir = os.path.abspath(os.path.dirname(__file__)) class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.googlemail.com')
MAIL_PORT = int(os.environ.get('MAIL_PORT', ''))
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in \
['true', 'on', '']
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
FLASKY_MAIL_SENDER = 'Flasky Admin <flasky@example.com>'
FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
SSL_REDIRECT = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_RECORD_QUERIES = True
FLASKY_POSTS_PER_PAGE =
FLASKY_FOLLOWERS_PER_PAGE =
FLASKY_COMMENTS_PER_PAGE =
FLASKY_SLOW_DB_QUERY_TIME = 0.5 @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://'
WTF_CSRF_ENABLED = False class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data.sqlite') @classmethod
def init_app(cls, app):
Config.init_app(app) # email errors to the administrators
import logging
from logging.handlers import SMTPHandler
credentials = None
secure = None
if getattr(cls, 'MAIL_USERNAME', None) is not None:
credentials = (cls.MAIL_USERNAME, cls.MAIL_PASSWORD)
if getattr(cls, 'MAIL_USE_TLS', None):
secure = ()
mail_handler = SMTPHandler(
mailhost=(cls.MAIL_SERVER, cls.MAIL_PORT),
fromaddr=cls.FLASKY_MAIL_SENDER,
toaddrs=[cls.FLASKY_ADMIN],
subject=cls.FLASKY_MAIL_SUBJECT_PREFIX + ' Application Error',
credentials=credentials,
secure=secure)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler) class HerokuConfig(ProductionConfig):
SSL_REDIRECT = True if os.environ.get('DYNO') else False @classmethod
def init_app(cls, app):
ProductionConfig.init_app(app) # handle reverse proxy server headers
from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app) # log to stderr
import logging
from logging import StreamHandler
file_handler = StreamHandler()
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler) class DockerConfig(ProductionConfig):
@classmethod
def init_app(cls, app):
ProductionConfig.init_app(app) # log to stderr
import logging
from logging import StreamHandler
file_handler = StreamHandler()
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler) class UnixConfig(ProductionConfig):
@classmethod
def init_app(cls, app):
ProductionConfig.init_app(app) # log to syslog
import logging
from logging.handlers import SysLogHandler
syslog_handler = SysLogHandler()
syslog_handler.setLevel(logging.INFO)
app.logger.addHandler(syslog_handler) config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'heroku': HerokuConfig,
'docker': DockerConfig,
'unix': UnixConfig, 'default': DevelopmentConfig
}

  基类Config 中包含通用配置,子类分别定义专用的配置。如果需要,你还可添加其他配置类。为了让配置方式更灵活且更安全,某些配置可以从环境变量中导入。例如,SECRET_KEY 的值,这是个敏感信息,可以在环境中设定,但系统也提供了一个默认值,以防环境中没有定义。在3 个子类中,SQLALCHEMY_DATABASE_URI 变量都被指定了不同的值。这样程序就可在不同的配置环境中运行,每个环境都使用不同的数据库。配置类可以定义init_app() 类方法,其参数是程序实例。在这个方法中,可以执行对当前环境的配置初始化。现在,基类Config 中的init_app() 方法为空。在这个配置脚本末尾,config 字典中注册了不同的配置环境,而且还注册了一个默认配置。

三.启动脚本

  顶级文件夹下的manage.py 文件用于启动程序。脚本内容如下:

#!/usr/bin/env python
import os
from app import create_app, db
from app.models import User, Role
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand
app = create_app(os.getenv('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)
if __name__ == '__main__':
manager.run()

  这个脚本先创建程序。如果已经定义了环境变量FLASK_CONFIG,则从中读取配置名;否则使用默认配置。然后初始化Flask-Script、Flask-Migrate 和为Python shell 定义的上下文。出于便利,脚本中加入了shebang 声明,所以在基于Unix 的操作系统中可以通过./manage.py 执行脚本,而不用使用复杂的python manage.py。

四.需求文件

  程序中必须包含一个requirements.txt 文件,用于记录所有依赖包及其精确的版本号。如果要在另一台电脑上重新生成虚拟环境,这个文件的重要性就体现出来了,例如部署程序时使用的电脑。pip 可以使用如下命令自动生成这个文件:

pip freeze >requirements.txt

  安装或升级包后,最好更新这个文件。需求文件的内容示例如下:

alembic==0.9.
bleach==2.0.
blinker==1.4
click==6.7
dominate==2.3.
Flask==0.12.
Flask-Bootstrap==3.3.7.1
Flask-HTTPAuth==3.2.
Flask-Login==0.4.
Flask-Mail==0.9.
Flask-Migrate==2.0.
Flask-Moment==0.5.
Flask-PageDown==0.2.
Flask-SQLAlchemy==2.2
Flask-WTF==0.14.
html5lib==0.999999999
itsdangerous==0.24
Jinja2==2.9.
Mako==1.0.
Markdown==2.6.
MarkupSafe==1.0
python-dateutil==2.6.
python-dotenv==0.6.
python-editor==1.0.
six==1.10.
SQLAlchemy==1.1.
visitor==0.1.
webencodings==0.5.
Werkzeug==0.12.
WTForms==2.1

  如果你要创建这个虚拟环境的完全副本,可以创建一个新的虚拟环境,并在其上运行以下命令:

pip install -r requirements.txt

五.创建数据库

  不管从哪里获取数据库URL,都要在新数据库中创建数据表。如果使用Flask-Migrate 跟踪迁移,可使用如下命令创建数据表或者升级到最新修订版本

python manage.py db upgrade

Flask从入门到精通之大型程序的结构一的更多相关文章

  1. Flask从入门到精通之大型程序的结构二

    一.程序包 程序包用来保存程序的所有代码.模板和静态文件.我们可以把这个包直接称为app(应用),如果有需求,也可使用一个程序专用名字.templates 和static 文件夹是程序包的一部分,因此 ...

  2. Flask从入门到精通之flask程序入门

    初始化 所有Flask程序都必须创建一个程序实例,Web服务器使用一种名为Web服务器网关接口的的协议(WSGI),把接收自客户端的所有请求转发给这个对象处理.程序实例是Flask类的对象,使用下面代 ...

  3. [ Python ] Flask 基于 Web开发 大型程序的结构实例解析

    作为一个编程入门新手,Flask是我接触到的第一个Web框架.想要深入学习,就从<FlaskWeb开发:基于Python的Web应用开发实战>这本书入手,本书由于是翻译过来的中文版,理解起 ...

  4. Flask从入门到精通之Flask-Bootstrap的使用

    Bootstrap(http://getbootstrap.com/)是Twitter 开发的一个开源框架,它提供的用户界面组件可用于创建整洁且具有吸引力的网页,而且这些网页还能兼容所有现代Web 浏 ...

  5. Flask从入门到精通之使用Flask-Migrate实现数据库迁移

    在开发程序的过程中,你会发现有时需要修改数据库模型,而且修改之后还需要更新数据库.仅当数据库表不存在时,Flask-SQLAlchemy 才会根据模型进行创建.因此,更新表的唯一方式就是先删除旧表,不 ...

  6. Flask从入门到精通之使用Flask-SQLAlchemy管理数据库

    Flask-SQLAlchemy 是一个Flask 扩展,简化了在Flask 程序中使用SQLAlchemy 的操作.SQLAlchemy 是一个很强大的关系型数据库框架,支持多种数据库后台.SQLA ...

  7. Flask从入门到精通之Flash消息

    请求完成后,有时需要让用户知道状态发生了变化.这里可以使用确认消息.警告或者错误提醒.一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提 ...

  8. Flask从入门到精通之重定向和用户会话

    最新版的hello.py 存在一个可用性问题.用户输入名字后提交表单,然后点击浏览器的刷新按钮,会看到一个莫名其妙的警告,要求在再次提交表单之前进行确认.之所以出现这种情况,是因为刷新页面时浏览器会重 ...

  9. Flask从入门到精通之跨站请求伪造保护

    默认情况下,Flask-WTF 能保护所有表单免受跨站请求伪造(Cross-Site Request Forgery,CSRF)的攻击.恶意网站把请求发送到被攻击者已登录的其他网站时就会引发CSRF ...

随机推荐

  1. Reactor 模型(一)基本并发编程模型

    Reactor 模型(一)基本并发编程模型 Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html) 在讲解 Reactor 线程模 ...

  2. 什么是adb命令?以及如果高效使用他们?

    接下来 我会为大家讲解 最通俗易懂的adb命令 希望大家能够喜欢...

  3. 转载hibernate 的3种 状态 。。

    转自  http://huangtut.iteye.com/blog/261520  对我有帮助,所以收藏了 Hibernate三种状态的区分,以及save,update,saveOrUpdate,m ...

  4. 2018.10.20 loj#2593. 「NOIP2010」乌龟棋(多维dp)

    传送门 f[i][j][k][l]f[i][j][k][l]f[i][j][k][l]表示用iii张111,jjj张222,kkk张333,lll张444能凑出的最大贡献. 然后从f[i−1][j][ ...

  5. 2018.07.23 hdu5828 Rikka with Sequence(线段树)

    传送门 这道题维护区间加,区间开根,区间求和. 线段树常规操作. 首先回忆两道简单得多的线段树. 第一个:区间覆盖,区间加,区间求和. 第二个:区间开根,区间求和. 这两个是名副其实的常规操作. 但这 ...

  6. 2018.07.07 BZOJ2212: Poi2011Tree Rotations(线段树合并)

    2212: [Poi2011]Tree Rotations Time Limit: 20 Sec Memory Limit: 259 MB Description Byteasar the garde ...

  7. s111 stark组件

    内容回顾: 1. 类当做key 2. django中的model所在app名称.以及小写类名. def index(request): # print(m1.UserInfo,m1.UserInfo. ...

  8. 20155218 2016-2017-2 《Java程序设计》第8周学习总结

    20155218 2016-2017-2 <Java程序设计>第8周学习总结 教材学习内容总结 java.util.logging包提供了日志功能相关类与接口,不必额外配置日志组件,就可以 ...

  9. python编码(七)

    本文中,以'哈'来解释作示例解释所有的问题,“哈”的各种编码如下: 1. UNICODE (UTF8-16),C854:2. UTF-8,E59388:3. GBK,B9FE. 一.python中的s ...

  10. (线段树 && 字符串的处理)codeforces -- 570C

    链接: http://acm.hust.edu.cn/vjudge/contest/view.action?cid=87813#problem/J Description Daniel has a s ...