# 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

3. --debug: 進入除錯模式
指令後面要加上--debug

要在程式碼內加上
```py
def lottery_generator(n):
from random import sample
numbers = sample(range(1, 44), 6)
breakpoint()
return numbers
```

- 使用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)
```


- 如果應用程式(模組)的檔案名稱是取名為 `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碼




## 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


Flask類別初始定義

`Flask(__name__, template_folder="templates")`
=> 預設是需要在目錄結構中,新增一==templates==資料夾
=> ==templates==的資料夾名稱可以自行定義

`Flask(__name__, static_folder="static")`
=> 預設是需要在目錄結構中,新增一==static==資料夾
=> ==static==的資料夾名稱可以自行定義
=> 除了html檔案之外,其它像是 js、css、image 都算是static file,都要放到==static==資料夾內

## 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 %}
```

## 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
## 今日範例 - 新增文章
目錄架構

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 %}
```

```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 %}
```

==如何使用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") # 最後回到這裡
```

根據上圖中的指令說明
==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>
```
