一樣是快一個月前的東西
#加密用的secret key
app.config['SECRET_KEY'] = 'c4dffa417abe4d31936cdf52d3a6d7ae'
# 存數據
session['key'] = 'value'
# 取數據
val = session.get('key')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)
對不起例子偷之前的
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 | |
|---|---|---|
| 中譯名稱 | 關聯式資料庫 | 非關聯式資料庫 |
| 格式 | 資料表格,欄&列 | 很多種 e.g. JSON、圖形 |
| 查詢方式 | 標準SQL語法 | 依使用的種類而定 |
| 資料關聯型 | 強 | 弱 |
| 彈性 | 低(定了就定了) | 高 |
| 例子 | MySQL、PostgreSQL、SQLite | MongoDB、Redis |
&
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)
初始化物件
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(),裡面存資料型別、條件等等
Step1. 開新terminal
Step2. 打python
Step3. 打這些(如果檔名跟變數名稱不一樣記得改)
Step4. 如果你前面都沒報錯,然後有一個叫instance的資料夾裡有一個叫site.db的檔案,那恭喜你成功創好DB了:)
可以看到表也建起來了
對路徑有更詳細的管理(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
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}")'before
after
| 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)正向查詢
反向查詢
好啦下周會講 講師在這邊道歉
試想一下
我們前面實作會用的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
所以你打算把一些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
然後他就死掉了
實際上這東西叫circular import,是python直譯的機制間接造成的問題
from flask import Flask
import onefrom app import app兩個檔案會反覆橫跳
app.py
one.py
有這些方法可以處理:
講回來,官方推薦用blueprints解決專案規模越來越大的問題
?
from flask import Blueprintfrom 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
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"有分出專屬的templates
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
從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>用法跟template_folder一模一樣,就不舉例了
admin = Blueprint('admin', __name__, static_folder='static')長得一樣
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
<!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
這個我不太熟,詳細的可以看官方文件
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)