---
# System prepended metadata

title: 20240730 Python
tags: [Python]

---

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