Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[CS Foundation] Web - 7 - CRUD in Flask

x-village
August 16, 2018
92

[CS Foundation] Web - 7 - CRUD in Flask

x-village

August 16, 2018
Tweet

Transcript

  1. Exercise 1- Discussion • Nagging Money 碎碎念念記帳 • MOZE 2.0

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

    Image: https://www.mozeapp.com/zh-tw#features • AndroMoney Image: https://web.andromoney.com
  3. Exercise 2 - Discussion • 新增帳務紀錄 (Create) • 查詢帳務紀錄 (Read)

    • 更更新帳務紀錄 (Update) • 刪除帳務紀錄 (Delete)
  4. 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模型
  5. 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模型 資料表名稱
  6. 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模型 資料欄欄位 (欄欄位名稱[欄欄位型別])
  7. Student id [INTEGER] name [STRING(120)] Grade student_id [INTEGER] id [INTEGER]

    score [TEXT] subject [TEXT] {0,1} 0..N 補充 - ER diagram (cont.)
  8. 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的資料
  9. 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的資料
  10. 補充 - 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
  11. 補充 - 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
  12. 補充 - 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
  13. 補充 - 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
  14. 補充 - 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
  15. Exercise 4 - Discussion Record id [INTEGER] name [STRING(120)] cost

    [INTEGER] 我們回到上⼀一堂課結束前,最後的設計了了!
  16. Clone Sample Code From GitHub git clone https://github.com/x-village/web- acccounting-example cd

    web-acccounting-example git checkout 0.1 本次課程的範例例程式碼,都在GitHub上了了
 ⾃自⼰己去找吧(???
  17. 使⽤用說明 • 確實使⽤用git clone • 不要在GitHub上⽤用下載的 • 使⽤用0.1版 (git checkout

    0.1) • 跟著接下來來的教學⼀一步⼀一步做修改 • 除了了跑不動以外,遇到的錯誤都可能是正常的 • 但可以先思考為什什麼會這樣 • 要怎麼樣才能修正
  18. 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)
  19. 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
  20. 專案架構 - Version 0.1.1 . #"" README.md !"" app.py git

    checkout 0.1.1 為專案加入說明⽂文件
  21. File "/home/leew/web-acccounting-example/app.py", line 2, in <module> from flask_sqlalchemy import SQLAlchemy

    ModuleNotFoundError: No module named 'flask_sqlalchemy' 好像少了了⼀一些需要的函式庫
  22. 專案架構 - Version 0.1.2 . #"" README.md #"" app.py !""

    requirements.txt 為專案加入requirements.txt 指定有⽤用到的函式庫
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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加入資料庫
  31. 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
  32. @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
  33. @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傳入資訊
  34. app.py (CRUD) - (1/7) @app.route("/record", methods=['GET']) def get_record(): ... ...

    get_record? get_records? 這樣的命名是不是有可能造成誤解呢
  35. 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
  36. 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
  37. 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
  38. 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資料
  39. 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格式的字串串
  40. 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
  41. 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當然幫我們處理理好了了
  42. 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當然幫我們處理理好了了
  43. 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
  44. 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不⼀一樣
  45. 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不⼀一樣
  46. 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不⼀一樣
  47. @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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. @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)
  54. @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
  55. @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
  56. @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?
  57. @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?
  58. 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
  59. 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就算成功)
  60. 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/
  61. 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/
  62. Home Page @app.route("/") def index(): return ''' <html> ... <title>x-village<title>

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

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

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

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

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

    ... <title>x-village<title> ... <html> ''' 把整份html的⽂文字寫到 另⼀一份html檔案中 git checkout 0.3.1
  68. 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
  69. . #"" README.md #"" app.py #"" requirements.txt #"" templates $

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

    !"" index.html !"" test.db 專案架構 - Version 0.3 git checkout 0.3 Flask預設會在名為templates 的資料夾下找html檔
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. 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
  77. @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們嗎
  78. <!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
  79. Exercise 8 1. 完成前⾯面的程式碼,開啟flask server (flask run) 2. 開啟瀏覽器到http://127.0.0.1:5000 3.

    新增⼀一筆name是breakfast, cost是70的資料 4. 重新整理理,確認新增的資料是否出現在⾴頁⾯面最下⾯面
  80. @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
  81. @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!
  82. @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
  83. ⾃自動化測試 def add(x, y): return x + y def test_add():

    assert add(1, 1) == 2 • 預先將每⼀一個功能的輸入和預期的結果列列出來來 • 透過程式比對輸入後的結果和預期結果 • e.g.,
  84. 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
  85. 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
  86. 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
  87. 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
  88. 專案架構 - 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
  89. 專案架構 - 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
  90. 專案架構 - 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
  91. cd "Your project" heroku login git init heroku git:remote -a

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

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

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

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

    "Your Heroku app name” git push heroku master heroku open 透過CMD/終端機發佈專案 在瀏覽器開啟剛剛發佈的專案
  97. 回到我們原本連資料庫的地⽅方 (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'
  98. app.py 'Your DB URI' app.config['SQLALCHEMY_DATABASE_URI'] = 'Your DB URI' 這樣總該可以跑了了吧‼

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

    但DB URI裡⾯面有包含著帳號密碼 直接把帳號密碼寫在程式碼很危險 這時候我們會⽤用環境變數
  100. 讀取環境變數 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
  101. 讀取環境變數 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
  102. • macOS / Linux
 
 
 • Windows 設定環境變數 -

    本地端 export DB_URI='Your DB URI' SET DB_URI='Your DB URI'
  103. 初始化資料庫 >>> from app import db >>> db.create_all() python •

    從本地端連接到Heroku上的資料庫
 (把環境變數DB_URI設定為Heroku上的資料庫) • 從本地端初始化資料庫
  104. 延伸閱讀 • 學習Flask • Flask Official Tutorial • The Flask

    Mega Tutorial • Flask Web Development, 2nd • Miguel Grinberg’s Speaker Deck