Flask[4]
Review
一樣是快一個月前的東西
Session
- flask內建的 = 簽過名的cookie
- 具體操作方式像dict
- 相較純cookie安全,通常拿來存一些個人資料、身分驗證等不能給使用者改的東東
#加密用的secret key
app.config['SECRET_KEY'] = 'c4dffa417abe4d31936cdf52d3a6d7ae'
# 存數據
session['key'] = 'value'
# 取數據
val = session.get('key').env
- 存一些不能公開的東東
- 用變數存
secret_code = 6767
password = "password"from flask import Flask
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
@app.route("/")
def main():
pw = os.getenv("password")
return f'password is: {pw}'
if __name__ == '__main__':
app.run(debug=True,port=5000)
對不起例子偷之前的
Database
Database的簡稱是DB

還記得嗎
from flask import Flask
from flask import redirect
from flask import request, render_template, url_for, session
app = Flask(__name__)
secret_key = 'c4dffa417abe4d31936cdf52d3a6d7ae'
app.config['SECRET_KEY'] = secret_key #用來加密的
@app.route("/")
def main():
return render_template("index.html")
@app.route("/post_example", methods=['GET', 'POST']) #form把資訊傳到 "/post_example", 我們和flask說這是post method
def post_example():
if request.method == 'POST':
#取得我們form的資料
#把我們input的資料存到session
session['name'] = request.form['username']
session['password'] = request.form['password']
#判斷我們的輸入是否正確
if session['name'] == 'admin' and session['password'] == 'admin':
return redirect(url_for('success'))
else:
return redirect(url_for('fail'))
else:
return redirect("http://127.0.0.1:5000/") #如果沒收到,就回到主畫面
@app.route("/success")
def success():
#這裡用session,表示可以在路由之間傳送資訊
return f"success! your name is {session['name']}, and your password is {session['password']}"
@app.route("/fail")
def fail():
return f"login failed, please try again"
if __name__ == '__main__':
app.run(debug=True,port=5000)這邊是寫死的,如果有DB就可以動態查詢
正式簡介
- 資料庫
- 顧名思義,用於儲存資料
- 基本操作:新增、查詢、刪除、修改...
- 分為SQL型和NoSQL型


SQL v.s. NoSQL
| SQL | NoSQL | |
|---|---|---|
| 中譯名稱 | 關聯式資料庫 | 非關聯式資料庫 |
| 格式 | 資料表格,欄&列 | 很多種 e.g. JSON、圖形 |
| 查詢方式 | 標準SQL語法 | 依使用的種類而定 |
| 資料關聯型 | 強 | 弱 |
| 彈性 | 低(定了就定了) | 高 |
| 例子 | MySQL、PostgreSQL、SQLite | MongoDB、Redis |
Flask-SQLAlchemy
&
SQL資料庫(SQLite)操作
先補一下SQLAlchemy
- Python ORM框架
- ORM
- 以物件導向方式操作資料庫
- 將資料庫的表格與欄位自動對應(映射)為程式碼中的物件與屬性
- 優:省去學不同資料庫之間的 SQL 語法、防SQL injection
- 缺:效能變差

Flask-SQLAchemy
- only for flask
- 寫法跟SQLAlchemy不盡相同
- 簡化配置、自動化管理資料庫連線
pip install Flask-SQLAlchemyfrom flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///site.db"
db = SQLAlchemy(app)Setup
設定路徑(注意是URI)
初始化物件
資料模型
- 定義資料表的結構
- 實踐方式:寫一個class繼承db.Model
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String
class User(db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(20), unique=True, nullable=False)
email: Mapped[str]
password: Mapped[str] = mapped_column(String(15), nullable=False)New(3.1.x之後)
使用與sqlalchemy相近的db.mapped_column()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String)
password = db.Column(db.String(15), nullable=False)Old
使用db.column(),裡面存資料型別、條件等等
EZ DB Setup

Step1. 開新terminal
Step2. 打python
EZ DB Setup

