# 20240730 Python 今日內容: - Flask - WSGI - Route - Template - Form (GET/POST) - CRUD 之 C ## 課外讀物 - JavaScript wat https://www.destroyallsoftware.com/talks/wat ## Flask - 在虛擬環境中安裝Flask - 建立新的資料夾 - 建立新的虛擬環境 ```py python -m venv .venv ``` - 進入虛擬環境 ```py cd .\.venv\Scripts\ .\Activate.ps1 ``` - 使用pip安裝Flask ```py pip install Flask ``` - 建立一個最小的應用程式(模組) ```py # hello.py from flask import Flask app = Flask(__name__) @app.route("/") def hello_world(): return "<p>Hello Flask</p>" ``` - 執行應用程式(模組) - 使用flask指令 ```py flask --app hello run --reload --debug      ``` 1. hello: 檔案(模組)名稱 2. --reload: 檔案修改後自動重新載入程式 指令後面要加上--reload ![image](https://hackmd.io/_uploads/rkGpeSLK0.png) 3. --debug: 進入除錯模式 指令後面要加上--debug ![image](https://hackmd.io/_uploads/BkVxbS8tC.png) 要在程式碼內加上 ```py def lottery_generator(n): from random import sample numbers = sample(range(1, 44), 6) breakpoint() return numbers ``` ![image](https://hackmd.io/_uploads/rJ2rXH8KC.png) - 使用python指令 ```py # 如果是使用python hello.py,直接啟動就會進入 # 直接啟動__name__ == __main__ if __name__ == "__main__": # 網址後面加的port號9527 => 127.0.0.1:9527 # debug=True 表示要啟動debug模式 app.run(port=9527, debug=True) ``` ![image](https://hackmd.io/_uploads/Hy4OOHUFA.png) ![image](https://hackmd.io/_uploads/Sy_0_SUY0.png) - 如果應用程式(模組)的檔案名稱是取名為 `app.py` 或 `wsgi.py` 可以省略不寫 ```py flask --app app run --reload --debug => 可簡寫為: flask run --reload --debug ``` ## Python內建 WSGI 主要是做規格轉換(轉接頭),也可以說是一種API - WSGI: Web Server Gateway Interface - ASGI: Asynchronous Server Gateway Interface (非同步) ``` user —→ [ web server —→ Python, PHP —→ database ] web server: Nginx, Apache (業界知名網站伺服器,類似LIVE SERVER,只能吃靜態檔案) (index.html) Nginx, Apache: 外掛模組 mod_php, mod_py.. 伺服器才看得懂 Nginx <-- 介面 <-- Flask (微服務) 沒有資料庫設定要自己架設 <-- FastAPI (微服務) <-- Django (較完整功能) <-- Python ``` ## debug mode 查看錯誤需要PIN碼 ![image](https://hackmd.io/_uploads/BJGZ9IUtR.png) ![image](https://hackmd.io/_uploads/r1MGcIUtA.png) ![image](https://hackmd.io/_uploads/B1CmcLUF0.png) ![image](https://hackmd.io/_uploads/H1hHCU8KC.png) ## Route ```PY from flask import Flask, render_template, request, redirect app = Flask(__name__) # 關鍵引數template_folder="templates"是預設資料夾名稱 # app = Flask(__name__, template_folder="templates") # 關鍵引數static_folder="static"是預設資料夾名稱 # 除了html之外 img、css、js 都是靜態檔案需要放到static資料夾內 # app = Flask(__name__, static_folder="static") <!-- 網頁起始路徑是由"/"開始 --> @app.route("/") def hello_world(): # 如果使用render_template渲染頁面的話,需要載入render_template模組 return render_template("pages/index.html", hello=123) # breakpoint() # python debugger 設定中斷點 不要部屬上線會壞 # return "<p> Hello Flask </p>" ``` ==@app.route("/")== 這個裝飾器將根 URL 路徑 / 與 hello_world 函數關聯起來。當用戶訪問根 URL 時(例如 http://localhost:5000/ ),hello_world 函數將被調用。 - 如果使用`render_template()`渲染頁面的話,需要import `render_template` 模組 ```py from flask import Flask, render_template, request, redirect ``` ==request== 匯入request套件後,如果Flask想要接收以 `GET / POST` 方式傳送的參數,就需要在method設定以`GET / POST`方式傳送 ==redirect== 表示轉址的意思 - 需要建立一資料夾名稱為: templates ![image](https://hackmd.io/_uploads/S1EzJDItR.png) ![image](https://hackmd.io/_uploads/H11JFXDYR.png) Flask類別初始定義 ![image](https://hackmd.io/_uploads/SJ4d0mvYR.png) `Flask(__name__, template_folder="templates")` => 預設是需要在目錄結構中,新增一==templates==資料夾 => ==templates==的資料夾名稱可以自行定義 ![image](https://hackmd.io/_uploads/H1Det7vtC.png) `Flask(__name__, static_folder="static")` => 預設是需要在目錄結構中,新增一==static==資料夾 => ==static==的資料夾名稱可以自行定義 => 除了html檔案之外,其它像是 js、css、image 都算是static file,都要放到==static==資料夾內 ![image](https://hackmd.io/_uploads/H1eUtQwK0.png) ## Template (jinja) - 模板引擎 https://jinja.palletsprojects.com/en/3.1.x/ - 將數據插入到 HTML 模板中,以動態生成 HTML 頁面 ```py # hello.py from flask import Flask, render_template app = Flask(__name__) @app.route("/") def hello_world(): return render_template("pages/index.html", hello=123) @app.route("/about") def about(): return render_template("pages/about.html") @app.route("/lottery") def lottery(): # 將產生的亂數序列指定到lottery_numbers lottery_numbers = lottery_generator(6) # 將lottery_numbers變數指定到numbers可以把數據渲染到HTML return render_template("pages/lottery.jinja", numbers=lottery_numbers) def lottery_generator(n): from random import sample # 載入random套件中的sample模組,隨機產生六個1~43的亂數 numbers = sample(range(1, 44), 6) return numbers ``` ```py # templates/layout/normal.jinja <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{% block title %} Flask {% endblock %}</title> </head> <body> # 使用jinja語法在共同版面挖一個區塊 # 對應到templates/pages/lottery.jinja 檔案中的區塊內容 {% block content %} {% endblock %} </body> </html> ``` ```py # templates/pages/lottery.jinja # 建立一基礎模板normal.jinja,並且在其它檔案中擴展 {% extends "layout/normal.jinja" %} # 使用共同基礎模板 {% block content %} <h1>樂透號碼</h1> # 使用jinja提供的for..in方式提取list中的元素 {% for n in numbers %} {{ n }} {% endfor %} {% endblock %} {% block title %} Hello Flask {% endblock %} ``` ![image](https://hackmd.io/_uploads/S1lFVdLtA.png) ## Form (GET/POST) ```py {% extends "layout/normal.jinja" %} {% block content %} <h1>文章列表</h1> # 指定當表單被提交時的請求方法POST和目標 /posts <form method="POST" action="/posts"> <label>標題</label> # 後端只會抓到name屬性 # id class 是沒用得 是前端在看 <input type="text" name="aaa" /> <input type="text" name="bbb" /> # 多選 # <input type="checkbox" name="ccc" value="1" /> # <input type="checkbox" name="ccc" value="2" /> # 送出表單 <button>送出</button> </form> {% endblock %} ``` ## CRUD 之 C - 建立表單之過程 - 當表單點選送出之後,會回到hello.py中 `if request.method == "POST": ` 判斷 - 寫入資料庫行為稱之為create ## 今日範例 - 新增文章 目錄架構 ![image](https://hackmd.io/_uploads/ByvTuuUK0.png) hello.py 主要執行程式 ```py from flask import Flask, render_template, request, redirect app = Flask(__name__) # app = Flask(__name__, template_folder="templates") # app = Flask(__name__, static_folder="static") # flask --app hello run # hello就是檔名,是一個模組 # flask --app app(wsgi) run => flask run # 檔名如果是 app or wsgi就可以省略不寫 @app.route("/") def hello_world(): return render_template("pages/hello.html", hello=123) # breakpoint() # python debugger 設定中斷點 不要部屬上線會壞 # return "<p> Hello Flask </p>" @app.route("/about") def about(): return render_template("pages/about.html") # return "<p> 關於我們 </p>" @app.route("/lottery") def lottery(): lottery_numbers = lottery_generator(6) return render_template("pages/lottery.jinja", numbers=lottery_numbers) # ========= 文章相關 ================= # # 允許接收 get post方法 # 沒寫methods 預設就是get methods=["GET"] # 同一個函數處理不同的方法 # 現在是post (get) 重新整理還是post (get) @app.route("/posts", methods=["GET", "POST"]) def posts(): if request.method == "POST": # 寫入資料庫 # 轉址 到 posts頁面 # http 302 重新定向原本的POST改為GET方法, # 這裡是不會在傳到伺服器,是瀏覽器自己的行為 return redirect("/posts") # 因為重新導向,方法改為 GET,所以最後會跑到這裡 return render_template("posts/index.html") # 最後回到這裡 # 新增列表 @app.route("/posts/new") def new_posts(): return render_template("posts/new.html") def lottery_generator(n): from random import sample numbers = sample(range(1, 44), 6) return numbers # 如果是使用python hello.py,如果是直接啟動就會進入 # 直接啟動__name_ == __main__ # __main == __main 確認頁面是否有單獨執行 如果匯入到其他頁面則不會執行 if __name__ == "__main__": app.run(port=9527, debug=True) ``` ```py # templates/posts/index.html {% extends "layout/normal.jinja" %} {% block content %} <h1>文章列表</h1> # 這裡送出的方法是GET方法 # 會轉跳到posts資料夾下的new.html頁面 <a href="/posts/new">新增文章</a> {% endblock %} ``` ![image](https://hackmd.io/_uploads/rJZh6dLFA.png) ```py # templates/posts/new.html {% extends "layout/normal.jinja" %} {% block content %} <h1>文章列表</h1> # 指定當表單被提交時的請求方法POST和目標 /posts <form method="POST" action="/posts"> <label>標題</label> # 後端只會使用name屬性 # id class 只有前端在使用 <input type="text" name="aaa" /> <input type="text" name="bbb" /> # 多選 # <input type="checkbox" name="ccc" value="1" /> # <input type="checkbox" name="ccc" value="2" /> # 送出表單 <button>送出</button> </form> {% endblock %} ``` ![image](https://hackmd.io/_uploads/HkNc6_LKA.png) ==如何使用debug來檢視資料== 在要檢視的地方加上中斷點 `breakpoint()` ``` @app.route("/posts", methods=["GET", "POST"]) def posts(): if request.method == "POST": # 寫入資料庫 breakpoint() <============== 設定中斷點,表單送出後會到這 return redirect("/posts") # 因為重新導向,方法改為 GET,所以最後會跑到這裡 return render_template("posts/index.html") # 最後回到這裡 ``` ![image](https://hackmd.io/_uploads/B1rmkKLYR.png) 根據上圖中的指令說明 ==p== 指的是print ==request.form== 當用戶提交表單時,表單中的數據會以字典的形式存儲在 request.form 中。你可以使用這個屬性來訪問和處理提交的表單數據。 ```py # templates/layout/normal.jinja <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{% block title %} Flask {% endblock %}</title> </head> <body> {# <nav> <ul> <li><a href="/">首頁</a></li> <li><a href="/lottery">hahaa</a></li> <li><a href="/about">關於我</a></li> </ul> </nav> #} # 載入shared目錄下的外部檔案navbar的html語法,渲染在頁面上 {% include 'shared/navbar.jinja' %} {% block content %} {% endblock %} </body> </html> ``` ```py # templates/shared/navbar.jinja <nav> <ul> <li><a href="/">首頁</a></li> <li><a href="/lottery">hahaa</a></li> <li><a href="/about">關於我</a></li> </ul> </nav> ``` ![image](https://hackmd.io/_uploads/S1PpbFLKR.png)