Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
[CS Foundation] Web - 7 - CRUD in Flask
Search
x-village
August 16, 2018
130
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
[CS Foundation] Web - 7 - CRUD in Flask
x-village
August 16, 2018
More Decks by x-village
See All by x-village
[CS Foundation] Web - 6 - Flask Introduction
xvillage
0
270
[CS Foundation] AIML - 5 - Deep Learning
xvillage
1
120
[CS Foundation] Web - 4 - JavaScript Web Course
xvillage
0
41
[CS Foundation] Web - 5 - Database
xvillage
0
62
[CS Foundation] AIML - 3 - Common Issue
xvillage
1
170
[CS Foundation] AIML - 4 - Classification
xvillage
0
51
[CS Foundation] Web - 1 - Web Course intro
xvillage
2
37
[CS Foundation] Web - 2 - HTML and CSS Web Course
xvillage
0
58
[CS Foundation] Web - 3 - css layout
xvillage
0
100
Featured
See All Featured
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.3k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
320
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.7k
Balancing Empowerment & Direction
lara
6
1.1k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Code Review Best Practice
trishagee
74
20k
It's Worth the Effort
3n
188
29k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
160
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
360
Testing 201, or: Great Expectations
jmmastey
46
8.2k
Transcript
CRUD in Flask X-Village 網⾴頁開發課程 By Wei Lee
⼼心理理建設 • 你可能不會在這堂課學到很多Flask的細節 • 那會學到什什麼呢? • 從頭構思⼀一個後端 • 怎麼逐步修改後端
• 你可能會在這次的教學中 • 看不懂程式碼在做什什麼 • 發現程式的執⾏行行⾏行行為不如預期 • 這都是正常的 • 這次的教學是帶著⼤大家從不好的甚⾄至錯誤的程式碼,⼀一步
⼀一步理理解和修改 ⼼心理理建設
什什麼是CRUD?
Image: http://lmgtfy.com/?q=CRUD
None
None
Image: https://zh.wikipedia.org/zh-tw/資料操縱語⾔言
Image: https://zh.wikipedia.org/zh-tw/資料操縱語⾔言
CRUD • Create (新增) • Read (查詢) • Update (修改)
• Delete (刪除)
CRUD create (新增) INSERT INTO address_book (name, email) VALUES ('Tommy',
'
[email protected]
');
CRUD create (新增) INSERT INTO address_book (name, email) VALUES ('Tommy',
'
[email protected]
');
CRUD read (查詢) SELECT * FROM address_book WHERE name='Tommy';
CRUD read (查詢) SELECT * FROM address_book WHERE name='Tommy';
CRUD update (修改) UPDATE address_book SET name='Tom', email='
[email protected]
' WHERE name=‘Tommy';
CRUD update (修改) UPDATE address_book SET name='Tom', email='
[email protected]
' WHERE name=‘Tommy';
CRUD delete (刪除) DELETE FROM address_book WHERE name='Tom';
CRUD delete (刪除) DELETE FROM address_book WHERE name='Tom';
CRUD的概念念其實就這樣 接下來來就要進入在Flask實作的部分了了!
正式進入實作前 先來來說說這堂課最後要完成什什麼吧 Image: https://pixabay.com/en/goal-setting-goal-dart-target-1955806/
記帳程式
Exercise 1 你有⽤用過什什麼記帳程式? 找找看現在有哪些記帳程式是比較多⼈人⽤用的?
Exercise 1- Discussion • Nagging Money 碎碎念念記帳 Image: https://www.mozeapp.com/zh-tw#features Image:
https://web.andromoney.com
Exercise 1- Discussion • Nagging Money 碎碎念念記帳 • MOZE 2.0
Image: https://www.mozeapp.com/zh-tw#features Image: https://web.andromoney.com
Exercise 1- Discussion • Nagging Money 碎碎念念記帳 • MOZE 2.0
Image: https://www.mozeapp.com/zh-tw#features • AndroMoney Image: https://web.andromoney.com
Exercise 2 你覺得⼀一個最基本的記帳程式要什什麼功能?
Exercise 2 - Discussion • 新增帳務紀錄 (Create) • 查詢帳務紀錄 (Read)
• 更更新帳務紀錄 (Update) • 刪除帳務紀錄 (Delete)
Exercise 3 你覺得要達到這樣的⽬目的我們需要什什麼資料表? ?
Exercise 3 - Discussion Record
Exercise 4 Record這個資料表要有什什麼欄欄位? ?
Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER]
score [TEXT] subject [TEXT] {0,1} 0..N 補充 - ER diagram • Entity-relationship Diagram • ⼀一種描述資料庫中資料架構的⽅方式 • ER模型
Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER]
score [TEXT] subject [TEXT] {0,1} 0..N 補充 - ER diagram • Entity-relationship Diagram • ⼀一種描述資料庫中資料架構的⽅方式 • ER模型 資料表名稱
Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER]
score [TEXT] subject [TEXT] {0,1} 0..N 補充 - ER diagram • Entity-relationship Diagram • ⼀一種描述資料庫中資料架構的⽅方式 • ER模型 資料欄欄位 (欄欄位名稱[欄欄位型別])
Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER]
score [TEXT] subject [TEXT] {0,1} 0..N 補充 - ER diagram (cont.)
Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER]
score [TEXT] subject [TEXT] {0,1} 0..N 補充 - ER diagram (cont.) 對⼀一筆Student的資料來來說,可以有0~多個Grade的資料
Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER]
score [TEXT] subject [TEXT] {0,1} 0..N 補充 - ER diagram (cont.) 對⼀一筆Student的資料來來說,可以有0~多個Grade的資料 對⼀一筆Grade來來說,可以有0或1個Student的資料
補充 - ER diagram (cont.) Student id [INTEGER] name [STRING(120)]
Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N
補充 - ER diagram (cont.) Student id [INTEGER] name [STRING(120)]
Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N
補充 - ER diagram (cont.) Student id [INTEGER] name [STRING(120)]
Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N
補充 - ER diagram (cont.) Student id [INTEGER] name [STRING(120)]
Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N
補充 - ER diagram (cont.) Student id [INTEGER] name [STRING(120)]
Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N
Exercise 4 Record這個資料表要有什什麼欄欄位? ?
Exercise 4 - Discussion Record cost [INTEGER]
Exercise 4 - Discussion Record cost [INTEGER] 如果我們新增了了兩兩筆cost都是100的Record, 我們要怎麼分辨他們?
Exercise 4 - Discussion Record id [INTEGER] cost [INTEGER]
Exercise 4 - Discussion Record id [INTEGER] cost [INTEGER] 只有cost,之後使⽤用者會知道這筆帳花在什什麼上嗎?
Exercise 4 - Discussion Record id [INTEGER] name [STRING(120)] cost
[INTEGER]
Exercise 4 - Discussion Record id [INTEGER] name [STRING(120)] cost
[INTEGER] 我們回到上⼀一堂課結束前,最後的設計了了!
到⽬目前為⽌止,我們⼀一⾏行行程式碼都還沒動
我想說的是... Image: Photoed by LeeW in Pycon TW 2017
我想說的是... Image: Photoed by LeeW in Pycon TW 2017
我想說的是... Image: Photoed by LeeW in Pycon TW 2017
先想好,再寫code
Clone Sample Code From GitHub git clone https://github.com/x-village/web- acccounting-example cd
web-acccounting-example git checkout 0.1 本次課程的範例例程式碼,都在GitHub上了了 ⾃自⼰己去找吧(???
使⽤用說明 • 確實使⽤用git clone • 不要在GitHub上⽤用下載的 • 使⽤用0.1版 (git checkout
0.1) • 跟著接下來來的教學⼀一步⼀一步做修改 • 除了了跑不動以外,遇到的錯誤都可能是正常的 • 但可以先思考為什什麼會這樣 • 要怎麼樣才能修正
專案架構 - Version 0.1 . !"" app.py
app.py (1/2) 1 from flask import Flask 2 from flask_sqlalchemy
import SQLAlchemy 3 from flask_migrate import Migrate 4 5 6 app = Flask(__name__) 7 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' 8 app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', True) 9 10 db = SQLAlchemy(app) 11 migrate = Migrate(app, db)
app.py (2/2) 14 class Record(db.Model): 15 id = db.Column(db.Integer, primary_key=True)
16 name = db.Column(db.String(120), nullable=True) 17 cost = db.Column(db.Integer, nullable=True) 18 19 20 @app.route("/") 21 def hello(): 22 return "Hello World!" 23 24 25 @app.route("/name") 26 def name(): 27 return "Hello Frank" git checkout 0.1
專案架構 - Version 0.1 現在專案只有⼀一個app.py 要怎麼讓剛接觸的⼈人快速上⼿手呢?
專案架構 - Version 0.1.1 . #"" README.md !"" app.py git
checkout 0.1.1 為專案加入說明⽂文件
README
File "/home/leew/web-acccounting-example/app.py", line 2, in <module> from flask_sqlalchemy import SQLAlchemy
ModuleNotFoundError: No module named 'flask_sqlalchemy'
File "/home/leew/web-acccounting-example/app.py", line 2, in <module> from flask_sqlalchemy import SQLAlchemy
ModuleNotFoundError: No module named 'flask_sqlalchemy' 好像少了了⼀一些需要的函式庫
專案架構 - Version 0.1.2 . #"" README.md #"" app.py !""
requirements.txt 為專案加入requirements.txt 指定有⽤用到的函式庫
requirements.txt Flask==1.0.2 Flask-Migrate==2.2.1 Flask-SQLAlchemy==2.3.2 git checkout 0.1.2
pip install -r requirements.txt 於是新加入的⼈人知道我們需要哪些函式庫了了!
CRUD in Web Service Screenshot: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete
CRUD in Web Service Screenshot: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete
CRUD in Web Service Screenshot: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete
app.py (CRUD) 新增帳務紀錄 35 @app.route("/record", methods=['POST']) 36 def add_record(): 37
record = Record(name='breakfast', cost=70) 38 db.session.add(record) 39 db.session.commit() 40 return 'Create Succeeded', 200
app.py (CRUD) 查詢帳務紀錄 1 import json ... ... 43 @app.route("/record",
methods=['GET']) 44 def get_record(): 45 records = Record.query.all() 46 records_json = json.dumps( 47 [ 48 { 49 'id': record.id, 50 'name': record.name, 51 'cost': record.cost 52 } 53 for record in records 54 ], 55 indent=4, 56 ensure_ascii=False 57 ) 58 return records_json, 200
app.py (CRUD) 查詢帳務紀錄 61 @app.route("/record", methods=["PUT"]) 62 def update_record(): 63
records = Record.query.filter_by(name='breakfast') 64 return 'Update Succeeded', 200
app.py (CRUD) 刪除帳務紀錄 67 @app.route("/record", methods=["DELETE"]) 68 def delete_record(): 69
first_record = Record.query.filter_by(name='breakfast').first() 70 db.session.delete(first_record) 71 db.session.commit() 72 return 'Delete Succeeded', 200 git checkout 0.2.0
Execute
Exercise 5 1. 完成前⾯面的程式碼,開啟flask server (flask run) 2. 開啟瀏覽器到http://127.0.0.1:5000/record 3.
確認出現以下畫⾯面
剛剛做了了什什麼呢
Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record 剛剛做了了什什麼呢
Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record Method 剛剛做了了什什麼呢
Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record Method 主機位址 剛剛做了了什什麼呢
Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record Method 主機位址 Port 剛剛做了了什什麼呢
Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record Method 主機位址 Port Path 剛剛做了了什什麼呢
Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record 因為現在還沒有東⻄西 所以是空的list Method 主機位址 Port Path
剛剛做了了什什麼呢
那POST, PUT, DELETE呢? Image: https://www.getpostman.com/
那POST, PUT, DELETE呢? Image: https://www.getpostman.com/
Postman
Postman
Postman
Postman
Exercise 6 1. 下載Postman(https://www.getpostman.com/) 2. 對127.0.0.1:5000/record測試POST, GET, PUT, DELETE四種已經實作的⽅方法
現在回頭來來看程式碼在做什什麼吧
Router @app.route("/record", methods=['POST']) def add_record(): record = Record(name='breakfast', cost=70) db.session.add(record)
db.session.commit() return 'Create Succeeded', 200
Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Server
Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Request Server
Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Request Response Server
Router (cont.)
Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Request Response / Server
http://flask.pocoo.org/
Router (cont.)
Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Request Response / /community/
Server http://flask.pocoo.org/community/
Router (cont.)
Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Request Response http://flask.pocoo.org/docs/1.0/ /
/community/ /docs/1.0/ Server
再回到程式碼
app.py (CRUD) - (1/4) @app.route("/record", methods=['POST']) def add_record(): record =
Record( name=‘breakfast', cost=70 ) db.session.add(record) db.session.commit() return 'Create Succeeded', 200
app.py (CRUD) - (1/4) @app.route("/record", methods=['POST']) def add_record(): record =
Record( name=‘breakfast', cost=70 ) db.session.add(record) db.session.commit() return 'Create Succeeded', 200 對/record這個route,做POST
app.py (CRUD) - (1/4) @app.route("/record", methods=['POST']) def add_record(): record =
Record( name=‘breakfast', cost=70 ) db.session.add(record) db.session.commit() return 'Create Succeeded', 200 創立⼀一筆名稱為breakfast, 花費為70的record
app.py (CRUD) - (1/4) @app.route("/record", methods=['POST']) def add_record(): record =
Record( name=‘breakfast', cost=70 ) db.session.add(record) db.session.commit() return 'Create Succeeded', 200 將剛創立的record加入資料庫
app.py (CRUD) - (1/4) @app.route("/record", methods=['POST']) def add_record(): record =
Record( name=‘breakfast', cost=70 ) db.session.add(record) db.session.commit() return 'Create Succeeded', 200 回傳⽂文字 狀狀態碼 Reference
明明就是要新增 為什什麼完全沒看到接受使⽤用者輸入的地⽅方呢? Image: https://pxhere.com/en/photo/1403204
@app.route("/record", methods=['POST']) def add_record(): record = Record(name='breakfast', cost=70) db.session.add(record) db.session.commit()
return 'Create Succeeded', 200 from flask import Flask, request ... ... req_data = request.form name = req_data['name'] cost = req_data['cost'] app.py (CRUD) - (2/4) git checkout 0.2.2
@app.route("/record", methods=['POST']) def add_record(): record = Record(name='breakfast', cost=70) db.session.add(record) db.session.commit()
return 'Create Succeeded', 200 from flask import Flask, request ... ... req_data = request.form name = req_data['name'] cost = req_data['cost'] app.py (CRUD) - (2/4) git checkout 0.2.2 取得client傳入資訊
app.py (CRUD) - (3/4)
app.py (CRUD) - (3/4)
app.py (CRUD) - (3/4)
app.py (CRUD) - (3/4)
app.py (CRUD) - (4/4)
app.py (CRUD) - (4/4)
app.py (CRUD) - (4/4)
app.py (CRUD) - (1/7) @app.route("/record", methods=['GET']) def get_record(): ... ...
get_record? get_records?
app.py (CRUD) - (1/7) @app.route("/record", methods=['GET']) def get_record(): ... ...
get_record? get_records? 這樣的命名是不是有可能造成誤解呢
app.py (CRUD) - (2/7) import json ... ... @app.route("/record", methods=['GET'])
def get_record (): records = Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200 s git checkout 0.2.3
app.py (CRUD) - (2/7) import json ... ... @app.route("/record", methods=['GET'])
def get_record (): records = Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200 s git checkout 0.2.3
app.py (CRUD) - (3/7) import json ... ... @app.route("/record", methods=['GET'])
def get_records(): records = Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200
app.py (CRUD) - (3/7) import json ... ... @app.route("/record", methods=['GET'])
def get_records(): records = Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200 從資料庫中找出所有record資料
app.py (CRUD) - (3/7) import json ... ... @app.route("/record", methods=['GET'])
def get_records(): records = Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200 json.dumps( ..., indent=4, ensure_ascii=False ) 把某個物件轉成json格式的字串串
app.py (CRUD) - (3/7) 這種做法叫list comprehension 跟下⾯面的程式碼效果是⼀一樣的 some_list = list()
for record in records: record_data = { 'id': record.id, 'name': record.name, 'cost': record.cost } some_list.append(record_data) import json ... ... @app.route("/record", methods=['GET']) def get_records(): records = Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200
如果不會這種寫法 會不會造成程式碼寫不出來來
如果不會這種寫法 會不會造成程式碼寫不出來來 並不會!
import json ... ... @app.route("/record", methods=['GET']) def get_records(): records =
Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200 app.py (CRUD) - (4/7) 處理理json這種常⾒見見的操作 Flask當然幫我們處理理好了了
import json ... ... @app.route("/record", methods=['GET']) def get_records(): records =
Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200 app.py (CRUD) - (4/7) import json ... ... @app.route("/record", methods=['GET']) def get_records(): records = Record.query.all() records_json = json.dumps( [ { 'id': record.id, 'name': record.name, 'cost': record.cost } for record in records ], indent=4, ensure_ascii=False ) return records_json, 200 處理理json這種常⾒見見的操作 Flask當然幫我們處理理好了了
app.py (CRUD) - (5/7) jsonify records_data = [ { 'id':
record.id, 'name': record.name, 'cost': record.cost } for record in records ] return jsonify(records_data), 200 from flask import Flask, request, @app.route("/record", methods=['GET']) def get_records(): records = Record.query.all() 兩兩種做法的差別請參參考 1. Flask设置返回json格式数据 2. json.dumps vs flask.jsonify git checkout 0.2.4
app.py (CRUD) - (6/7) 如果我們想要除了了get_records 還需要get_record呢?
app.py (CRUD) - (7/7) @app.route( ‘/record/<int:record_id>', methods=[‘GET'] ) def get_record(record_id):
record = ( Record.query .filter_by(id=record_id).first() ) record_data = { 'id': record.id, 'name': record.name, 'cost': record.cost } return jsonify(record_data), 200 git checkout 0.2.5 Warning: 這是新的function(get_record)跟前⾯面的get_records不⼀一樣
app.py (CRUD) - (7/7) @app.route( ‘/record/<int:record_id>', methods=[‘GET'] ) def get_record(record_id):
record = ( Record.query .filter_by(id=record_id).first() ) record_data = { 'id': record.id, 'name': record.name, 'cost': record.cost } return jsonify(record_data), 200 指定某⼀一筆record的id git checkout 0.2.5 Warning: 這是新的function(get_record)跟前⾯面的get_records不⼀一樣
app.py (CRUD) - (7/7) @app.route( ‘/record/<int:record_id>', methods=[‘GET'] ) def get_record(record_id):
record = ( Record.query .filter_by(id=record_id).first() ) record_data = { 'id': record.id, 'name': record.name, 'cost': record.cost } return jsonify(record_data), 200 從資料庫找出符合條件的 第⼀一筆record git checkout 0.2.5 Warning: 這是新的function(get_record)跟前⾯面的get_records不⼀一樣
@app.route("/record", methods=["PUT"]) def update_record(): records = Record.query.filter_by(name=‘breakfast') return 'Update Succeeded',
200 app.py (CRUD) - (1/2)
@app.route("/record", methods=["PUT"]) def update_record(): records = Record.query.filter_by(name=‘breakfast') return 'Update Succeeded',
200 app.py (CRUD) - (1/2) @app.route("/record", methods=["PUT"]) def update_record(): records = Record.query.filter_by(name=‘breakfast') return 'Update Succeeded', 200
app.py (CRUD) - (2/2) @app.route( ‘/record/<int:record_id>', methods=[‘PUT'] ) def update_record(record_id):
req_data = request.form record = ( Record.query .filter_by(id=record_id).first() ) record.name = req_data['name'] record.cost = req_data['cost'] db.session.add(record) db.session.commit() return ‘Update Succeeded', 200 git checkout 0.2.6
app.py (CRUD) - (2/2) @app.route( ‘/record/<int:record_id>', methods=[‘PUT'] ) def update_record(record_id):
req_data = request.form record = ( Record.query .filter_by(id=record_id).first() ) record.name = req_data['name'] record.cost = req_data['cost'] db.session.add(record) db.session.commit() return ‘Update Succeeded', 200 取得client傳來來的資料 git checkout 0.2.6
app.py (CRUD) - (2/2) @app.route( ‘/record/<int:record_id>', methods=[‘PUT'] ) def update_record(record_id):
req_data = request.form record = ( Record.query .filter_by(id=record_id).first() ) record.name = req_data['name'] record.cost = req_data['cost'] db.session.add(record) db.session.commit() return ‘Update Succeeded', 200 找出指定的record git checkout 0.2.6
app.py (CRUD) - (2/2) @app.route( ‘/record/<int:record_id>', methods=[‘PUT'] ) def update_record(record_id):
req_data = request.form record = ( Record.query .filter_by(id=record_id).first() ) record.name = req_data['name'] record.cost = req_data['cost'] db.session.add(record) db.session.commit() return ‘Update Succeeded', 200 更更新record資訊 git checkout 0.2.6
app.py (CRUD) - (2/2) @app.route( ‘/record/<int:record_id>', methods=[‘PUT'] ) def update_record(record_id):
req_data = request.form record = ( Record.query .filter_by(id=record_id).first() ) record.name = req_data['name'] record.cost = req_data['cost'] db.session.add(record) db.session.commit() return ‘Update Succeeded', 200 將修改過後的record存入資料庫 git checkout 0.2.6
@app.route("/record", methods=["DELETE"]) def delete_record(): first_record = ( Record.query .filter_by(name=‘breakfast’).first() )
db.session.delete(first_record) db.session.commit() return 'Delete Succeeded', 200 app.py (CRUD) - (1/2)
@app.route("/record", methods=["DELETE"]) def delete_record(): first_record = ( Record.query .filter_by(name=‘breakfast’).first() )
db.session.delete(first_record) db.session.commit() return 'Delete Succeeded', 200 app.py (CRUD) - (1/2) 找到名稱是breakfast的record
@app.route("/record", methods=["DELETE"]) def delete_record(): first_record = ( Record.query .filter_by(name=‘breakfast’).first() )
db.session.delete(first_record) db.session.commit() return 'Delete Succeeded', 200 app.py (CRUD) - (1/2) 刪除指定record
@app.route("/record", methods=["DELETE"]) def delete_record(): first_record = ( Record.query .filter_by(name=‘breakfast’).first() )
db.session.delete(first_record) db.session.commit() return 'Delete Succeeded', 200 app.py (CRUD) - (1/2) 刪除指定record 等等,為什什麼這個功能只能刪除breakfast的record?
@app.route("/record", methods=["DELETE"]) def delete_record(): first_record = ( Record.query .filter_by(name=‘breakfast’).first() )
db.session.delete(first_record) db.session.commit() return 'Delete Succeeded', 200 app.py (CRUD) - (1/2) @app.route("/record", methods=["DELETE"]) def delete_record(): first_record = ( Record.query .filter_by(name=‘breakfast’).first() ) db.session.delete(first_record) db.session.commit() return 'Delete Succeeded', 200 等等,為什什麼這個功能只能刪除breakfast的record?
def delete_record(record_id): db.session.delete(record) db.session.commit() return 'Delete Succeeded', 200 @app.route("/record/<int:record_id>", methods=["DELETE"])
record = Record.query.filter_by(id=record_id).first() app.py (CRUD) - (2/2) git checkout 0.2.7
def delete_record(record_id): db.session.delete(record) db.session.commit() return 'Delete Succeeded', 200 @app.route("/record/<int:record_id>", methods=["DELETE"])
record = Record.query.filter_by(id=record_id).first() app.py (CRUD) - (2/2) git checkout 0.2.7 從資料庫找出符合條件的 第⼀一筆record
Exercise 7 1. 完成前⾯面的程式碼,開啟flask server (flask run) 2. ⽤用Postman對http://127.0.0.1:5000/record 做多次的POST
3. 開啟瀏覽器到http://127.0.0.1:5000/record 4. 確認出現類似以下的畫⾯面 5. 透過Postman去測試現在的POST, PUT, DELETE是否都能對 資料庫作操作 (只要出現_____ Succeeded就算成功)
終於完成CRUD了了!
但只讓使⽤用者看到這樣的畫⾯面 好像也不太對
Server Side Render Browser Server Database http://…/… html SQL Data
Image: https://www.kisspng.com/png-computer-icons-html-blog-web-page-video-recorder-1227080/
Client Side Render Browser Server Database http://…/… json SQL Data
Image: https://www.kisspng.com/png-computer-icons-html-blog-web-page-video-recorder-1227080/
# views @app.route("/") def hello(): return "Hello World!" 為我們的記帳程式做⼀一個⾸首⾴頁
# views @app.route("/") def hello(): return "Hello World!" 為我們的記帳程式做⼀一個⾸首⾴頁
Home Page @app.route("/") def index(): return ''' <html> ... <title>x-village<title>
... <html> ''' git checkout 0.3
Home Page @app.route("/") def index(): return ''' <html> ... <title>x-village<title>
... <html> ''' 回傳整個html 第⼆二個參參數如果沒提供狀狀態碼 則預設會回傳200 git checkout 0.3
Home Page @app.route("/") def index(): return ''' <html> ... <title>x-village<title>
... <html> ''' 回傳整個html 第⼆二個參參數如果沒提供狀狀態碼 則預設會回傳200 如果html很長很長,app.py就會變得⼜又臭⼜又長 git checkout 0.3
Home Page @app.route("/") def index(): return ''' <html> ... <title>x-village<title>
... <html> ''' 回傳整個html 第⼆二個參參數如果沒提供狀狀態碼 則預設會回傳200 如果html很長很長,app.py就會變得⼜又臭⼜又長 Flask當然也幫你想到了了! git checkout 0.3
Home Page - Template @app.route("/") def index(): return ''' <html>
... <title>x-village<title> ... <html> ''' git checkout 0.3.1
Home Page - Template @app.route("/") def index(): return ''' <html>
... <title>x-village<title> ... <html> ''' git checkout 0.3.1
Home Page - Template @app.route("/") def index(): return ''' <html>
... <title>x-village<title> ... <html> ''' 把整份html的⽂文字寫到 另⼀一份html檔案中 git checkout 0.3.1
Home Page - Template from flask import Flask, request, jsonify,
render_template ... @app.route("/") def index(): return render_template('index.html') @app.route("/") def index(): return ''' <html> ... <title>x-village<title> ... <html> ''' 把整份html的⽂文字寫到 另⼀一份html檔案中 git checkout 0.3.1
#"" README.md #"" app.py #"" index.html !"" requirements.txt 為什什麼flask找不到我的index.html
. #"" README.md #"" app.py #"" requirements.txt #"" templates $
!"" index.html !"" test.db 專案架構 - Version 0.3 git checkout 0.3
. #"" README.md #"" app.py #"" requirements.txt #"" templates $
!"" index.html !"" test.db 專案架構 - Version 0.3 git checkout 0.3 Flask預設會在名為templates 的資料夾下找html檔
Designed by Po-Chun Lu Download Here https://raw.githubusercontent.com/x-village/web-acccounting-example/0.3/templates/index.html 因為這堂課教的是後端,前端的程式碼就先給你了了
如果只是回傳整份html 好像看不太出有後端的好處
Template System • 先寫好html的模板,再根據不同的狀狀況填入資料 • Flask 預設使⽤用 Jinja2 Image: https://pagingsupermom.com/alphabet-fill/
http://jinja.pocoo.org/docs/2.10/
Template System • 先寫好html的模板,再根據不同的狀狀況填入資料 • Flask 預設使⽤用 Jinja2 Image: https://pagingsupermom.com/alphabet-fill/
http://jinja.pocoo.org/docs/2.10/
Template System • 先寫好html的模板,再根據不同的狀狀況填入資料 • Flask 預設使⽤用 Jinja2 Image: https://pagingsupermom.com/alphabet-fill/
http://jinja.pocoo.org/docs/2.10/ B
Template System • 先寫好html的模板,再根據不同的狀狀況填入資料 • Flask 預設使⽤用 Jinja2 Image: https://pagingsupermom.com/alphabet-fill/
http://jinja.pocoo.org/docs/2.10/ B E
index.html <!DOCTYPE html> <html lang="en"> <head> ... <title>Accounting</title> ... </head>
... </html> 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html
index.html <!DOCTYPE html> <html lang="en"> <head> ... <title>Accounting</title> ... </head>
... </html> 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html
index.html (new) <!DOCTYPE html> <html lang="en"> <head> ... <title>{{ title
}}</title> ... </head> ... </html> index.html <!DOCTYPE html> <html lang="en"> <head> ... <title>Accounting</title> ... </head> ... </html> 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html
app.py index.html (new) <!DOCTYPE html> <html lang="en"> <head> ... <title>{{
title }}</title> ... </head> ... </html> index.html <!DOCTYPE html> <html lang="en"> <head> ... <title>Accounting</title> ... </head> ... </html> @app.route("/") def index(): return render_template('index.html') 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html
app.py index.html (new) <!DOCTYPE html> <html lang="en"> <head> ... <title>{{
title }}</title> ... </head> ... </html> index.html <!DOCTYPE html> <html lang="en"> <head> ... <title>Accounting</title> ... </head> ... </html> , title='Accounting') @app.route("/") def index(): return render_template('index.html' 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html
app.py index.html (new) <!DOCTYPE html> <html lang="en"> <head> ... <title>{{
title }}</title> ... </head> ... </html> index.html <!DOCTYPE html> <html lang="en"> <head> ... <title>Accounting</title> ... </head> ... </html> , title='Accounting') @app.route("/") def index(): return render_template('index.html' 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html
@app.route("/record", methods=['POST']) def add_record(): ... @app.route("/record", methods=['GET']) def get_records(): ...
@app.route('/record/<int:record_id>', methods=['GET']) def get_record(record_id): ... @app.route('/record/<int:record_id>', methods=['PUT']) def update_record(record_id): ... @app.route("/record/<int:record_id>", methods=["DELETE"]) def delete_record(record_id): ... 還記得我們先寫好的record API們嗎
<!DOCTYPE html> <html lang="en"> ... <body> ... <script src="https://code.jquery.com/jquery-3.3.1.min.js" ...></script>
<script> ... /** * Send Post Request to create new purchase record */ function postItemToServer(inputData) { $.ajax({ url: API_URL, method: 'POST', data: inputData, success: function (data) { console.log(data); }, error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); } }); } ... </script> </body> </html> 透過ajax向後端API取得資料 git checkout 1.0 Download Here https://raw.githubusercontent.com/x-village/web-acccounting-example/1.0/templates/index.html
Exercise 8 1. 完成前⾯面的程式碼,開啟flask server (flask run) 2. 開啟瀏覽器到http://127.0.0.1:5000 3.
新增⼀一筆name是breakfast, cost是70的資料 4. 重新整理理,確認新增的資料是否出現在⾴頁⾯面最下⾯面
到這裡 ⼀一個基本的網站就完成了了
Image: https://www.flickr.com/photos/mathoov/4681491052
Image: https://www.flickr.com/photos/mathoov/4681491052
Image: https://www.flickr.com/photos/mathoov/4681491052 Actually, there are two more things
接下來來的內容比較進階 不會深入,只會帶⼀一點基本的概念念
git checkout X.X.X 如果你眼睛比較尖 你也許會發現有些⾴頁⾯面右下⾓角有這個
git checkout X.X.X 如果你眼睛比較尖 你也許會發現有些⾴頁⾯面右下⾓角有這個
git checkout X.X.X 如果你眼睛比較尖 你也許會發現有些⾴頁⾯面右下⾓角有這個 在我的範例例程式碼 透過這個git指令 就能切換到當下內容的程式碼
前⾯面其實還有⼀一個Bug Image: https://www.epicgames.com/unrealtournament/forums/unreal-tournament-discussion/ut-game-general- discussion/7737-what-do-we-want-bright-skins-when-do-we-want-them-in-the-next-patch/page11
前⾯面其實還有⼀一個Bug Image: https://www.epicgames.com/unrealtournament/forums/unreal-tournament-discussion/ut-game-general- discussion/7737-what-do-we-want-bright-skins-when-do-we-want-them-in-the-next-patch/page11
@app.route("/record", methods=['POST']) def add_record(): req_data = request.form name = req_data['name']
cost = req_data['cost'] record = Record(name='breakfast', cost=70) db.session.add(record) db.session.commit() return 'Create Succeeded', 200
@app.route("/record", methods=['POST']) def add_record(): req_data = request.form name = req_data['name']
cost = req_data['cost'] record = Record(name='breakfast', cost=70) db.session.add(record) db.session.commit() return 'Create Succeeded', 200 Client輸入的資訊,根本沒被⽤用來來建立record!
@app.route("/record", methods=['POST']) def add_record(): req_data = request.form name = req_data['name']
cost = req_data['cost'] db.session.add(record) db.session.commit() return 'Create Succeeded', 200 record = Record(name=name, cost=cost) 其實要修改並不困難 但我們是不是有可能在專案發佈前就先發現呢? git checkout 1.0.2
測試
在正式發佈前,多⽤用Postman測試吧
現在只有4個API 如果有400個API要測試呢? Image: https://www.lianan.com.tw/drliananepaper/Article/294-2
⾃自動化測試
⾃自動化測試 def add(x, y): return x + y def test_add():
assert add(1, 1) == 2 • 預先將每⼀一個功能的輸入和預期的結果列列出來來 • 透過程式比對輸入後的結果和預期結果 • e.g.,
def test_add_record(client, accept_json): resp = client.post( '/record', headers=accept_json, data={ 'name':
'test_record', 'cost': 100 } ) assert resp.status_code == 200 assert resp.data.decode('utf-8') == 'Create Succeeded' test_record = Record.query.filter_by(name='test_record').first() assert test_record.name == 'test_record' assert test_record.cost == 100 測試add_record git checkout 1.0.2
def test_add_record(client, accept_json): resp = client.post( '/record', headers=accept_json, data={ 'name':
'test_record', 'cost': 100 } ) assert resp.status_code == 200 assert resp.data.decode('utf-8') == 'Create Succeeded' test_record = Record.query.filter_by(name='test_record').first() assert test_record.name == 'test_record' assert test_record.cost == 100 測試add_record git checkout 1.0.2
def test_add_record(client, accept_json): resp = client.post( '/record', headers=accept_json, data={ 'name':
'test_record', 'cost': 100 } ) assert resp.status_code == 200 assert resp.data.decode('utf-8') == 'Create Succeeded' test_record = Record.query.filter_by(name='test_record').first() assert test_record.name == 'test_record' assert test_record.cost == 100 測試add_record git checkout 1.0.2
def test_add_record(client, accept_json): resp = client.post( '/record', headers=accept_json, data={ 'name':
'test_record', 'cost': 100 } ) assert resp.status_code == 200 assert resp.data.decode('utf-8') == 'Create Succeeded' test_record = Record.query.filter_by(name='test_record').first() assert test_record.name == 'test_record' assert test_record.cost == 100 測試add_record git checkout 1.0.2
在程式碼亂到無法挽回前 先重整架構吧
專案架構 - Version 1.1 original (Version 1.0.2) new (Version 1.1)
. #"" README.md #"" app.py #"" requirements.txt #"" templates $ !"" index.html !"" test_app.py #"" README.md #"" app.py #"" models.py #"" requirements.txt #"" templates $ !"" index.html #"" tests $ #"" __init__.py $ !"" test_record.py !"" views.py git checkout 1.0.3
專案架構 - Version 1.1 original (Version 1.0.2) new (Version 1.1)
. #"" README.md #"" app.py #"" requirements.txt #"" templates $ !"" index.html !"" test_app.py #"" README.md #"" app.py #"" models.py #"" requirements.txt #"" templates $ !"" index.html #"" tests $ #"" __init__.py $ !"" test_record.py !"" views.py git checkout 1.0.3
專案架構 - Version 1.1 original (Version 1.0.2) new (Version 1.1)
. #"" README.md #"" app.py #"" requirements.txt #"" templates $ !"" index.html !"" test_app.py #"" README.md #"" app.py #"" models.py #"" requirements.txt #"" templates $ !"" index.html #"" tests $ #"" __init__.py $ !"" test_record.py !"" views.py git checkout 1.0.3
Appendix
Deployment (部署)
接著把我們的網站公開給⼤大家吧 flask run --host=0.0.0.0
接著把我們的網站公開給⼤大家吧 flask run --host=0.0.0.0 設定成0.0.0.0,外⾯面的⼈人就能訪問到你的網站了了
可是這樣...我的電腦就要⼀一直開著了了
丟到雲端上吧
常⾒見見的平台
常⾒見見的平台
接下來來會⽤用Heroku作為範例例
註冊⼀一個Heroku帳號
註冊⼀一個Heroku帳號
創立Heroku app (1/3)
創立Heroku app (1/3)
創立Heroku app (2/3)
創立Heroku app - (3/3)
https://devcenter.heroku.com/articles/heroku-cli#download-and-install 安裝Heroku CLI
cd "Your project" heroku login git init heroku git:remote -a
"Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案
cd "Your project" heroku login git init heroku git:remote -a
"Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 登入Heroku
cd "Your project" heroku login git init heroku git:remote -a
"Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 如果專案本⾝身就被git追蹤,可以跳過這步
cd "Your project" heroku login git init heroku git:remote -a
"Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 為git加入heroku這個remote
cd "Your project" heroku login git init heroku git:remote -a
"Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 發佈到Heroku上
cd "Your project" heroku login git init heroku git:remote -a
"Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 在瀏覽器開啟剛剛發佈的專案
可是沒成功
可是沒成功
heroku logs --tail 來來看看發⽣生什什麼事 (在CMD/終端機)
None
我們還沒跟Heroku説要怎麼跑我們的網站
Getting Started on Heroku with Python https://devcenter.heroku.com/articles/getting-started-with-python
我們還需要三個檔案 1. requirements.txt (或Piplock) 2. Profile 3. runtime.txt
我們還需要三個檔案 1. requirements.txt (或Piplock) 2. Profile 3. runtime.txt 告訴Heroku需要的函式庫
我們還需要三個檔案 1. requirements.txt (或Piplock) 2. Profile 3. runtime.txt 告訴Heroku需要的函式庫 告訴Heroku如何執⾏行行你的web
我們還需要三個檔案 1. requirements.txt (或Piplock) 2. Profile 3. runtime.txt 告訴Heroku需要的函式庫 告訴Heroku如何執⾏行行你的web
告訴Heroku要⽤用 哪個版本的python
requirements.txt ... gunicorn==19.9.0 Procfile web gunicorn app:app runtime.txt python-3.6.5 git
checkout 1.0.4
requirements.txt ... gunicorn==19.9.0 Procfile web gunicorn app:app runtime.txt python-3.6.5 在原先的requirements.txt
加入gunicorn(部署⽤用的函式庫) git checkout 1.0.4
再試⼀一次! git add requirements.txt Procfile runtime.txt git push heroku master
heroku open
成功了了!
可是...按了了Submit為什什麼沒有⽤用
heroku logs --tail
heroku logs --tail 看起來來是資料庫的問題
Heroku 不⽀支援sqlite https://devcenter.heroku.com/categories/data-management
資料庫設定
資料庫設定 創建資料庫
換成使⽤用Postgres吧 https://www.heroku.com/postgres
換成使⽤用Postgres吧 https://www.heroku.com/postgres
None
None
None
找到你在Heroku創的app
我們可以在Heroku上⽤用Postgres了了
點⼀一下
點⼀一下
資料庫的資料都在這
資料庫設定 創建資料庫
資料庫設定 創建資料庫
資料庫設定 創建資料庫 連接資料庫
回到我們原本連資料庫的地⽅方 (app.py) from flask import Flask, request, jsonify, render_template from
flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate # initialize app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db' app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', True) db = SQLAlchemy(app) migrate = Migrate(app, db) import views app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
http://flask-sqlalchemy.pocoo.org/2.3/config/ 看看Flask-SQLAlchemy 怎麼說
http://flask-sqlalchemy.pocoo.org/2.3/config/ 看看Flask-SQLAlchemy 怎麼說
dialect+driver://username:password@host:port/database sqlite:///test.db 比較⼀一下資料庫URI格式跟我們原本的設定
dialect+driver://username:password@host:port/database sqlite:///test.db 比較⼀一下資料庫URI格式跟我們原本的設定
dialect+driver://username:password@host:port/database sqlite:///test.db 比較⼀一下資料庫URI格式跟我們原本的設定
dialect+driver://username:password@host:port/database sqlite:///test.db 比較⼀一下資料庫URI格式跟我們原本的設定
dialect+driver://username:password@host:port/database Your DB URI
app.py 'Your DB URI' app.config['SQLALCHEMY_DATABASE_URI'] = 'Your DB URI' 這樣總該可以跑了了吧‼
app.py 'Your DB URI' app.config['SQLALCHEMY_DATABASE_URI'] = 'Your DB URI' 這樣總該可以跑了了吧‼
但DB URI裡⾯面有包含著帳號密碼 直接把帳號密碼寫在程式碼很危險
app.py 'Your DB URI' app.config['SQLALCHEMY_DATABASE_URI'] = 'Your DB URI' 這樣總該可以跑了了吧‼
但DB URI裡⾯面有包含著帳號密碼 直接把帳號密碼寫在程式碼很危險 這時候我們會⽤用環境變數
讀取環境變數 git checkout 1.0.5 import os from flask import Flask,
request, jsonify, render_template from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate # initialize app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DB_URI') app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', True) db = SQLAlchemy(app) migrate = Migrate(app, db) import views import os app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DB_URI') 從環境變數取出key是DB_URI的value
讀取環境變數 git checkout 1.0.5 import os from flask import Flask,
request, jsonify, render_template from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate # initialize app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DB_URI') app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', True) db = SQLAlchemy(app) migrate = Migrate(app, db) import views import os app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DB_URI') 從環境變數取出key是DB_URI的value
• macOS / Linux • Windows 設定環境變數 -
本地端 export DB_URI='Your DB URI' SET DB_URI='Your DB URI'
設定環境變數 - Heroku (1/2)
設定環境變數 - Heroku (2/2) DB_URI ‘Your DB URI’
安裝Python連接Postgres的函式庫 requirements.txt ... psycopg2==2.7.5 git checkout 1.0.6
再試⼀一次! git add requirements.txt app.py git push heroku master heroku
open
還是沒⽤用
heroku logs --tail 資料表不存在⁉
資料庫設定 創建資料庫 連接資料庫
資料庫設定 創建資料庫 連接資料庫
資料庫設定 創建資料庫 連接資料庫 初始化資料庫
初始化資料庫 >>> from app import db >>> db.create_all() python •
從本地端連接到Heroku上的資料庫 (把環境變數DB_URI設定為Heroku上的資料庫) • 從本地端初始化資料庫
資料庫設定 創建資料庫 連接資料庫 初始化資料庫
資料庫設定 創建資料庫 連接資料庫 初始化資料庫
heroku open
None
終於成功了了
關於如何在Heroku上部署Flask專案的細節 可以參參考Deploying-Flask-To-Heroku
延伸閱讀 • 學習Flask • Flask Official Tutorial • The Flask
Mega Tutorial • Flask Web Development, 2nd • Miguel Grinberg’s Speaker Deck
• 部署 • Deploying-Flask-To-Heroku • 測試 • pytest • pytest-flask
延伸閱讀