網頁後端 Flask

講師介紹:

成電CKCSC 38th - 教學 + 網管

翁釩予 MLGnotCOOL

@mlgnotcool

 

自我介紹網站?

(可以看出來為什麼我是教後端不是前端)

講師介紹:

興趣?(除了扣丁之外)

講師介紹:

學術力:

競程、演算法、網頁設計、python

APCS 中高級 5+4、北市賽三等獎 第17名

(對是打競程的我不知道為什麼我來講後端了 :P)

歐對 我打字很快算學術力嗎 :P

後端

是什麼可以吃嗎?

什麼是後端?

就是在伺服器處理前端不能處理的東西

例如:密碼登入之類的東西

什麼是後端?

前端:你看的到的

後端:伺服器處理的

什麼是後端?

沒有後端的網站 -> 靜態網站

有後端的網站 -> 動態網站

處理後端的程式語言有非常多種:

像是 php, javascript, python 之類的

 

而我們今天要教的是python的Flask函式庫,專門來處理網頁後端的

什麼是後端?

來看看一個有後端的網站吧

100%不是我寫的扣

Flask

下載

錐形瓶?

python web framework

flask

Flask

Python

(就看你現在這台電腦有沒有載吧)

 1. 下載python:網站

2. 在vscode確認有沒有選對的版本:

Flask

如何使用Flask

1. 先在vscode建立一個簡單的前端網站

(我相信你們昨天才學過 還記得怎麼作)

Flask

如何使用Flask

2. 下載vscode的python extension

Flask

如何使用Flask

3. 下載Flask package

pip install Flask

開啟terminal

打入這個指令

Flask

小插入 先解釋一個檔案再電腦中的途徑

那要怎麼從一個檔案走到另一個檔案呢?

(讓html知道我們的檔案在哪裡)

假設我們要從index.html走到funny.png:

一些要知道的小知識:

"." - 現在這個資料夾

"/" - 要走的途徑

".." - 返回上一層資料夾

 所以就會是:(拿昨天學得的前端來當例子)

<link href="./images/funny.png" rel="icon">

Flask

如何使用Flask

4. 建立一個python檔案 (.py)

5. 檔案結構(Flask才能運作)

html 放 ./templates

css 放 ./static/css

js 放 ./static/js

圖片放 ./static/images

Flask

如何使用Flask

請大家先做到這步

(接下來就要叫大家打扣了)

 

(也可以先寫個前端idk)

Python

基礎語法

Python

(這裡預設你已經有學過一些程式語言)

變數

a = 10 #整數
b = 20.2 #小數
c = 'string' #字串
d = [10, 30, 40] #陣列

# 對不需用跟他說是什麼類型

Python

if

a = 10
b = 20

if a == 15:
  print("a is 15")
elif b == 20:
  print("b is 20")
else:
  print("neither is true")
  
 #對 python 不用小括號
 #對 python 不用分號
 #對 python 不用大括號 (不下放btw)

Python

while

a = 10

while a < 20:
  print(a)
  a += 1
  
 # 應該蠻直觀的

Python

for

# 重複10次
for i in range(10): #i 會是 0~9
  print(i)
  
for i in range (2, 19, 2): #如果翻譯成c++或js就會是 for (int i=2; i<19; i+=2)
  print(i)
  
arr = ["apple", "banana", "tomato"]
for i in arr: #i 會是 arr 中的元素
  print(i) 
  

Python

function

def add(a, b):
  ans = a+b
  return ans

print(add(1, 2))

#python宣告函式的方法是def
#剩下就和其他語言差不多了

Python

f string

fruit = "apple"

print(f'My favorite fruit is {fruit}')
#可以在字串中加入變數 很好用

Flask

語法

Flask

from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
    return "Hello, World!"

if __name__ == '__main__':
    app.run(debug=True,port=5000)

基礎模版

除了3-5行之外

大致上都是複製貼上 可以先不用理解

 

(只要知道他導入Flask

然後可以讓網站跑就好了)

Flask

基礎模版

第3行 - 裝飾器

 

第4-5行 - 函式

當我們載入網站時會跑的函式

from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
    return "Hello, World!"

if __name__ == '__main__':
    app.run(debug=True,port=5000)

Flask

@app.route("/a")
def helloa():	
    return "Hello, World! aaa"

這個函式跑得網站:

http://127.0.0.1:5000/a

from flask import render_template

@app.route("/a")
def hellob():
    name = "John"
    return render_template("index.html", name=name)
<h1>Hello {{ name }}!</h1>

html檔案 (等一下會介紹 不急)

@app.route('/add/<int:a>/<int:b>')
def add(a, b):
    return f'{a} + {b} = {a+b}'

http://127.0.0.1:8080/add/3/4

如何顯示前端

Flask

Flask

小補充:

如何將使用者導入其他網址