Step3. 打這些(如果檔名跟變數名稱不一樣記得改)

Step4. 如果你前面都沒報錯,然後有一個叫instance的資料夾裡有一個叫site.db的檔案,那恭喜你成功創好DB了:)
EZ DB Setup
可以看到表也建起來了

EZ DB Setup
對路徑有更詳細的管理(with os)
基本操作
新增資料
from app import User
with app.app_context():
user = User(username="Roger", email="rogeris2486@gmail.com", password="2486")
db.session.add(user)
db.session.commit()創一個user object
然後用db.session.add() & db.session.commit()
查詢資料
with app.app_context():
User.query.all() #old
with app.app_context():
db.session.scalars(db.select(User)).all() #new用query.all()可以看所有的(會是一個list)
可以加上filter_by()來定查詢條件
*.first() 會回傳找到的第一個
with app.app_context():
user = User.query.filter_by(username='Roger').first()
print(user.email) #old
with app.app_context():
user = db.session.execute(db.select(User).filter_by(username="Roger")).scalar_one()
print(user.email) #new更新資料
直接改query到的然後commit就行
with app.app_context():
user_to_change = User.query.filter_by(username="Roger").first()
user_to_change.password = "6767"
db.session.commit() #update的部分一樣的刪除資料
same
with app.app_context():
user_to_delete = User.query.filter_by(username="Roger").first()
db.session.delete(user_to_delete)
db.session.commit()with app.app_context():
db.drop_all()drop_all()可以刪掉所有table
補充:__repr__
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String)
password = db.Column(db.String(15), nullable=False)
def __repr__(self):
return f'User(username="{self.username}", email="{self.email}", password="{self.password}")'- 「定義物件的官方字串表示形式」
- debug用,能比較清楚知道報錯內容


before
after
資料表關聯
資料表關聯
- 關聯式資料庫需使用
- 實作方式:用主鍵(Primary Key)、外鍵(Foreign Key)(指向另一個表的主鍵)連結
- 種類
- 一對一
- 一對多
- 多對多

一對多關聯
| id | type |
|---|---|
| 1 | pizza |
| 2 | burger |
| 3 | fries |
| id | name | order |
|---|---|---|
| 1 | Jason | pizza |
| 2 | Amber | burger |
| 3 | Hehe | burger |
| 4 | Michael | fries |
burger可以對應到很多個name要的 -> 一對多
Customers
Food
實作
class Food(db.Model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(20), unique=True, nullable=False)
ordered_by = db.relationship('Customers', backref='order', lazy="dynamic")
class Customers(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True, nullable=False)
food_id = db.Column(db.Integer, db.ForeignKey('food.id'), nullable=False)建立與Customers的關係
backref:直接給Customers ''order''這個屬性
lazy="dynamic":print 出的會是一個list
Foreign Key
指向Food的id
上操作
with app.app_context():
food1 = Food(type="pizza")
food2 = Food(type="burger")
food3 = Food(type="fries")
cus1 = Customers(name="Jason", order=food1)
cus2 = Customers(name="Amber", order=food2)
cus3 = Customers(name="Hehe", order=food2)
cus4 = Customers(name="Michael", order=food3)
db.session.add(food1)
db.session.add(food2)
db.session.add(food3)
db.session.add(cus1)
db.session.add(cus2)
db.session.add(cus3)
db.session.add(cus4)
db.session.commit()


上操作
with app.app_context():
food = Food.query.filter_by(type="burger").first()
order_by = food.ordered_by.all()
for o in order_by:
print(o.name)

with app.app_context():
cus = Customers.query.filter_by(name="Hehe").first()
print(cus.order.type)正向查詢(從Food找ordered_by,看有哪些Customers)
反向查詢(從Customers.orders回去翻type,也就是food裡面的東東)
一對一關聯實作
你問我為什麼直接跳實作
因為只要在前面例子的db.relationship()裡面再加上uselist=False就可以了
多對多關聯
| id | type |
|---|---|
| 1 | pizza |
| 2 | burger |
| 3 | fries |
| id | name | order |
|---|---|---|
| 1 | Jason | pizza, fries |
| 2 | Amber | burger |
| 3 | Hehe | pizza, burger |
| 4 | Michael | fries |
Customers
Food
會需要透過「中繼表」來實作
?

