正文html
程序包结构python
——————————————————————————————————
flask文件夹结构sql
其中:
app为程序包,Flask程序保存在这个包中
migrations文件夹包含数据库迁移脚本
tests包保存单元测试
requirements文件夹中记录程序的依赖
config.py是程序的配置文件
manage.py是程序的运行文件,用于启动程序即程序的其余任务shell
——————————————————————————————————数据库
app包结构 flask
其中:
auth为保存专门用于认证的auth蓝本
main为保存main蓝本的包
static文件夹用于保存静态文件,例如HTML代码中引用的图片、 JavaScript 源码文件和 CSS
templates用于保存网页的模板bootstrap
———————————————————————————————————————api
蓝本文件夹结构(以auth文件夹为例,main蓝本等再也不赘述)安全
蓝本的细节就不在此赘述,在flask框架中用到蓝本,能够对不一样的程序功能使用不一样的蓝本,这是保证程序整齐有序的办法。(想一想把全部功能都写在一块儿会多么混乱)。这里说明一下到蓝本的程序运行原理:app/auth/views.py 模块引入蓝本,而后使用蓝本的 route 修饰器定义与认证相关的路由,而后再渲染views中设定的网页模板。看起来是否是若是程序能运行到蓝本这一步,咱们就能够对网页进行操做了。
2. 运行说明
在运行程序的时候,咱们在虚拟环境下,经过以下命令来完成。因而可知,程序的运行是由manage.py来开始的session
(venv) $ python manage.py runserver
那么,咱们来看看这个manage.py吧,期待不期待?兴奋不兴奋?
#!/usr/bin/env python import os COV = None if os.environ.get('FLASK_COVERAGE'): import coverage COV = coverage.coverage(branch=True, include='app/*') COV.start() if os.path.exists('.env'): print('Importing environment from .env...') for line in open('.env'): var = line.strip().split('=') if len(var) == 2: os.environ[var[0]] = var[1] from app import create_app, db from app.models import User, Follow, Role, Permission, Post, Comment from flask_script import Manager, Shell from flask_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, Follow=Follow, Role=Role, Permission=Permission, Post=Post, Comment=Comment) manager.add_command("shell", Shell(make_context=make_shell_context)) manager.add_command('db', MigrateCommand) @manager.command def test(coverage=False): """Run the unit tests.""" if coverage and not os.environ.get('FLASK_COVERAGE'): import sys os.environ['FLASK_COVERAGE'] = '1' os.execvp(sys.executable, [sys.executable] + sys.argv) import unittest tests = unittest.TestLoader().discover('tests') unittest.TextTestRunner(verbosity=2).run(tests) if COV: COV.stop() COV.save() print('Coverage Summary:') COV.report() basedir = os.path.abspath(os.path.dirname(__file__)) covdir = os.path.join(basedir, 'tmp/coverage') COV.html_report(directory=covdir) print('HTML version: file://%s/index.html' % covdir) COV.erase() @manager.command def profile(length=25, profile_dir=None): """Start the application under the code profiler.""" from werkzeug.contrib.profiler import ProfilerMiddleware app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[length], profile_dir=profile_dir) app.run() @manager.command def deploy(): """Run deployment tasks.""" from flask_migrate import upgrade from app.models import Role, User # migrate database to latest revision upgrade() # create user roles Role.insert_roles() # create self-follows for all users User.add_self_follows() if __name__ == '__main__': manager.run()
下面咱们按照顺序去理一下,看看manage.py究竟是如何运行的。
import os COV = None if os.environ.get('FLASK_COVERAGE'): import coverage COV = coverage.coverage(branch=True, include='app/*') COV.start()
这段代码是关于程序测试覆盖度方面的,咱们能够先行略去,测试自己并不影响程序的运行。
if os.path.exists('.env'): print('Importing environment from .env...') for line in open('.env'): var = line.strip().split('=') if len(var) == 2: os.environ[var[0]] = var[1]
这段代码的做用是从.env文件中导入环境变量。具体的你们能够去搜索一下这个文件,在程序配置中,有些信息是在环境变量中设置的。可是此处咱们也略过细节。
from app import create_app, db from app.models import User, Follow, Role, Permission, Post, Comment from flask_script import Manager, Shell from flask_migrate import Migrate, MigrateCommand
各类导入,从app包中导入create_app工厂函数和数据库db。从app.models模块中导入User, Follow, Role, Permission, Post, Comment等类。在flask_script扩展中导入Manager, Shell, 从flask_migrate扩展中导入Migrate, MigrateCommand。什么意思呢?本身去搜索一下。
此处有一个坑:在原文中flask的扩展采用from flask.ext.script import Manager, Shell的导入方式,可是实际用的时候却会报错,从报错的信息中能够知道flask.ext.script已经弃用了,改成flask_script便可。其余的扩展也是同样。
>>> from flask.ext.script import Manager __main__:1: ExtDeprecationWarning: Importing flask.ext.script is deprecated, use flask_script instead.
这一句是建立工厂函数的实例,create_app是由from app import create_app, db导入的。
这里有一个坑须要注意一下,from app import create_app, db中的app是咱们建立的一个包,其放置于顶层文件夹中,其中包括一个__init__.py文件。而语句app = create_app(os.getenv (‘FLASK_CONFIG’) or ‘default’)的app则是给create_app函数建立的实例的一个命名,这两个的含义是不同的。对于我这种小白,刚刚开始也是迷惑了一阵子。
如今咱们来看一下具体实现的方法。
os.getenv(‘FLASK_CONFIG’) or ‘default’),这个’FLASK_CONFIG’咱们是咱们设置的环境变量,经过os.getenv咱们能够得到这个环境参数。由于这是对工厂函数create_app进行实例化,所以咱们获得的环境参数就做为create_app的参数,若是没有设置’FLASK_CONFIG’这个环境变量,那么就将’default’这个默认值赋给工厂函数。那么这个设置的’FLASK_CONFIG’环境变量或这个’default’究竟是什么呢?这须要到工厂函数中看一下。
既然create_app是从app包中导入的,那么让咱们一块儿来看看这个包里面都有什么吧!打开app下面的__init__.py文件,看到没有create_app藏在这里呢。
app / __ init __.py
from flask import Flask from flask_bootstrap import Bootstrap from flask_mail import Mail from flask_moment import Moment from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_pagedown import PageDown from config import config #依旧是各类导入,这里应该不难理解,在工厂函数中导入了flask及其扩展,也经过from config import config导入了程序配置文件config.py中的config属性 bootstrap = Bootstrap() mail = Mail() moment = Moment() db = SQLAlchemy() pagedown = PageDown() login_manager = LoginManager() login_manager.session_protection = 'strong' login_manager.login_view = 'auth.login' #这里是建立导入的Flask扩展的实例。 def create_app(config_name): app = Flask(__name__) #建立Flask的实例 app.config.from_object(config[config_name]) config[config_name].init_app(app) bootstrap.init_app(app) mail.init_app(app) moment.init_app(app) db.init_app(app) login_manager.init_app(app) pagedown.init_app(app) #建立flask扩展的实例 if not app.debug and not app.testing and not app.config['SSL_DISABLE']: from flask_sslify import SSLify sslify = SSLify(app) from .main import main as main_blueprint app.register_blueprint(main_blueprint) from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') from .api_1_0 import api as api_1_0_blueprint app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0') #注册蓝图到工厂函数中 return app
咱们来分析
app = create_app(os.getenv ('FLASK_CONFIG') or 'default')
在create_app中有以下的语句:
def create_app(config_name): app = Flask(__name__) app.config.from_object(config[config_name])
这里的os.getenv (‘FLASK_CONFIG’) or 'default’做为参数被传递给app.config.from_object(config[config_name]),其中的app是Flask的一个实例。
又一个坑:app.config中的config是Flask类的一个属性,与咱们的配置文件config.py不是同一个东西,也不是config.py中的config属性。而config[config_name]得config是配置文件config.py中导入的,咱们在config.py文件中看一下:
config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig, 'heroku': HerokuConfig, 'unix': UnixConfig, 'default': DevelopmentConfig }
能够发现,FLASK_CONFIG应该被设置成字典的键,根据你须要运行的模式选取’development’, ‘testing’, ‘production’, ‘heroku’, ‘unix’, ‘default’中的一个。固然,若是不在环境变量中设置的话,就默认选取的’default’。那么from_object返回的是该健对应值。例如设置’default’返回的就是’DevelopmentConfig’。
到了这里咱们知道app.config.from_object(config[config_name])实际上就是app.config.DevelopmentConfig,这个config.DevelopmentConfig就是config.py中的DevelopmentConfig类。那么看看这个DevelopmentConfig:
class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \ 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
这个DevelopmentConfig的父类是Config类,继承Config中的全部属性和方法。这样app.config.DevelopmentConfig就将DevelopmentConfig设定的配置绑定到了app——即Flask的实例上去。
固然,咱们这里是以DevelopmentConfig为例,其余的配置是同样的原理。
接着执行create_app中的语句是:
config[config_name].init_app(app)
这里的init_app是在DevelopmentConfig类或其余运行环境配置类中的一个初始化方法,但值得注意的是,DevelopmentConfig类中并无直接写入这个方法,而是继承Config类。
在config类中有一个静态方法,不执行任何操做。
@staticmethod def init_app(app): pass
所以在DevelopmentConfig中,这个初始化实际上并无进行,直接pass。这是由于这里根本不须要初始化,以前执行的配置就彻底够了。
可是值得注意的是,在ProductionConfig中是有重写这个方法,所以会根据重写的方法对app进行初始化配置。
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
login_manager.init_app(app)
pagedown.init_app(app)
这时咱们已经完成了实例化Flask,并对实例app进行了初始化配置。对于咱们用到的flask扩展也进行一样的初始化配置。
坑:此处的init_app方法是各扩展中自带的方法,并非刚才咱们用到的在config.py中本身写的init_app方法,不过功能差不太多。但有时候让人挺迷惑的。
if not app.debug and not app.testing and not app.config['SSL_DISABLE']: from flask_sslify import SSLify sslify = SSLify(app)
这里是启用安全HTTP的flask扩展flask_sslify,先略去。
from .main import main as main_blueprint app.register_blueprint(main_blueprint) from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') from .api_1_0 import api as api_1_0_blueprint app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')
刚才说了那么多,无非是完成了实例化和配置初始化,可是咱们的对网页处理的视图函数以及网页自己的内容在哪里处理呢?
视图函数咱们是交给蓝原本处理的,此处是将蓝本注册到工厂函数中去。
到此有没有发现,在manage.py中大部分事情都让工厂函数干去了,首先是建立实例,而后初始化配置,最后把工做交给蓝本去干。
根据实现的功能不一样分红不一样的蓝本。好比在这个程序中,认证就放在auth这个蓝本中。蓝本去分配其中的路由。经过蓝本中的视图文件去操做form和template,对数据库的操做也是在视图文件中实现。原理就是这个原理,细节就很少说了。
接下来的代码:
manager = Manager(app)
migrate = Migrate(app, db)
这是方便程序在脚本下操做的flask_script中的Manage类的实例和数据库迁移的实例。
可能你们还有一个疑问:数据库是何时建立的,这其实在对工厂函数初始化配置的时候,经过调用config.py中的配置已经完成了。
总结
经过运行manage.py的过程分析,发现整体框架特色以下:
manage.py——公司的总经理
工厂函数——项目经理
config.py——公司财务总监
蓝本——各技术部门经理
视图文件——广大苦逼工程师
template/form——生产工具(枪/炮)
总经理:干
项目经理:好, 财务总监,这粮和钱…, 部门经理们,我给大家说个事呗
财务总监:有的,你有我有全都有。都在这里,别客气
技术部门经理:好的。那啥,小明,小红,抄家伙
工程师:…(端着枪扛着炮就冲出去了)
本文转自:http://www.javashuo.com/article/p-rillchhz-x.html
文章编写不易,实属感谢!