Slide 1

Slide 1 text

CRUD in Flask X-Village 網⾴頁開發課程 By Wei Lee

Slide 2

Slide 2 text

⼼心理理建設 • 你可能不會在這堂課學到很多Flask的細節 • 那會學到什什麼呢? • 從頭構思⼀一個後端 • 怎麼逐步修改後端

Slide 3

Slide 3 text

• 你可能會在這次的教學中 • 看不懂程式碼在做什什麼 • 發現程式的執⾏行行⾏行行為不如預期 • 這都是正常的 • 這次的教學是帶著⼤大家從不好的甚⾄至錯誤的程式碼,⼀一步 ⼀一步理理解和修改 ⼼心理理建設

Slide 4

Slide 4 text

什什麼是CRUD?

Slide 5

Slide 5 text

Image: http://lmgtfy.com/?q=CRUD

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Image: https://zh.wikipedia.org/zh-tw/資料操縱語⾔言

Slide 9

Slide 9 text

Image: https://zh.wikipedia.org/zh-tw/資料操縱語⾔言

Slide 10

Slide 10 text

CRUD • Create (新增) • Read (查詢) • Update (修改) • Delete (刪除)

Slide 11

Slide 11 text

CRUD create (新增) INSERT INTO address_book (name, email) VALUES ('Tommy', '[email protected]');

Slide 12

Slide 12 text

CRUD create (新增) INSERT INTO address_book (name, email) VALUES ('Tommy', '[email protected]');

Slide 13

Slide 13 text

CRUD read (查詢) SELECT * FROM address_book WHERE name='Tommy';

Slide 14

Slide 14 text

CRUD read (查詢) SELECT * FROM address_book WHERE name='Tommy';

Slide 15

Slide 15 text

CRUD update (修改) UPDATE address_book SET name='Tom', email='[email protected]' WHERE name=‘Tommy';

Slide 16

Slide 16 text

CRUD update (修改) UPDATE address_book SET name='Tom', email='[email protected]' WHERE name=‘Tommy';

Slide 17

Slide 17 text

CRUD delete (刪除) DELETE FROM address_book WHERE name='Tom';

Slide 18

Slide 18 text

CRUD delete (刪除) DELETE FROM address_book WHERE name='Tom';

Slide 19

Slide 19 text

CRUD的概念念其實就這樣 接下來來就要進入在Flask實作的部分了了!

Slide 20

Slide 20 text

正式進入實作前 先來來說說這堂課最後要完成什什麼吧 Image: https://pixabay.com/en/goal-setting-goal-dart-target-1955806/

Slide 21

Slide 21 text

記帳程式

Slide 22

Slide 22 text

Exercise 1 你有⽤用過什什麼記帳程式? 找找看現在有哪些記帳程式是比較多⼈人⽤用的?

Slide 23

Slide 23 text

Exercise 1- Discussion • Nagging Money 碎碎念念記帳 Image: https://www.mozeapp.com/zh-tw#features Image: https://web.andromoney.com

Slide 24

Slide 24 text

Exercise 1- Discussion • Nagging Money 碎碎念念記帳 • MOZE 2.0 Image: https://www.mozeapp.com/zh-tw#features Image: https://web.andromoney.com

Slide 25

Slide 25 text

Exercise 1- Discussion • Nagging Money 碎碎念念記帳 • MOZE 2.0 Image: https://www.mozeapp.com/zh-tw#features • AndroMoney Image: https://web.andromoney.com

Slide 26

Slide 26 text

Exercise 2 你覺得⼀一個最基本的記帳程式要什什麼功能?

Slide 27

Slide 27 text

Exercise 2 - Discussion • 新增帳務紀錄 (Create) • 查詢帳務紀錄 (Read) • 更更新帳務紀錄 (Update) • 刪除帳務紀錄 (Delete)

Slide 28

Slide 28 text

Exercise 3 你覺得要達到這樣的⽬目的我們需要什什麼資料表? ?

Slide 29

Slide 29 text

Exercise 3 - Discussion Record

Slide 30

Slide 30 text

Exercise 4 Record這個資料表要有什什麼欄欄位? ?

Slide 31

