# Flask學習筆記
[官方](https://flask.palletsprojects.com/en/1.1.x/)
感覺不會用查這裡比較準
**大綱**
[TOC]
## 網站基礎架構
上到下的順序
**瀏覽器**
-使用者使用-
**前端**(產生畫面)ex: js/css/http
-由http發送要求到後端-
**後端** ex: python/flask
-發送要求到資料庫-
**資料庫** ex: MongoDB/mySQL
### Web API 相關術語
* **HTTP** - 網際網路之間互相傳送訊息
* **URL** - 網址
* **JSON** - 一種輕量級的資料結構,用來傳輸由屬性值或者序列性的值組成的資料物件
* **REST** - 一種全球資訊網軟體架構風格,便於不同軟體/程式在網路中互相傳遞資訊。
## virtualenv
要先架設虛擬環境
沒有用也可以動
有設會比較好,因為多文件開發,會需要不同的編譯環境,不同專案因需要不同版本環境而互相影響,就會"碰"
### Linux
```
$virtualenv flask
$flask/bin/pip install flask
```
### Windows上開始虛擬環境(名字是env)
好像又不能用了:(
參考來源[How to set up a virtual environments for Windows](https://programwithus.com/learn/python/pip-virtualenv-windows)
```
cd project //在project資料夾底下
virtualenv env
\\env\Scripts\activate.bat //creates a batch file
cd env
cd Scripts
activate
pip install flask
$deactivate //離開
```
### 換conda看看(要先載anaconda,一樣是在Windows)
```
conda create -n 專案名字 python=3.6
conda activate 專案名字
install flask(第一次用就好)
```
結果我每次打開他都會幫我先`conda activate base`
deactivate
```
conda deactivate
```
## 開始用Flask
### 基本的
```python=
#!flask/bin/python
from flask import Flask #載入flask
app = Flask(__name__) #初始化 Flask 類別成為 instance
#__name__ 這邊是用來定位目前載入資料夾的位置,用來判別 template__folder 或 static_folder 資料夾位置。
@app.route('/') #路由和處理函式配對
def index(): #回應網站首頁連線
return "Hello, World!" #網站首頁回應內容
if __name__ == '__main__': #啟動網站伺服器
app.run(debug=True) #debug=True ctrl+s可以直接改網站
```
### 執行
```
$python app.py
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 263-040-831
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [23/Oct/2020 02:12:38] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [23/Oct/2020 02:12:52] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [23/Oct/2020 02:12:57] "GET / HTTP/1.1" 200 -
```
127.0什麼的是連線指令等等的東西,包括案重新整理什麼的。
server端永遠不會停止,所以可以一直接受前端的訊息
ctrl+c之後再重新整理,就會出現無法連上網站
Warning的部分只要在跑的時候設定development環境,就不會再跳出來
(在powershell上設)
```
$env:FLASK_ENV = "development"
```
#### 更好的執行方法
拿掉最後的`if __name__ == '__main__':
app.run(debug=True) `
因為容易會有沒有原因的異常錯誤,或是會執行多次部分程式
可以改成
```
$ export FLASK_APP=your_file.py
$ flask run
```
我是在vscode裡面的powershell跑的(在env前面要加錢錢符號才能動,但是我不懂錢錢符號是甚麼意思
```
$env:FLASK_APP = "a" #檔名是a.py
flask run
```
run後面可以加的參數
* –reload # 修改 py 檔後,Flask server 會自動 reload
* –debugger # 如果有錯誤,會在頁面上顯示是哪一行錯誤
* –host # 可以指定允許訪問的主機IP,0.0.0.0 為所有主機的意思
* –port # 自訂網路埠號的參數
舉例
```
flask run --reload --debugger --host 127.0.0.1 --port 5000
```
我如果像很多教學一樣用http://0.0.0.0:80/ 都會打不開...
[官方推薦的方法](https://flask.palletsprojects.com/en/1.1.x/cli/)
### 寫成不同個py檔(Blueprints)
#### 插播:import的用法
一個py檔就是一個module
```python=
import [module]
```
是直接把某個檔案裡所有東西抓來用,所以要用裡面某個函式時要加點(ex:`random.randint(0, 5)`)
ps.relative import不能直接用import
```python=
from [module] import [name1, name2, ...]
```
把檔案裡的函式拿來用,所以不需要加點
(ex:`randint(0, 5)`)
```python=
import [module] as [new_name]
```
跟第一個一樣,只是名字換了新的
(ex:`new_name.randint(0, 5)`)
剩下不想寫了[import](https://medium.com/pyladies-taiwan/python-%E7%9A%84-import-%E9%99%B7%E9%98%B1-3538e74f57e3)
注意:
如果要用相對位置,要寫一個__init__.py在母資料夾
小心import的deadlock(兩個module互相import就會出現)
#### 2個檔案example
以main.py跟a.py試試看
```python=
#main.py
from flask import Flask
import a #把a.py讀進來
app = Flask(__name__)
a.init_app(app) #在a.裡面def一個大函式,這一行之後裡面的東西都可以用
@app.route('/')
def index():
return 'hi!'
if __name__ == "__main__":
app.run(debug=True)
```
```python=
#a.py
def init_app(app):
@app.route('/a')
def aaa():
return 'i am a'
@app.route('/b')
def bbb():
return 'i am b'
```
在網頁上打http://127.0.0.1:5000/a就可以跑出"i am a"
#### 龐大架構要用Blueprints
(template是一個html檔,先放著以後再看[個人網頁模板教學](http://nckuacc.github.io/lastwork/))
假設資料夾長這樣
```
/flask
├── /view
│ └── api.py
└── main.py
```
main.py放在 flask/main.py
```python=
#main.py
from flask import Flask
from flask import Blueprint
from view.api import app2 #view底下的api.app中,拿出app2
app = Flask(__name__)
@app.route('/')
def index():
return "Hello index"
app.register_blueprint(app2) #藍圖抓進來用
```
api.py放在 flask/view/api.py
```python=
from flask import Blueprint
app2 = Blueprint('app2', __name__) #一個創建起來的藍圖
@app2.route('/app2')
def show():
return "Hello Blueprint app2"
```
在網頁上打http://127.0.0.1:5000/app2就可以跑出"Hello Blueprint app2"
### jsonify
我自己寫的
```python=
import flask
from flask import jsonify #使用jsoonify
app.config["JSON_AS_ASCII"] = False # 為了跑出中文
app = flask.Flask(__name__)
app.config["DEBUG"] = True
#創一個json
family = {
"living_city": "Tainan",
"members": [
{
"name": "David",
},
{
"name": "Susan",
},
{
"name": "Kevin",
},
{
"name": "Kathleen",
}
]
}
@app.route('/', methods=['GET'])
def home():
return "<h1>Hello !</h1>"
@app.route('/show', methods=['GET'])
def familly():
return jsonify(family)
#inp是可以傳入的參數
@app.route('/show/<inp>', methods=['GET'])
def inputt(inp):
return jsonify(inp)
app.run()
```
### 傳送json
寫一個send,在python send.py之後會把json傳到指定網址
```python=
#send.py
import requests
me = {'last_name': 'Luo', 'first_name ': 'Xuan-Yu'}
m = requests.get('http://127.0.0.1:5001/get/json', json=me)
#如果要用post可以改成m = requests.post~~~~~~
#下面是debug用的
decoded_result = m.json()
print('in send')
print(decoded_result)
```
在app.py中的函式
```python=
@app.route('/get/json', methods=['GET'])
def get_json():
json_data = request.json
#print是debug用的
print('in web')
print(json_data)
return jsonify(json_data)
```
request.json 取得接受到的json檔
### Flask-sqlalchemy
先安裝sqlalchemy
```
pip install flask-sqlalchemy
```
在檔案一開始要import
```python=
from flask_sqlalchemy import SQLAlchemy
```
連到哪個sql?(跨謀,先放著)是改後面那串
```
app.config['SQLALCHEMY_DATABASE_URI'] = [DB_TYPE]+[DB_CONNECTOR]://[USERNAME]:[PASSWORD]@[HOST]:[PORT]/[DB_NAME]
```
應該會長這樣
ps: sqlite:後三個斜線是相對位置,四個斜線是絕對位置(要設定母資料夾)
```python=
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tmp/test.db'
```
試試看能不能建立一個空的database
```python=
from flask import Flask
from flask import request
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy() #在括號裡面打app的話代表甚麼都不initialize,跟只有括號有一點差別,目前是都可用
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db.init_app(app) # ????
@app.route('/', methods=['GET'])
def index():
return "Hello!"
@app.route('/create', methods=['GET'])
def indexx():
db.create_all()
return 'ok'
```
直接執行就會在tmp資料夾底下創一個test.db(若資料夾不存在就會出現Internal Server Error)
[QUICKSTART](https://flask-sqlalchemy.palletsprojects.com/en/2.x/quickstart/)
[SQLAlchemy的API](https://flask.palletsprojects.com/en/1.1.x/search/?q=SQLAlchemy%28app%29)
#### 建立database與基本使用
寫一個class記錄要放甚麼東西
```python=
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, request
import datetime
import time
app = Flask(__name__)
# MySql datebase
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False #不會跑出警告
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///cars.db"
db = SQLAlchemy(app)
class Cars(db.Model):
__tablename__ = "車車"
plate_num = db.Column(db.String(30), primary_key=True) # 車牌
arriving_time = db.Column(db.DateTime, nullable=False) # 預計抵達時間
driver = db.Column(db.String(10), nullable=False) # 駕駛人
def __init__(self, plate_num, arriving_time, driver):
self.plate_num = plate_num
self.arriving_time = arriving_time
self.driver = driver
@app.route('/')
def index():
# Create data
db.create_all()
return 'ok'
@app.route('/add', methods=['GET'])
def add():
c1 = Cars('FFF-353', datetime.datetime(2020, 11, 6, 21, 19, 15),'王小寶')
c2 = Cars('BBB-353',datetime.datetime(2020, 11, 6, 21, 50, 15), '王大寶')
c = [c1, c2]
db.session.add_all(c)
db.session.commit()
return 'ok'
@app.route('/find', methods=['GET'])
def find():
query = Cars.query.filter_by(plate_num='AAA-303').first() #找車牌AAA-303的車
# print(query.plate_num)
if query is None: #沒有找到的時候
return 'not found'
else: #找到就印出駕駛人名字
return query.driver
@app.route('/change', methods=['GET'])
def change():
query = Cars.query.filter_by(plate_num='BBB-353').first()
query.car_type = 1 #改變找到的query中的某個值
db.session.commit()
return 'changed'
@app.route('/delete/<inp>', methods=['GET'])
def delete(inp): #記得要在函式的input參數這裡宣告從網址傳入的值
query = Cars.query.filter_by(plate_num=inp).first()
db.session.delete(query) #刪掉找到的query
db.session.commit()
return 'deleted'
```
ps.如果重複新增同樣primary key的資料就會在網頁跑出internal sever error,以上面的例子來說,不能一直刷新/add這個頁面
#### Table的互相連結
如果要連接兩個table就要再寫一個table互相連結彼此
假設是a、b之間用atob連接
```python=
#in a
db_a_atob = db.relationship("atob", backref="a")
#in b
db_b_atob = db.relationship("atob", backref="b")
#in atob
#要加上foreign key
aid = db.Column(db.Integer, db.ForeignKey('a.aid'), nullable=False)
bid = db.Column(db.Integer, db.ForeignKey('b.bid'), nullable=False)
```
連結好之後
假如說要查第幾個連結的值
```python=
query = atob.query.filter_by(id=1).first() #取得第一筆資料
print (query.a.element)
```
就會印出a的element(被連接的),query.a的a是backref
### 與html做連結
* server端
每個函式return那行加入render template,開頭也要import,template要放在同資料夾的templates底下
假如說從database取得某個table的所有資料並傳到html中
(positions和cars都是要傳入html中的資料,Positions和Cars是創建database的class名稱)
```python=
@app.route('/')
def index():
positions = Positions.query.all()
cars = Cars.query.all()
return render_template('index.html', positions=positions, cars=cars)
```
* 網頁端
以剛剛傳入的positions和cars的資料來說,如果要show出每一個資料
(位置是創建database時的database tablename)
```htmlembedded=
{% for 位置 in positions %}
<!-- 要顯示的內容-->
{% endfor %}
```
要取得值的話用{{ tablename.data }}(要在上面寫的迴圈之內)
不管是在show或是id等等取值一樣
ex:
```htmlembedded=
<button type="button" class="form-control" id="pos_buttton"
button_id="posInput{{ 位置.pid }}">{{ 位置.name }}</button>
```
button_id會變成Input+第n個數據的pid,然後顯示第n數據的name
## 遇到的問題
#### 如果執行(./xxxxx.py)出來的網址沒有用ctrl+c關閉
先輸入指令
```
ps -fA | grep python
```
看第二欄的pid再用
```
kill -9 pid
```
去結束就可以繼續使用
#### 把GET直接改成POST會出現Method Not Allowed
```python=
@app.route('/', methods=['POST']) #這裡
```
可以改成
```python=
@app.route('/', methods=['GET','POST'])
```
是因為是用這個路徑去到達這個網址,所以如果只允許POST就會出錯
那應該是要把POST應用在接收資料上,GET用來進入網站。
## 其他補充:
chmod a+x app.py (a+x a是所有用戶 +x是執行權限)
### POST and GET
有點像GET是明信片,POST是信封的感覺
* GET
以 Query String(一種Key/Vaule的編碼方式)加在要寄送的地址(URL)後面
會有安全性問題
* POST
### JSON格式
陣列可以用 [ ] 來寫入資料
物件可以用 { } 來寫入資料
name / value 是成對的,中間透過 (:) 來區隔
flask jsonify的參考:[在flask中使用jsonify和json.dumps的区别](https://blog.csdn.net/Duke_Huan_of_Qi/article/details/76064225)
### 更改位置
在app.run裡面改
```python=
app.run(host="127.0.0.1", port=5001) #會連到http://127.0.0.1:5001/
```
### SQL
SQL 是應用程式與資料庫之間溝通的語言,大概就是用SQL下指令去對database幹嘛幹嘛
row- 一筆資料
column - 資料的一個部分
create的時候要定義好資料型態
NOT NULL資料不可為空
UNIQUE資料都不相同
CHECK有限制條件
Primary Key 可以是原本資料內的一個欄位,或是一個人造欄位 (與原本資料沒有關係的欄位),用確認每一筆資料
### ORM
Object Relational Mapping
用於實現物件導向程式語言裡不同類型系統的資料之間的轉換。
就是可以在程式語言中使用的虛擬資料庫
## 在powershell上的指令
建virtual env相關
```
> conda create -n project_name python=3.6
> conda env list //看哪些env
> conda env remove -n project_name //刪除這個env
> pip list //看有什麼套件
> conda activate project_name //進入env
> conda deactivate //離開env
```
切換到python終端機
```
> python //就會變成>>>在用ctrl+z跳出就好
```