from flask import redirect, url_for

@app.route("/")
def main():
    return redirect(url_for("other_route"))
	#將使用者導到 /other_route 這個網址

Flask

實作一

/random/<int:min>/<int:max>

 

這個路由會在 min 和 max 之間產生一個隨機數並回傳,例如:

/random/1/100 → 隨機數:67

 

  • 提示

import random

使用 random.randint(min, max)

Flask

from flask import Flask
import random

app = Flask(__name__)

@app.route('/random/<int:a>/<int:b>')
def main(a, b):
    return f'random number is {random.randint(a, b)}'

if __name__ == '__main__':
    app.run(debug=True,port=5000)

解答:

Jinja

Jinja

能把在後端把一些資訊傳給前端的東西

Jinja

{{ 兩個大括號是變數 }} 
	
{% 執行jinja2內置函數 %}
	
{# 註解說明 #}

語法:

在flask傳出變數,並顯示html:

from flask import render_template

name = "World"
return render_template("index.html", name=name)

Jinja

from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route("/")
def hello():
    name = "World"
    return render_template("index.html", name=name)

if __name__ == '__main__':
    app.run(debug=True,port=5000)
<h1>Hello {{ name }}!</h1>

html檔案

Flask

Jinja

from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route("/")
def main():
    name = "admin"
    return render_template("index.html", name=name)

if __name__ == '__main__':
    app.run(debug=True,port=5000)
    {% if name == "admin" %}
        <h1>Logged in, Hello {{ name }}!</h1>
    {% else %}
        <h1>Not Logged in, Try again!</h1>
    {%endif%}

Flask

html檔案

Jinja

from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route("/")
def main():
    items = ["apples", "bananas", "tomatoes"]
    return render_template("index.html", items=items)

if __name__ == '__main__':
    app.run(debug=True,port=5000)
    {% for item in items %}
        <li> {{ item }} </li>
    {% endfor %}

Flask

html檔案

Jinja

小補充:

如何在html導入css, js

(可以直接用url_for)

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

<script src="{{ url_for('static', filename='js/main.js') }}"></script>

Jinja

實作二

後端有一個數字陣列 arr

 

在前端迭代 arr

如果是偶數就用 <li> render那個數字出來

Jinja

解答:

from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route("/")
def main():
    arr = [1, 2, 5, 7, 3, 8, 10]
    return render_template(("index.html"), arr = arr)

if __name__ == '__main__':
    app.run(debug=True,port=5000)
    {% for i in arr %}
        {% if i%2 == 0 %}
            <li>{{ i }}</li>
        {% endif %}
    {% endfor %}

Flask

html檔案

HTTP Method

HTTP Method

假設今天我們要把一個東西從前端傳到後端

那我們要怎麼達到呢?

(沒是我只是想秀我setup和btr的東西)

資訊(如我空格輸入的東西)

他要這個資訊

HTTP Method

我們主要是用兩種方式

方法 get post
如何 直接加在URL後
head
傳送資料另外寫
body
好處 便宜 安全
壞處 不安全
屬性 request.args request.form

沒有哪個一定比較好 要看情況

Get Method

我們該如何get到資訊呢?

直接加在URL後就好了!

http://127.0.0.1:5000/get_example?name=ming&email=ming@example.com

http://127.0.0.1:5000/get_example/ming/ming@example.com

 

都可以取得兩個資訊

那兩個有什麼區別呢?

Get Method

Query String (?a=5&b=10) Path Variables (/5/10)
傳遞方式 request.args.get() <int:a>、<int:b>
適合場景 搜尋、可選參數 固定結構的 API
URL 範例 /add?a=5&b=10 /add/5/10
可選參數 可以省略(使用 default) 不能省略,必須填值
可讀性 較靈活,適合不固定的查詢 較清楚,適合固定格式

假設我們要用get method拿到兩個數字

有兩種不同的方法

Form

通常來說都是用html中的<form>來傳送資訊

(你們昨天應該有學到table吧)

<h2>get method:</h2>
<form action="/get_example" method="get">
  <table border="1">
    <tr>
      <th>欄位</th>
      <th>輸入</th>
    </tr>
    <tr>
      <td>姓名</td>
      <td><input type="text" name="name"></td>
    </tr>
    <tr>
      <td>電子郵件</td>
      <td><input type="email" name="email"></td>
    </tr>
    <tr>
      <td colspan="2"><input type="submit" value="送出"></td>
    </tr>
  </table>
</form>

get:

後端找的內容

跟表單說這是get

Form

通常來說都是用html中的<form>來傳送資訊

(你們昨天應該有學到table吧)

<h2>post method</h2>
<form action="/post_example" method="post">
  <table border="1">
    <tr>
      <th>欄位</th>
      <th>輸入</th>
    </tr>
    <tr>
      <td>使用者名稱</td>
      <td><input type="text" name="username"></td>
    </tr>
    <tr>
      <td>密碼</td>
      <td><input type="password" name="password"></td>
    </tr>
    <tr>
      <td colspan="2"><input type="submit" value="提交"></td>
    </tr>
  </table>
</form>

post:

後端找的內容

跟表單說這是post

Form

get

from flask import Flask
from flask import redirect
from flask import request, render_template

app = Flask(__name__)

@app.route("/")
def main():
    return render_template("index.html")

@app.route("/get_example", methods=['GET']) #form把資訊傳到 "/get_example", 我們和flask說這是get method
def get_example():
    if request.args:
        #取得我們form的資料(在URL中)
        name = request.args.get('name')
        email = request.args.get('email')

        return f'<h2>{name} / {email} </h2>'
    else:
        return redirect("http://127.0.0.1:5000/") #如果沒收到,就回到主畫面

if __name__ == '__main__':
    app.run(debug=True,port=5000)

Form

get

可以看到 get的資訊都在URL裡面

Form

post

from flask import Flask
from flask import redirect
from flask import request, render_template

app = Flask(__name__)

@app.route("/")
def main():
    return render_template("index.html")

@app.route("/post_example", methods=['GET', 'POST']) #form把資訊傳到 "/post_example", 我們和flask說這是post method
def post_example():
    if request.method == 'POST':
        #取得我們form的資料
        name = request.form['username']
        email = request.form['password']

        return f'<h2>{name} / {email} </h2>'
    else:
        return redirect("http://127.0.0.1:5000/") #如果沒收到,就回到主畫面

if __name__ == '__main__':
    app.run(debug=True,port=5000)

Form

post

為什麼還要 'GET' 呢?

 

如果我們在沒有資料的情況下直接開啟網址

(例如直接打入網址,或把東西都寫在同個route底下)

 

那後端收到的第一次就會是 get method

而這樣才能正確的判斷 不出錯

HTTP Method

實作三

將剛剛講的表單輸入做出來

可以輸入你的名稱,email,密碼之類的東西

 

解答就是剛剛的扣(不要完全抄 自己寫寫看)

cookie/session

cookie?

cookie / session

http://127.0.0.1:5000/a

http://127.0.0.1:5000/b

架設我們今天要在route之間傳送資訊:

例如:登入狀態,偏好設定等

我們可以用cookie和session達到!

cookie

cookie是伺服器留給使用者的一些資訊

 

讓我們在載入其他route時,後端可以取來使用的資訊

cookie

怎麼知道這個cookie不是使用者自己簽的?

(不是伺服器給的)

加密 AES

數字簽名 RSA

session

在後端Flask存的Cookie

 

= session

經過簽名的cookie

session

Cookie Session
存在哪裡? 使用者
(Client 端)
伺服器
(Server 端)
安全性 不安全
容易被竊取、修改
安全
因為存放在伺服器
適用情境 記住登入狀態、偏好設定
(較不重要的東西)
會員登入、銀行交易
(需要更高安全性)

session

from flask import Flask, session

app = Flask(__name__)

secret_key = 'c4dffa417abe4d31936cdf52d3a6d7ae'
app.config['SECRET_KEY'] = secret_key #用來加密的

#有一個 session 的 dict,可以儲存一個使用者的資訊,並且在route之間溝通

@app.route('/login')
def login():
    session['login'] = True
    return 'Login success'

@app.route('/logout')
def logout():
    session['login'] = False
    return 'Logout success'
    
@app.route('/main')
def profile():
    if session.get('login'): #login
        return 'Main page'
    else: #not login
        return 'User not logged in'
    
if __name__ == '__main__':
    app.run(debug=True, port=5000)
# 生成一個隨機的 secret key
import uuid
print(uuid.uuid4().hex)

session

實作四

建立一個前端能輸入 使用者名稱跟密碼

他打什麼使用者名稱就把他存到 session 裡

 

並且做出一個admin介面

只有使用者名稱為admin和密碼符合才可查看

 

簡單來說就是建立一個簡單的登入頁面

session

解答

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>post method</h2>
    <form action="/post_example" method="post">
    <table border="1">
        <tr>
            <th>欄位</th>
            <th>輸入</th>
        </tr>
        <tr>
            <td>使用者名稱</td>
            <td><input type="text" name="username"></td>
        </tr>       
        <tr>
            <td>密碼</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" value="提交"></td>
        </tr>
    </table>
    </form>

</body>
</html>

session

解答

from flask import Flask
from flask import redirect
from flask import request, render_template, url_for, session

app = Flask(__name__)

secret_key = 'c4dffa417abe4d31936cdf52d3a6d7ae'
app.config['SECRET_KEY'] = secret_key #用來加密的

@app.route("/")
def main():
    return render_template("index.html")

@app.route("/post_example", methods=['GET', 'POST']) #form把資訊傳到 "/post_example", 我們和flask說這是post method
def post_example():
    if request.method == 'POST':
        #取得我們form的資料

        #把我們input的資料存到session
        session['name'] = request.form['username']
        session['password'] = request.form['password']

        #判斷我們的輸入是否正確
        if session['name'] == 'admin' and session['password'] == 'admin':
            return redirect(url_for('success'))
        else:
            return redirect(url_for('fail'))
    else:
        return redirect("http://127.0.0.1:5000/") #如果沒收到,就回到主畫面
    
@app.route("/success")
def success():
    #這裡用session,表示可以在路由之間傳送資訊
    return f"success! your name is {session['name']}, and your password is {session['password']}"

@app.route("/fail")
def fail():
    return f"login failed, please try again"

if __name__ == '__main__':
    app.run(debug=True,port=5000)

.env

.env

功能

讓重要或敏感資料(不能公開的)不顯示在程式碼上

如資料庫密碼、DcBot token

.env

2. 創建檔案:

一定要一模一樣

(對他是沒有名字的檔案 然後副檔名是 .env)

pip install python-dotenv

1. 下載python-dotenv:

.env

格式:

變數 = 值
變數 = "字串"

example:

secret_code = 67
password = "password"

.env

假設要丟到github上

那要怎麼隱藏env檔

1. 創建 .gitignore 檔 (對名字要一模一樣)

.env

如何使用

from flask import Flask

import os
from dotenv import load_dotenv
load_dotenv()

app = Flask(__name__)

@app.route("/")
def main():
    pw = os.getenv("password")
    return f'password is: {pw}'

if __name__ == '__main__':
    app.run(debug=True,port=5000)
password = "password"

.env

實作五

用 .env 檔存一個變數

在用flask把他顯示出來

 

(例如可以存存看剛剛的secret_key)

api

假設我們要從前端取後端的資訊,或是從其他伺服器拿資訊的化,我們要怎麼作呢?

(對我用了同張圖)

前端 (or 後端)

後端 (or 其他伺服器)

api

api

簡單來說,我們可以用api,從其他伺服器(或自己的)拿取資料

api

我們從自己的後端開一個api,然後再從前端的js拿取:

from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

@app.route('/api_get', methods=['GET'])
def api_get():
    name = request.args.get('name')

    email = 'placeholder' #(可以從name拿一些資訊 啊我懶得做了 你們可以自己想想看)
    return jsonify({'message': f'Hello, {name}! Your Email is {email}.'})

@app.route('/')
def home():
    return render_template('index.html')
    
if __name__ == '__main__':
    app.run(debug=True, port=5000)

後端部分:

api

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flask Class</title>
</head>
<body>
    <!--這裡可以有些輸入,像是要傳給後端的資料-->
    <input id="name">
    <button onclick="send_get_request()">click to get information!</button>
    <p id="response"></p>

    <script src="{{ url_for('static', filename='js/main.js') }}"></script>
</html>

前端部分:

function send_get_request(){
    //避免特殊字元(像是空格、&、=、/ 等)破壞網址格式。

    let name = document.getElementById("name").value;
    
    //encodeURIComponent() 是用來對使用者輸入的做 URL 編碼
    fetch(`/api_get?name=${encodeURIComponent(name)}`)
    .then(res => res.json()) //回應為 JSON
    .then(data => document.getElementById("response").innerText = data.message) //把那個資訊(data) 變成 #response 的文字
    .catch(err => console.error("錯誤:", err)); //如果錯誤的話
}

api

實作六

建立一個和剛剛類似,可以從前端輸入名字,

然後取得一些使用者資料的網站

 

(答案在上面,我懶得在用了 :P)

api

補充

在python 取得其他api的資訊

舉例來說:discord的api

每個api 通常來說都有 docs,

他會跟你說要輸入什麼,然後他會回傳什麼

api

import requests

API_ENDPOINT = "https://discord.com/api/v10"

def exchange_code(code):
    # gets the access_token

    data = {
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': redir
    }
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    r = requests.post(f'{API_ENDPOINT}/oauth2/token', data=data, headers=headers, auth=(client_id, client_secret))
    r.raise_for_status()
    return get_user_data(r.json()['access_token'])

def get_user_data(accessToken):
    # gets the user id from the access_token

    headers = {
        "Authorization": f"Bearer {accessToken}"
    }

    r = requests.get(url=api_url, headers=headers)
    r.raise_for_status()
    return r.json()

這兩個是dc bot的資訊

我們從這個api的網址request資訊

(開不懂沒關係 通常這種東西都用抄的)

課程結束

幾樣我沒講到

但其實也算重要的東西:

Data Base

websocket

blueprint

祝你們寫後端好運!

Flask

By MLGnotCOOL