Slide 31 text

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模型

Slide 32

Slide 32 text

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模型 資料表名稱

Slide 33

Slide 33 text

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模型 資料欄欄位 (欄欄位名稱[欄欄位型別])

Slide 34

Slide 34 text

Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER] score [TEXT] subject [TEXT] {0,1} 0..N 補充 - ER diagram (cont.)

Slide 35

Slide 35 text

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的資料

Slide 36

Slide 36 text

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的資料

Slide 37

Slide 37 text

補充 - 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

Slide 38

Slide 38 text

補充 - 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

Slide 39

Slide 39 text

補充 - 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

Slide 40

Slide 40 text

補充 - 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

Slide 41

Slide 41 text

補充 - 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

Slide 42

Slide 42 text

Exercise 4 Record這個資料表要有什什麼欄欄位? ?

Slide 43

Slide 43 text

Exercise 4 - Discussion Record cost [INTEGER]

Slide 44

Slide 44 text

Exercise 4 - Discussion Record cost [INTEGER] 如果我們新增了了兩兩筆cost都是100的Record, 我們要怎麼分辨他們?

Slide 45

Slide 45 text

Exercise 4 - Discussion Record id [INTEGER] cost [INTEGER]

Slide 46

Slide 46 text

Exercise 4 - Discussion Record id [INTEGER] cost [INTEGER] 只有cost,之後使⽤用者會知道這筆帳花在什什麼上嗎?

Slide 47

Slide 47 text

Exercise 4 - Discussion Record id [INTEGER] name [STRING(120)] cost [INTEGER]

Slide 48

Slide 48 text

Exercise 4 - Discussion Record id [INTEGER] name [STRING(120)] cost [INTEGER] 我們回到上⼀一堂課結束前,最後的設計了了!

Slide 49

Slide 49 text

到⽬目前為⽌止,我們⼀一⾏行行程式碼都還沒動

Slide 50

Slide 50 text

我想說的是... Image: Photoed by LeeW in Pycon TW 2017

Slide 51

Slide 51 text

我想說的是... Image: Photoed by LeeW in Pycon TW 2017

Slide 52

Slide 52 text

我想說的是... Image: Photoed by LeeW in Pycon TW 2017

Slide 53

Slide 53 text

先想好,再寫code

Slide 54

Slide 54 text

Clone Sample Code From GitHub git clone https://github.com/x-village/web- acccounting-example cd web-acccounting-example git checkout 0.1 本次課程的範例例程式碼,都在GitHub上了了
 ⾃自⼰己去找吧(???

Slide 55

Slide 55 text

使⽤用說明 • 確實使⽤用git clone • 不要在GitHub上⽤用下載的 • 使⽤用0.1版 (git checkout 0.1) • 跟著接下來來的教學⼀一步⼀一步做修改 • 除了了跑不動以外,遇到的錯誤都可能是正常的 • 但可以先思考為什什麼會這樣 • 要怎麼樣才能修正

Slide 56

Slide 56 text

專案架構 - Version 0.1 . !"" app.py

Slide 57

Slide 57 text

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)

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

專案架構 - Version 0.1 現在專案只有⼀一個app.py 要怎麼讓剛接觸的⼈人快速上⼿手呢?

Slide 60

Slide 60 text

專案架構 - Version 0.1.1 . #"" README.md !"" app.py git checkout 0.1.1 為專案加入說明⽂文件

Slide 61

Slide 61 text

README

Slide 62

Slide 62 text

File "/home/leew/web-acccounting-example/app.py", line 2, in from flask_sqlalchemy import SQLAlchemy ModuleNotFoundError: No module named 'flask_sqlalchemy'

Slide 63

Slide 63 text

File "/home/leew/web-acccounting-example/app.py", line 2, in from flask_sqlalchemy import SQLAlchemy ModuleNotFoundError: No module named 'flask_sqlalchemy' 好像少了了⼀一些需要的函式庫

Slide 64

Slide 64 text

專案架構 - Version 0.1.2 . #"" README.md #"" app.py !"" requirements.txt 為專案加入requirements.txt 指定有⽤用到的函式庫

Slide 65

Slide 65 text