實作
customer_foods = db.Table('customer_foods',
db.Column('customer_id', db.Integer, db.ForeignKey('customers.id'), primary_key=True),
db.Column('food_id', db.Integer, db.ForeignKey('food.id'), primary_key=True)
)
class Food(db.Model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(20), unique=True, nullable=False)
ordered_by = db.relationship('Customers',
secondary=customer_foods,
backref=db.backref('orders', lazy='dynamic'),
lazy='dynamic')
class Customers(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True, nullable=False)secondary指向customer_foods這個表
customer_foods:中繼表
移掉Foreign key,全都由中繼表處理
上操作
with app.app_context():
pizza = Food(type="pizza")
burger = Food(type="burger")
fries = Food(type="fries")
jason = Customers(name="Jason")
amber = Customers(name="Amber")
hehe = Customers(name="Hehe")
michael = Customers(name="Michael")
jason.orders.append(pizza)
jason.orders.append(fries)
amber.orders.append(burger)
hehe.orders.append(pizza)
hehe.orders.append(burger)
michael.orders.append(fries)
db.session.add_all([pizza, burger, fries])
db.session.add_all([jason, amber, hehe, michael])
db.session.commit()


上操作
with app.app_context():
p = Food.query.filter_by(type="pizza").first()
for cus in p.ordered_by.all():
print(cus.name)
with app.app_context():
cus = Customers.query.filter_by(name="Amber").first()
for p in cus.orders.all():
print(p.type)
正向查詢
反向查詢
Blueprints
前面的區域以後再來探索吧

好啦下周會講 講師在這邊道歉
Blueprints簡介
試想一下
我們前面實作會用的flask應用都只會有兩、三個route而已
但當今天應用規模變大變複雜 route變成一坨 你還會想把所有東西都放在app.py嗎
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Hello World"
@app.route("/one")
def one():
return "one"
@app.route("/two")
def two():
return "two"
@app.route("/three")
def three():
return "three"
#...
@app.route("/sixty_six")
def sixty_six():
return "sixty six"
@app.route("/sixty_seven")
def sixty_seven():
return "sixty seven"
if __name__ == "__main__":
app.run(debug=True,port=8080)app.py
Blueprints簡介
所以你打算把一些route分到別的檔案再import進來
from flask import Flask
import one
app = Flask(__name__)
@app.route("/")
def index():
return "Hello World"
if __name__ == "__main__":
app.run(debug=True,port=8080)from app import app
@app.route("/one")
def one():
return "one"
@app.route("/two")
def two():
return "two"
@app.route("/three")
def three():
return "three"
#...
@app.route("/sixty_six")
def sixty_six():
return "sixty six"
@app.route("/sixty_seven")
def sixty_seven():
return "sixty seven"app.py
one.py
Blueprints簡介

然後他就死掉了

Blueprints簡介
實際上這東西叫circular import,是python直譯的機制間接造成的問題
from flask import Flask
import onefrom app import app兩個檔案會反覆橫跳
app.py
one.py
Blueprints簡介
有這些方法可以處理:
- 用函式,可以把import語句包在裡面or以參數的方式傳入
- 再創一個module放共同要用的東東
- 整個module import進來,不要寫from ......
Blueprints簡介
講回來,官方推薦用blueprints解決專案規模越來越大的問題

?
Blueprints簡介

