盡管在單一腳本中編寫小型WEB程序很方便,但是這種方法并不能廣泛使用。程序變復雜之后,使用單個大型源碼文件會導致后期的維護及擴展困難尚骄。但是不同于其它的WEB框架,F(xiàn)lask并不強制要求大型項目使用特定的組織方式侵续,程序結構的組織方式完全由開發(fā)者決定倔丈。
項目的結構
ousimd/
|-- flask/
|-- <python虛擬環(huán)境>
|-- app/ <項目的模塊名稱>
|-- static/ <靜態(tài)文件夾>
|-- templates/ <HTML模板文件夾>
|-- main/ <前端藍圖>
|-- __init__.py
|-- views.py <路由和視圖函數(shù)文件>
|-- forms.py <表單類文件, wtforms插件必須項>
|-- admin/ <后臺管理藍圖>
|-- __init__.py
|-- views.py <路由和視圖函數(shù)文件>
|-- forms.py <表單類文件, wtforms插件必須項>
|-- __init__.py
|-- another.py <可選項,根據(jù)需要增加>
|-- models.py <數(shù)據(jù)庫模型文件>
|-- migrations/ <數(shù)據(jù)庫表關系文件夾,Flask-Migrate遷移數(shù)據(jù)庫時使用>
|-- tests/
|-- __init__.py
|-- test*.py <測試文件>
|-- requeirements.txt <插件依賴包>
|-- config.py <項目的配置文件>
|-- manage.py <用于啟動程序以及其它程序任務>
配置選項
在項目中配置config.py
# -*- coding:utf-8 -*-
__author__ = u'東方鶚'
import os
import hashlib
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or hashlib.new(name='md5', string='ousikeji@#').hexdigest()
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
@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:///' + 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
}
基類Config中包含通用配置状蜗,子類分別定義專用的配置需五。如果需要,你還可以添加其它配置類轧坎。
為了讓配置方式更加靈活且安全警儒,某些配置可以從環(huán)境變量中導入。例如眶根,SECRET_KEY的值,這是個敏感信息边琉,可以在環(huán)境中設定属百,但系統(tǒng)也提供了一個默認值,以防環(huán)境中沒有定義变姨。
在3個子類中族扰,SQLALCHEMY_DATABASE_URI變量都被指定了不同的值,這樣程序就可以在不同的配置環(huán)境中運行,每個環(huán)境都使用不同的數(shù)據(jù)庫渔呵。其中SQLALCHEMY_DATABASE_URI變量對應的數(shù)據(jù)庫引擎在 ** 《第七章 使用 Flask 擴展管理數(shù)據(jù)庫》 ** 有詳細介紹怒竿。
配置類可以定義init_app()類方法,其參數(shù)是程序實例扩氢。在這個方法中耕驰,可以執(zhí)行對當前環(huán)境的配置初始化。現(xiàn)在录豺,基類Config中的init_app()方法為空朦肘。
程序包
程序包是用來保存程序的所有代碼、模板和靜態(tài)文件双饥。我們可以把這個包直接成為app(應用)媒抠,如果有需求,也可使用一個程序專用名字咏花。templates和static文件夾是程序包的一部分趴生,因此這兩個文件夾被移到了app中。數(shù)據(jù)庫模型app/models.py
和其他專用支持函數(shù)也被移到這個包中昏翰。
使用工廠函數(shù)
構造文件導入了大多數(shù)正在使用的Flask擴展苍匆。由于尚未初始化所需的程序實例,所以沒有初始化擴展矩父,創(chuàng)建擴展類時沒有向構造函數(shù)傳入?yún)?shù)锉桑。create_app()函數(shù)就是程序的工廠函數(shù),接受一個參數(shù)窍株,是程序使用的配置名民轴。配置類在config.py
文件中定義,其中保存的配置可以使用Flask的app.config配置對象提供的from_object()方法直接導入程序球订。至于配置對象后裸,測可以通過名字從config字典中選擇。程序創(chuàng)建并配置好后冒滩,接能初始化擴展了微驶。在之前創(chuàng)建的擴展對象上調用init_app()可以完成初始化過程。
程序包的構造文件app/__init__.py
# -*- coding:utf-8 -*-
__author__ = '東方鶚'
from flask import Flask
from config import config
# db = SQLAlchemy()
def create_app(config_name):
""" 使用工廠函數(shù)初始化程序實例"""
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app=app)
# db.init_app(app=app)
return app
工廠函數(shù)返回創(chuàng)建的程序示例开睡,不過要注意因苹,現(xiàn)在工廠函數(shù)創(chuàng)建的程序還不完成,在今后會陸續(xù)添加和完善篇恒。
使用藍圖
為了獲得最大的靈活性扶檐,程序包中創(chuàng)建一個子包,用于保存藍圖胁艰。
創(chuàng)建 藍圖main和藍圖admin
app/main/__init__.py
# -*- coding:utf-8 -*-
__author__ = '東方鶚'
from flask import Blueprint
main = Blueprint('main', __name__)
# 在末尾導入相關模塊款筑,是為了避免循環(huán)導入依賴智蝠,因為在下面的模塊中還要導入藍本main
from . import views, errors
app/admin/__init__.py
# -*- coding:utf-8 -*-
__author__ = '東方鶚'
from flask import Blueprint
admin = Blueprint('admin', __name__)
# 在末尾導入相關模塊,是為了避免循環(huán)導入依賴奈梳,因為在下面的模塊中還要導入藍本main
from . import views, errors
通過實例化一個Blueprint類對象可以創(chuàng)建藍圖杈湾。這個構造函數(shù)有兩個必須指定的參數(shù):藍圖的名字和藍圖所在的包或模塊。和程序一樣攘须,大多數(shù)情況下第二個參數(shù)使用Python的__name__
變量即可漆撞。
藍圖在工廠函數(shù)create_app()
中注冊
在app/__init__.py
中注冊藍圖
def create_app(config_name):
# ...
# 注冊藍本 main
from .main import main as main_blueprint
app.register_blueprint(main_blueprint, url_prefix='/main')
# 注冊藍本 admin
from .admin import admin as admin_blueprint
app.register_blueprint(admin_blueprint, url_prefix='/admin')
return app
藍圖的錯誤處理(以藍圖main為例)
app/main/errors.py
# -*- coding:utf-8 -*-
__author__ = '東方鶚'
from flask import render_template, request, jsonify
from . import main
@main.app_errorhandler(403)
def forbidden(e):
if request.accept_mimetypes.accept_json and \
not request.accept_mimetypes.accept_html:
response = jsonify({'error': 'forbidden'})
response.status_code = 403
return response
return render_template('main/errors/403.html'), 403
@main.app_errorhandler(404)
def page_not_found(e):
if request.accept_mimetypes.accept_json and \
not request.accept_mimetypes.accept_html:
response = jsonify({'error': 'not found'})
response.status_code = 404
return response
return render_template('main/errors/404.html'), 404
@main.app_errorhandler(500)
def internal_server_error(e):
if request.accept_mimetypes.accept_json and \
not request.accept_mimetypes.accept_html:
response = jsonify({'error': 'internal server error'})
response.status_code = 500
return response
return render_template('main/errors/500.html'), 500
在藍圖中編寫錯誤處理程序少有不同,如果使用errorhandler修時期阻课,那么只有藍圖中的錯誤才能觸發(fā)處理程序叫挟。要想注冊程序全局的錯誤處理程序,必須使用app_errorhandler限煞。當然抹恳,示例還提供基于json的錯誤提示編寫方法。
藍圖中定義程序路由(以藍圖main為例)
app/main/views.py
# -*- coding:utf-8 -*-
__author__ = '東方鶚'
from flask import render_template
from . import main
@main.route('/', methods=['GET', 'POST'])
def index():
return render_template('main/index.html')
templates/main/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>藍圖main歡迎您署驻!</h1>
</body>
</html>
在藍圖中編寫視圖函數(shù)主要有兩點不同:第一奋献,和前面的錯誤處理程序一樣,路由修飾器由藍圖提供旺上;第二瓶蚂,url_for()函數(shù)的用法不同。在藍圖中宣吱,F(xiàn)lask會為藍圖中的全部端點加上一個 ** 命名空間 ** 窃这,這樣就可以在不同的藍圖中使用相同的端點名定義視圖函數(shù),而不會產(chǎn)生沖突征候。命名空間就是藍圖的名字(Blueprint構造函數(shù)的第一個參數(shù))杭攻,所以視圖函數(shù)index()注冊的端點名是main.index,其URL使用url_for('main.index')獲取疤坝。
啟動腳本
manage.py
# -*- coding:utf-8 -*-
__author__ = '東方鶚'
import os
from app import create_app, db
from flask_script import Manager, Shell
# from flask_migrate import Migrate, MigrateCommand
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app=app)
# migrate = Migrate(app=app, db=db)
def make_shell_context():
return dict(app=app, db=db)
manager.add_command("shell", Shell(make_context=make_shell_context))
# manager.add_command('db', MigrateCommand)
@manager.command
def test():
""" 單元測試 """
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(test=tests)
if __name__ == '__main__':
manager.run()
需求文件
程序中必須包含一個requirements.txt文件兆解,用于記錄所有依賴包及其精確的版本號。如果要在另一臺電腦上重新生成虛擬環(huán)境跑揉,這個文件的重要性就體現(xiàn)出來了锅睛,例如部署程序時使用的電腦。pip可以使用如下命令自動生成這個文件:
(flask)$ pip freeze > requirements.txt
安裝或升級包后历谍,最好更新這個文件现拒。
如果你要創(chuàng)建這個虛擬環(huán)境的完全副本,你可以創(chuàng)建一個心的虛擬環(huán)境望侈,并在其上運行以下命令:
(flask)$ pip install -r requirements.txt
如果在今后由于升級程序包遇到問題印蔬,你可以隨時換回這個需求文件中的版本,因為這些版本和程序兼容甜无。
結果展示
啟動程序
(flask)$ python manage.py runserver --host 0.0.0.0
在瀏覽器中輸入http://localhost:5000/main
扛点,顯示如下內容。
[圖片上傳失敗...(image-8ff1f3-1509964243171)]
OK岂丘,到此一個按照大型項目結構組織的項目已經(jīng)完成了基礎的構造陵究。