requirements.txt Flask==1.0.2 Flask-Migrate==2.2.1 Flask-SQLAlchemy==2.3.2 git checkout 0.1.2

Slide 66

Slide 66 text

pip install -r requirements.txt 於是新加入的⼈人知道我們需要哪些函式庫了了!

Slide 67

Slide 67 text

CRUD in Web Service Screenshot: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

Slide 68

Slide 68 text

CRUD in Web Service Screenshot: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

Slide 69

Slide 69 text

CRUD in Web Service Screenshot: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Execute

Slide 75

Slide 75 text

Exercise 5 1. 完成前⾯面的程式碼,開啟flask server
 (flask run) 2. 開啟瀏覽器到http://127.0.0.1:5000/record 3. 確認出現以下畫⾯面

Slide 76

Slide 76 text

剛剛做了了什什麼呢

Slide 77

Slide 77 text

Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record 剛剛做了了什什麼呢

Slide 78

Slide 78 text

Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record Method 剛剛做了了什什麼呢

Slide 79

Slide 79 text

Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record Method 主機位址 剛剛做了了什什麼呢

Slide 80

Slide 80 text

Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record Method 主機位址 Port 剛剛做了了什什麼呢

Slide 81

Slide 81 text

Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record Method 主機位址 Port Path 剛剛做了了什什麼呢

Slide 82

Slide 82 text

Image: http://deadspace.wikia.com/wiki/File:Chrome_icon.svg GET: 127.0.0.1:5000/record 因為現在還沒有東⻄西 所以是空的list Method 主機位址 Port Path 剛剛做了了什什麼呢

Slide 83

Slide 83 text

那POST, PUT, DELETE呢? Image: https://www.getpostman.com/

Slide 84

Slide 84 text

那POST, PUT, DELETE呢? Image: https://www.getpostman.com/

Slide 85

Slide 85 text

Postman

Slide 86

Slide 86 text

Postman

Slide 87

Slide 87 text

Postman

Slide 88

Slide 88 text

Postman

Slide 89

Slide 89 text