- 是一種Flask推薦,用於構建、擴展應用的方法
- 優點
- 有利於大型專案和團隊合作
- 有效的組織,增強代碼可讀性和維護性
- 可以用這個改URL前綴,以防route之間衝突
- 可共享靜態文件、模板等等
- 缺點
- 套用在應用上然後應用創建的時候,要改只能全都砍掉
from flask import Blueprint一個簡單的實例
from flask import Blueprint
blueprint = Blueprint('one', __name__)
@blueprint.route('/one')
def index():
return "one"from flask import Flask
from one import blueprint
app = Flask(__name__)
app.register_blueprint(blueprint)
@app.route("/")
def index():
return "Hello World"
if __name__ == "__main__":
app.run(debug=True)one.py
app.py
實體化
import進來
「註冊」:把blueprint裡的操作套用在app上
*這兩個步驟是必需的
一個簡單的實例

巢狀藍圖
可以在一個藍圖上註冊另一個藍圖
parent = Blueprint('parent', __name__, url_prefix='/parent')
child = Blueprint('child', __name__, url_prefix='/child')
parent.register_blueprint(child)
app.register_blueprint(parent)child這個藍圖裡面如果有個route叫create
那URL會變成/parent/child/create
一些進階參數設定
url_prefix
blueprint會用url_prefix來實作
https://www.youtube.com/@changyi/shorts
常會看到這種url
from flask import Blueprint
blueprint = Blueprint('one', __name__, url_prefix='/numbers')
@blueprint.route('/one')
def index():
return "one"
template_folder
- 用blueprint切網站的方式有兩種:依功能/路徑分
- 功能:根據不同功能用blueprint分,每個有自己的route、templates、static等等
- 路徑:只切到route,剩下的都只各分在一個大資料夾


有分出專屬的templates
template_folder
blueprint會用template_folder來實作
找到他對應的template
結構
from flask import Blueprint, render_template
blueprint = Blueprint('one', __name__, url_prefix='/numbers',template_folder='templates')
@blueprint.route('/one')
def index():
return render_template('one/index.html')

one/one.py
template_folder
從blueprint外面找他的模板的話,會在前面加(blueprint名字).
e.g. 要找one的index.html就會變成url_for(one.index)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="{{ url_for('one.index') }}">點我</a>
</body>
</html>
static_folder
用法跟template_folder一模一樣,就不舉例了
admin = Blueprint('admin', __name__, static_folder='static')長得一樣
其他套件介紹
Flask-WTF
- 不是你想的那樣子
- 幫你寫好的表單功能
- 有input&資料驗證&防CSRF
pip install Flask-WTFfrom flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(), Length(min=6)])
submit = SubmitField('Login')from flask import Flask, render_template, redirect, url_for, flash
from forms import LoginForm
app = Flask(__name__)
app.config['SECRET_KEY'] = '6767'
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
flash(f'成功登入!Email: {form.email.data}')
return redirect(url_for('login'))
return render_template('login.html', form=form)
if __name__ == '__main__':
app.run(debug=True)上:app.py 下:forms.py
Flask-WTF
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h2>登入</h2>
<form method="POST" action="">
{{ form.hidden_tag() }}
<div>
{{ form.email.label }}<br>
{{ form.email(placeholder="example@mail.com") }}<br>
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
<br>
<div>
{{ form.password.label }}<br>
{{ form.password() }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
<br>
<div>
{{ form.submit() }}
</div>
</form>
</body>
</html>login.html
Flask-login
- 直接幫你管理session,不用自己寫
- 主要
- LoginManager:主要控制
- UserMixin:創用戶的模型會需要繼承的
- user_loader:告訴套件怎麼從資料庫查該用戶物件
- 其他實用功能
- login_user(user)、logout_user():登入登出
- @login_required:裝飾器,登入才能看的頁面會加
- current_user:代表當前登入用戶的變數
Flask-login
這個我不太熟,詳細的可以看官方文件
Flask-bcrypt
- 密碼雜湊&驗證
pip install flask-bcryptfrom flask_bcrypt import Bcrypt
bcrypt = Bcrypt(app)
pw_hash = bcrypt.generate_password_hash('password').decode('utf-8')
is_valid = bcrypt.check_password_hash(pw_hash, 'password')加密
驗證(True/False)
上完了 噎
Flask[4]
By amberchen
Flask[4]
- 35