Python Flask 概念與實作(三) - 專案檔案分佈
前言
專案檔案的部署這一節,原本想說不就是檔案按種類區分就好?殊不知這其中細節水很深,搞得死去活來。最後也做了好很多功課,甚至有想把現有專案砍掉重來的念頭。但後來發現若只是單純重新建新專案只是照本宣科,何不試著修改當前的專案🙄?
關於這節,若你的網站只是單純blog、少許頁面或API等少量功能可以走馬看花略過,
但如果是有意擴大網站的話,要特別注意檔案分佈的問題👻!
目前 Python Flask 概念與實作 大致規劃為
在開發過程中,Test(測試)其實也很重要,但礙於缺乏經驗,若之後有望的話再補上(專案打包、網站優化等等亦同)
1 | . |
小型的專案大致上會是這個架構,這樣的架構優點是小而精、簡單且一目瞭然,需要增加網頁時僅需在app/routes.py設定URL,同時在app/templates中新增對應的html即可。而隨著網站的成長,會面臨的幾種問題是:
app/routes.py內的URL太多,有時甚至不小心設定兩個相同的URL,導致較晚設定的URL失效- 同樣也是
app/routes.py管理上的問題,設定URL前綴時必須逐一URL更改(e.g. /products) app/templates中的html過多,不易管理- 測試功能時,需更改
config.py設定
Application Factory
為了解決上述的問題,Flask提供Application Factory(工廠模式),將app/__init__.py調整為一個由 create_app() 建立Flask app的方法,而不是直接啟用套件。在run.py依需求輸入參數,即可套用對應的config.py設定
- 修改
config.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44# config.py
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__)) # 當前檔案絕對路徑, __file__: 當前檔案(config.py)
load_dotenv() # 引用.env檔
class Config():
# Database Configuration
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = os.getenv("SECRET_KEY") or 'A_VERY_LONG_SECRET_KEY' # 給form使用
# user add image of porduct upload_folder
PRODUCT_IMG_UPLOAD_FOLDER = "app/static/product/items"
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.getenv("DEV_DATABASE_URL") or \
'sqlite:///' + os.path.join(basedir, 'instance', 'devp-data.sqlite')
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'instance', 'test-data.sqlite')
class ProductionConfig(Config):
DEBUG = False
FLASK_RUN_HOST = "0.0.0.0"
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'mysql+pymysql://{}:{}@{}/{}'.format(
os.getenv("MYSQL_USERNAME"),
os.getenv("MYSQL_PASSWORD"),
os.getenv("MYSQL_IP"),
os.getenv("MYSQL_DATABASE")
)
config = {
'devp': DevelopmentConfig, # 開發設定/DB
'test': TestingConfig, # 測試設定/DB
'prod': ProductionConfig, # 正式設定/DB
'default': DevelopmentConfig # 預設(開發)
} - 修改
/app/models.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# app.models.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin
from flask_bcrypt import Bcrypt
# 將套件移至此
db = SQLAlchemy()
login = LoginManager()
bcrypt = Bcrypt()
def load_user(user_id):
return User.query.filter_by(id=user_id).first()
class User(db.Model, UserMixin):
# 略... - 修改
/app/__init__.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35# app.__init__.py
from flask import Flask, render_template
from flask_bootstrap import Bootstrap4
from config import config
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_bcrypt import Bcrypt
bootstrap = Bootstrap4()
def create_app(configname=None):
app = Flask(__name__)
if configname == None:
configname = 'default'
app.config.from_object(config[configname])
# 初始化套件
bootstrap.init_app(app)
db.init_app(app)
login.init_app(app)
bcrypt.init_app(app)
# login_required 設定
login.login_view = "signin"
login.login_message = "You must signin to access this page."
login.login_message_category = "info"
### routes
# from app.routes import * # 無法一次性引入
def index():
return render_template("index.html")
return app - 修改
run.py1
2
3
4
5
6# run.py
from app import create_app
if __name__ == "__main__":
app = create_app() # 預設為devp, 可依需求輸入
app.run()
如此一來,使用Application Factory即可快速切換設定檔與DB,但這僅只是解決小型專案的第四個問題而已。
Blueprint
Blueprint(藍圖)可以將專案中的功能區塊化,雖說看起來很抽象,但其實在你的專案還沒成長起來前,也不太知道如何區分功能。但好在Blueprint可以輕易地將URL前綴管理的問題輕易解決。
假設網站中除了首頁以外,也有其他功能。e.g. register/login(或是有order(訂單)、cart(購物車)也行)
1 | . |
- 在專案下新增
/users(與/app平行) - 在
/users下新增__init__.py1
2
3
4
5
6
7# users.__init__.py
from flask import Blueprint
users_bp = Blueprint('users', __name__, template_folder='templates')
# 'users': blueprint name
# template_folder: 此blueprint HTML模板引用位置(可選參數)
from . import routes - 在
/users下新增routes.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# users.routes.py
from . import users_bp
from flask import redirect, flash, render_template, url_for
from app.forms import *
### 註冊 ###
def register():
form = RegisterForm()
if form.validate_on_submit():
# 略...
return redirect(url_for('users.signin')) # 跳轉URL時需加上blueprint name前綴
return render_template("register.html", form=form)
### 登入 ###
def signin():
form = SigninForm()
if form.validate_on_submit():
# 略...
return render_template("sign-in.html", form=form)
### 登出 ###
def logout():
logout_user()
return redirect(url_for("users.signin")) - 在
/users下新增templates資料夾,並將原/app/templates中的register.html與signin.html移至此 - 修改
register.html與signin.html中的url_for(),有使用到users需加前綴(e.g.url_for('signin')改為url_for('users.signin')) - 修改
/app/__init__.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30# app.__init__.py
from flask import Flask
from flask_bootstrap import Bootstrap4
from config import config
from app.models import db, login, bcrypt # 改在app.models中實體化
from .users import users_bp # 引入users blueprint
bootstrap = Bootstrap4()
def create_app(configname='test'):
app = Flask(__name__)
if configname == None:
configname = 'default'
app.config.from_object(config[configname])
bootstrap.init_app(app)
db.init_app(app)
login.init_app(app)
bcrypt.init_app(app)
# login_required 設定
login.login_view = "signin"
login.login_message = "You must login to access this page."
login.login_message_category = "info"
### routes, blueprint
app.register_blueprint(users_bp, url_prefix='/users') # url_prefix: URL前綴
return app - 修改
/app/models.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# /app/models.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin
from flask_bcrypt import Bcrypt
from datetime import datetime
db = SQLAlchemy()
login = LoginManager()
bcrypt = Bcrypt()
def load_user(user_id):
return Users.query.filter_by(id=user_id).first()
class Cart(db.Model):
# 略...
經過上述修改後,當前專案架構:
1 | . |
藉由使用blueprint即可解決小型專案遇到的1~3問題🥳
Application Factory vs. Blueprint
起初以為Factory與Blueprint都是管理大型專案的一種方法,但後來實作後赫然驚覺各自的功能根本不一樣。Factory是讓開發人員可以快速切換database與config.py設定,而Blueprint是將大型專案功能逐一模組化。
結論
- 使用Application Factory(工廠模式)快速切換DB與設定
- 使用Blueprint(藍圖)管理大型專案