Exercise 6 1. 下載Postman(https://www.getpostman.com/)
 2. 對127.0.0.1:5000/record測試POST, GET, PUT, DELETE四種已經實作的⽅方法

Slide 90

Slide 90 text

現在回頭來來看程式碼在做什什麼吧

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Server

Slide 93

Slide 93 text

Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Request Server

Slide 94

Slide 94 text

Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Request Response Server

Slide 95

Slide 95 text

Router (cont.)

Slide 96

Slide 96 text

Router (cont.) Image: http://pngimg.com/download/25911 http://flask.pocoo.org/static/logo/flask.png Client Request Response / Server http://flask.pocoo.org/

Slide 97

Slide 97 text

Router (cont.)

Slide 98

Slide 98 text

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/

Slide 99

Slide 99 text

Router (cont.)

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

再回到程式碼

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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加入資料庫

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

明明就是要新增 為什什麼完全沒看到接受使⽤用者輸入的地⽅方呢? Image: https://pxhere.com/en/photo/1403204

Slide 108

Slide 108 text

@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

Slide 109

Slide 109 text

@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傳入資訊

Slide 110

Slide 110 text

app.py (CRUD) - (3/4)

Slide 111

Slide 111 text

app.py (CRUD) - (3/4)

Slide 112

Slide 112 text

app.py (CRUD) - (3/4)

Slide 113

Slide 113 text

app.py (CRUD) - (3/4)

Slide 114

Slide 114 text

app.py (CRUD) - (4/4)

Slide 115

Slide 115 text

app.py (CRUD) - (4/4)

Slide 116

Slide 116 text

app.py (CRUD) - (4/4)

Slide 117

Slide 117 text

app.py (CRUD) - (1/7) @app.route("/record", methods=['GET']) def get_record(): ... ... get_record? get_records?

Slide 118

Slide 118 text

app.py (CRUD) - (1/7) @app.route("/record", methods=['GET']) def get_record(): ... ... get_record? get_records? 這樣的命名是不是有可能造成誤解呢

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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資料

Slide 123

Slide 123 text

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格式的字串串

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

如果不會這種寫法 會不會造成程式碼寫不出來來

Slide 126

Slide 126 text

如果不會這種寫法 會不會造成程式碼寫不出來來 並不會!

Slide 127

Slide 127 text

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當然幫我們處理理好了了

Slide 128

Slide 128 text

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當然幫我們處理理好了了

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

app.py (CRUD) - (6/7) 如果我們想要除了了get_records 還需要get_record呢?

Slide 131

Slide 131 text

app.py (CRUD) - (7/7) @app.route( ‘/record/', 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不⼀一樣

Slide 132

Slide 132 text

app.py (CRUD) - (7/7) @app.route( ‘/record/', 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不⼀一樣

Slide 133

Slide 133 text

app.py (CRUD) - (7/7) @app.route( ‘/record/', 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不⼀一樣

Slide 134

Slide 134 text

@app.route("/record", methods=["PUT"]) def update_record(): records = Record.query.filter_by(name=‘breakfast') return 'Update Succeeded', 200 app.py (CRUD) - (1/2)

Slide 135

Slide 135 text

@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

Slide 136

Slide 136 text

app.py (CRUD) - (2/2) @app.route( ‘/record/', 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

Slide 137

Slide 137 text

app.py (CRUD) - (2/2) @app.route( ‘/record/', 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

Slide 138

Slide 138 text

app.py (CRUD) - (2/2) @app.route( ‘/record/', 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

Slide 139

Slide 139 text

app.py (CRUD) - (2/2) @app.route( ‘/record/', 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

Slide 140

Slide 140 text

app.py (CRUD) - (2/2) @app.route( ‘/record/', 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

Slide 141

Slide 141 text

@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)

Slide 142

Slide 142 text

@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

Slide 143

Slide 143 text

@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

Slide 144

Slide 144 text

@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?

Slide 145

Slide 145 text

@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?

Slide 146

Slide 146 text

def delete_record(record_id): db.session.delete(record) db.session.commit() return 'Delete Succeeded', 200 @app.route("/record/", methods=["DELETE"]) record = Record.query.filter_by(id=record_id).first() app.py (CRUD) - (2/2) git checkout 0.2.7

Slide 147

Slide 147 text

def delete_record(record_id): db.session.delete(record) db.session.commit() return 'Delete Succeeded', 200 @app.route("/record/", methods=["DELETE"]) record = Record.query.filter_by(id=record_id).first() app.py (CRUD) - (2/2) git checkout 0.2.7 從資料庫找出符合條件的 第⼀一筆record

Slide 148

Slide 148 text

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就算成功)

Slide 149

Slide 149 text

終於完成CRUD了了!

Slide 150

Slide 150 text

但只讓使⽤用者看到這樣的畫⾯面 好像也不太對

Slide 151

Slide 151 text

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/

Slide 152

Slide 152 text

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/

Slide 153

Slide 153 text

# views @app.route("/") def hello(): return "Hello World!" 為我們的記帳程式做⼀一個⾸首⾴頁

Slide 154

Slide 154 text

# views @app.route("/") def hello(): return "Hello World!" 為我們的記帳程式做⼀一個⾸首⾴頁

Slide 155

Slide 155 text

Home Page @app.route("/") def index(): return ''' ... x-village<title> ... <html> ''' git checkout 0.3

Slide 156

Slide 156 text

Home Page @app.route("/") def index(): return ''' ... x-village<title> ... <html> ''' 回傳整個html 
 第⼆二個參參數如果沒提供狀狀態碼 則預設會回傳200 git checkout 0.3

Slide 157

Slide 157 text

Home Page @app.route("/") def index(): return ''' ... x-village<title> ... <html> ''' 回傳整個html 
 第⼆二個參參數如果沒提供狀狀態碼 則預設會回傳200 如果html很長很長,app.py就會變得⼜又臭⼜又長 git checkout 0.3

Slide 158

Slide 158 text

Home Page @app.route("/") def index(): return ''' ... x-village<title> ... <html> ''' 回傳整個html 
 第⼆二個參參數如果沒提供狀狀態碼 則預設會回傳200 如果html很長很長,app.py就會變得⼜又臭⼜又長 Flask當然也幫你想到了了! git checkout 0.3

Slide 159

Slide 159 text

Home Page - Template @app.route("/") def index(): return ''' ... x-village<title> ... <html> ''' git checkout 0.3.1

Slide 160

Slide 160 text

Home Page - Template @app.route("/") def index(): return ''' ... x-village<title> ... <html> ''' git checkout 0.3.1

Slide 161

Slide 161 text

Home Page - Template @app.route("/") def index(): return ''' ... x-village<title> ... <html> ''' 把整份html的⽂文字寫到 另⼀一份html檔案中 git checkout 0.3.1

Slide 162

Slide 162 text

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 ''' ... x-village<title> ... <html> ''' 把整份html的⽂文字寫到 另⼀一份html檔案中 git checkout 0.3.1

Slide 163

Slide 163 text

#"" README.md #"" app.py #"" index.html !"" requirements.txt 為什什麼flask找不到我的index.html

Slide 164

Slide 164 text

. #"" README.md #"" app.py #"" requirements.txt #"" templates $ !"" index.html !"" test.db 專案架構 - Version 0.3 git checkout 0.3

Slide 165

Slide 165 text

. #"" README.md #"" app.py #"" requirements.txt #"" templates $ !"" index.html !"" test.db 專案架構 - Version 0.3 git checkout 0.3 Flask預設會在名為templates 的資料夾下找html檔

Slide 166

Slide 166 text

Designed by Po-Chun Lu Download Here https://raw.githubusercontent.com/x-village/web-acccounting-example/0.3/templates/index.html 因為這堂課教的是後端,前端的程式碼就先給你了了

Slide 167

Slide 167 text

如果只是回傳整份html 好像看不太出有後端的好處

Slide 168

Slide 168 text

Template System • 先寫好html的模板,再根據不同的狀狀況填入資料 • Flask 預設使⽤用 Jinja2 Image: https://pagingsupermom.com/alphabet-fill/ http://jinja.pocoo.org/docs/2.10/

Slide 169

Slide 169 text

Template System • 先寫好html的模板,再根據不同的狀狀況填入資料 • Flask 預設使⽤用 Jinja2 Image: https://pagingsupermom.com/alphabet-fill/ http://jinja.pocoo.org/docs/2.10/

Slide 170

Slide 170 text

Template System • 先寫好html的模板,再根據不同的狀狀況填入資料 • Flask 預設使⽤用 Jinja2 Image: https://pagingsupermom.com/alphabet-fill/ http://jinja.pocoo.org/docs/2.10/ B

Slide 171

Slide 171 text

Template System • 先寫好html的模板,再根據不同的狀狀況填入資料 • Flask 預設使⽤用 Jinja2 Image: https://pagingsupermom.com/alphabet-fill/ http://jinja.pocoo.org/docs/2.10/ B E

Slide 172

Slide 172 text

index.html ... Accounting ... ... 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html

Slide 173

Slide 173 text

index.html ... Accounting ... ... 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html

Slide 174

Slide 174 text

index.html (new) ... {{ title }} ... ... index.html ... Accounting ... ... 根據後端的資料,回傳不同的html Download Here https://github.com/x-village/web-acccounting-example/blob/0.3.1/templates/index.html

Slide 175

Slide 175 text

app.py index.html (new) ... {{ title }} ... ... index.html ... 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

Slide 176

Slide 176 text

app.py index.html (new) ... {{ title }} ... ... index.html ... Accounting ... ... , 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

Slide 177

Slide 177 text

app.py index.html (new) ... {{ title }} ... ... index.html ... Accounting ... ... , 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

Slide 178

Slide 178 text

@app.route("/record", methods=['POST']) def add_record(): ... @app.route("/record", methods=['GET']) def get_records(): ... @app.route('/record/', methods=['GET']) def get_record(record_id): ... @app.route('/record/', methods=['PUT']) def update_record(record_id): ... @app.route("/record/", methods=["DELETE"]) def delete_record(record_id): ... 還記得我們先寫好的record API們嗎

Slide 179

Slide 179 text

... ... ... /** * 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); } }); } ... 透過ajax向後端API取得資料 git checkout 1.0 Download Here https://raw.githubusercontent.com/x-village/web-acccounting-example/1.0/templates/index.html

Slide 180

Slide 180 text

Exercise 8 1. 完成前⾯面的程式碼,開啟flask server (flask run) 2. 開啟瀏覽器到http://127.0.0.1:5000 3. 新增⼀一筆name是breakfast, cost是70的資料 4. 重新整理理,確認新增的資料是否出現在⾴頁⾯面最下⾯面

Slide 181

Slide 181 text

到這裡 ⼀一個基本的網站就完成了了

Slide 182

Slide 182 text

Image: https://www.flickr.com/photos/mathoov/4681491052

Slide 183

Slide 183 text

Image: https://www.flickr.com/photos/mathoov/4681491052

Slide 184

Slide 184 text

Image: https://www.flickr.com/photos/mathoov/4681491052 Actually, there are two more things

Slide 185

Slide 185 text

接下來來的內容比較進階 不會深入,只會帶⼀一點基本的概念念

Slide 186

Slide 186 text

git checkout X.X.X 如果你眼睛比較尖 你也許會發現有些⾴頁⾯面右下⾓角有這個

Slide 187

Slide 187 text

git checkout X.X.X 如果你眼睛比較尖 你也許會發現有些⾴頁⾯面右下⾓角有這個

Slide 188

Slide 188 text

git checkout X.X.X 如果你眼睛比較尖 你也許會發現有些⾴頁⾯面右下⾓角有這個 在我的範例例程式碼 透過這個git指令 就能切換到當下內容的程式碼

Slide 189

Slide 189 text

前⾯面其實還有⼀一個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

Slide 190

Slide 190 text

前⾯面其實還有⼀一個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

Slide 191

Slide 191 text

@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

Slide 192

Slide 192 text

@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!

Slide 193

Slide 193 text

@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

Slide 194

Slide 194 text

測試

Slide 195

Slide 195 text

在正式發佈前,多⽤用Postman測試吧

Slide 196

Slide 196 text

現在只有4個API 如果有400個API要測試呢? Image: https://www.lianan.com.tw/drliananepaper/Article/294-2

Slide 197

Slide 197 text

⾃自動化測試

Slide 198

Slide 198 text

⾃自動化測試 def add(x, y): return x + y def test_add(): assert add(1, 1) == 2 • 預先將每⼀一個功能的輸入和預期的結果列列出來來 • 透過程式比對輸入後的結果和預期結果 • e.g.,

Slide 199

Slide 199 text

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

Slide 200

Slide 200 text

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

Slide 201

Slide 201 text

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

Slide 202

Slide 202 text

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

Slide 203

Slide 203 text

在程式碼亂到無法挽回前 先重整架構吧

Slide 204

Slide 204 text

專案架構 - 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

Slide 205

Slide 205 text

專案架構 - 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

Slide 206

Slide 206 text

專案架構 - 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

Slide 207

Slide 207 text

Appendix

Slide 208

Slide 208 text

Deployment (部署)

Slide 209

Slide 209 text

接著把我們的網站公開給⼤大家吧 flask run --host=0.0.0.0

Slide 210

Slide 210 text

接著把我們的網站公開給⼤大家吧 flask run --host=0.0.0.0 設定成0.0.0.0,外⾯面的⼈人就能訪問到你的網站了了

Slide 211

Slide 211 text

可是這樣...我的電腦就要⼀一直開著了了

Slide 212

Slide 212 text

丟到雲端上吧

Slide 213

Slide 213 text

常⾒見見的平台

Slide 214

Slide 214 text

常⾒見見的平台

Slide 215

Slide 215 text

接下來來會⽤用Heroku作為範例例

Slide 216

Slide 216 text

註冊⼀一個Heroku帳號

Slide 217

Slide 217 text

註冊⼀一個Heroku帳號

Slide 218

Slide 218 text

創立Heroku app (1/3)

Slide 219

Slide 219 text

創立Heroku app (1/3)

Slide 220

Slide 220 text

創立Heroku app (2/3)

Slide 221

Slide 221 text

創立Heroku app - (3/3)

Slide 222

Slide 222 text

https://devcenter.heroku.com/articles/heroku-cli#download-and-install 安裝Heroku CLI

Slide 223

Slide 223 text

cd "Your project" heroku login git init heroku git:remote -a "Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案

Slide 224

Slide 224 text

cd "Your project" heroku login git init heroku git:remote -a "Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 登入Heroku

Slide 225

Slide 225 text

cd "Your project" heroku login git init heroku git:remote -a "Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 如果專案本⾝身就被git追蹤,可以跳過這步

Slide 226

Slide 226 text

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

Slide 227

Slide 227 text

cd "Your project" heroku login git init heroku git:remote -a "Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 發佈到Heroku上

Slide 228

Slide 228 text

cd "Your project" heroku login git init heroku git:remote -a "Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 在瀏覽器開啟剛剛發佈的專案

Slide 229

Slide 229 text

可是沒成功

Slide 230

Slide 230 text

可是沒成功

Slide 231

Slide 231 text

heroku logs --tail 來來看看發⽣生什什麼事 (在CMD/終端機)

Slide 232

Slide 232 text

No content

Slide 233

Slide 233 text

我們還沒跟Heroku説要怎麼跑我們的網站

Slide 234

Slide 234 text

Getting Started on Heroku with Python https://devcenter.heroku.com/articles/getting-started-with-python

Slide 235

Slide 235 text

我們還需要三個檔案 1. requirements.txt
 (或Piplock) 2. Profile
 3. runtime.txt

Slide 236

Slide 236 text

我們還需要三個檔案 1. requirements.txt
 (或Piplock) 2. Profile
 3. runtime.txt 告訴Heroku需要的函式庫

Slide 237

Slide 237 text

我們還需要三個檔案 1. requirements.txt
 (或Piplock) 2. Profile
 3. runtime.txt 告訴Heroku需要的函式庫 告訴Heroku如何執⾏行行你的web

Slide 238

Slide 238 text

我們還需要三個檔案 1. requirements.txt
 (或Piplock) 2. Profile
 3. runtime.txt 告訴Heroku需要的函式庫 告訴Heroku如何執⾏行行你的web 告訴Heroku要⽤用 哪個版本的python

Slide 239

Slide 239 text

requirements.txt ... gunicorn==19.9.0 Procfile web gunicorn app:app runtime.txt python-3.6.5 git checkout 1.0.4

Slide 240

Slide 240 text

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

Slide 241

Slide 241 text

再試⼀一次! git add requirements.txt Procfile runtime.txt git push heroku master heroku open

Slide 242

Slide 242 text

成功了了!

Slide 243

Slide 243 text

可是...按了了Submit為什什麼沒有⽤用

Slide 244

Slide 244 text

heroku logs --tail

Slide 245

Slide 245 text

heroku logs --tail 看起來來是資料庫的問題

Slide 246

Slide 246 text

Heroku 不⽀支援sqlite https://devcenter.heroku.com/categories/data-management

Slide 247

Slide 247 text

資料庫設定

Slide 248

Slide 248 text

資料庫設定 創建資料庫

Slide 249

Slide 249 text

換成使⽤用Postgres吧 https://www.heroku.com/postgres

Slide 250

Slide 250 text

換成使⽤用Postgres吧 https://www.heroku.com/postgres

Slide 251

Slide 251 text

No content

Slide 252

Slide 252 text

No content

Slide 253

Slide 253 text

No content

Slide 254

Slide 254 text

找到你在Heroku創的app

Slide 255

Slide 255 text

我們可以在Heroku上⽤用Postgres了了

Slide 256

Slide 256 text

點⼀一下

Slide 257

Slide 257 text

點⼀一下

Slide 258

Slide 258 text

資料庫的資料都在這

Slide 259

Slide 259 text

資料庫設定 創建資料庫

Slide 260

Slide 260 text

資料庫設定 創建資料庫

Slide 261

Slide 261 text

資料庫設定 創建資料庫 連接資料庫

Slide 262

Slide 262 text

回到我們原本連資料庫的地⽅方 (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'

Slide 263

Slide 263 text

http://flask-sqlalchemy.pocoo.org/2.3/config/ 看看Flask-SQLAlchemy 怎麼說

Slide 264

Slide 264 text

http://flask-sqlalchemy.pocoo.org/2.3/config/ 看看Flask-SQLAlchemy 怎麼說

Slide 265

Slide 265 text

dialect+driver://username:password@host:port/database sqlite:///test.db 比較⼀一下資料庫URI格式跟我們原本的設定

Slide 266

Slide 266 text

dialect+driver://username:password@host:port/database sqlite:///test.db 比較⼀一下資料庫URI格式跟我們原本的設定

Slide 267

Slide 267 text

dialect+driver://username:password@host:port/database sqlite:///test.db 比較⼀一下資料庫URI格式跟我們原本的設定

Slide 268

Slide 268 text

dialect+driver://username:password@host:port/database sqlite:///test.db 比較⼀一下資料庫URI格式跟我們原本的設定

Slide 269

Slide 269 text

dialect+driver://username:password@host:port/database Your DB URI

Slide 270

Slide 270 text

app.py 'Your DB URI' app.config['SQLALCHEMY_DATABASE_URI'] = 'Your DB URI' 這樣總該可以跑了了吧‼

Slide 271

Slide 271 text

app.py 'Your DB URI' app.config['SQLALCHEMY_DATABASE_URI'] = 'Your DB URI' 這樣總該可以跑了了吧‼ 但DB URI裡⾯面有包含著帳號密碼 直接把帳號密碼寫在程式碼很危險

Slide 272

Slide 272 text

app.py 'Your DB URI' app.config['SQLALCHEMY_DATABASE_URI'] = 'Your DB URI' 這樣總該可以跑了了吧‼ 但DB URI裡⾯面有包含著帳號密碼 直接把帳號密碼寫在程式碼很危險 這時候我們會⽤用環境變數

Slide 273

Slide 273 text

讀取環境變數 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

Slide 274

Slide 274 text

讀取環境變數 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

Slide 275

Slide 275 text

• macOS / Linux
 
 
 • Windows 設定環境變數 - 本地端 export DB_URI='Your DB URI' SET DB_URI='Your DB URI'

Slide 276

Slide 276 text

設定環境變數 - Heroku (1/2)

Slide 277

Slide 277 text

設定環境變數 - Heroku (2/2) DB_URI ‘Your DB URI’

Slide 278

Slide 278 text

安裝Python連接Postgres的函式庫 requirements.txt ... psycopg2==2.7.5 git checkout 1.0.6

Slide 279

Slide 279 text

再試⼀一次! git add requirements.txt app.py git push heroku master heroku open

Slide 280

Slide 280 text

還是沒⽤用

Slide 281

Slide 281 text

heroku logs --tail 資料表不存在⁉

Slide 282

Slide 282 text

資料庫設定 創建資料庫 連接資料庫

Slide 283

Slide 283 text

資料庫設定 創建資料庫 連接資料庫

Slide 284

Slide 284 text

資料庫設定 創建資料庫 連接資料庫 初始化資料庫

Slide 285

Slide 285 text

初始化資料庫 >>> from app import db >>> db.create_all() python • 從本地端連接到Heroku上的資料庫
 (把環境變數DB_URI設定為Heroku上的資料庫) • 從本地端初始化資料庫

Slide 286

Slide 286 text

資料庫設定 創建資料庫 連接資料庫 初始化資料庫

Slide 287

Slide 287 text

資料庫設定 創建資料庫 連接資料庫 初始化資料庫

Slide 288

Slide 288 text

heroku open

Slide 289

Slide 289 text

No content

Slide 290

Slide 290 text

終於成功了了

Slide 291

Slide 291 text

關於如何在Heroku上部署Flask專案的細節 可以參參考Deploying-Flask-To-Heroku

Slide 292

Slide 292 text

延伸閱讀 • 學習Flask • Flask Official Tutorial • The Flask Mega Tutorial • Flask Web Development, 2nd • Miguel Grinberg’s Speaker Deck

Slide 293

Slide 293 text

• 部署 • Deploying-Flask-To-Heroku • 測試 • pytest • pytest-flask 延伸閱讀