正文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
文章編寫不易,實屬感謝!