Python Web Flask — 使用SQLAlchemy資料庫
在我們建立網站的時候,如果希望網站不是單純的靜態展示網站,資料庫就扮演了不可或缺的角色。所謂資料庫(Database),是按照資料的結構來組織、儲存和管理資料的地方,可以說是電子化的檔案櫃。在網頁應用程式上,一直以來多以關聯式資料庫為主(常見的有MySQL、Postgre SQL、SQLite、MS SQL Server等)近年來NoSQL也開始大為流行。我們對於資料庫的操作大致上不外乎儲存、新增、修改和刪除資料(CRUD)等操作方式。
本篇將以 Flask、Flask-SQLAlchemy為基礎來實作資料庫的操作。在開始時做之前,我們還需要暸解的一個概念是ORM。
什麼是 ORM
所謂的ORM(Object-relational Mapping)框架是在關聯式資料庫(Database)和業務實體物件(Application)之間做一個映對(mapping),ORM會在背後自動將 Python 代碼轉換成應對的SQL語法,再來進行對資料庫的操作。換句話說,開發者可以直接用 Python 的語法對資料庫進行操作,不需要再去寫複雜的SQL語法處理資料的選取,只需要簡單的操作物件的屬性與方法就可以達到寫SQL語法的效果。
由於ORM框架可以讓開發者無需處理SQL的敘述,這對於開發者來說,可以加速開發的效率。另外,ORM提供了獨立於SQL的介面,它會自己處理不同資料庫之間的差異,因此便於移轉資料庫。並且使用快取最佳化技術可以加強資料庫操作的效率。
既然ORM如此好用,我們該如何將這個概念實作在Python的網站中?這時候登場的是SQLAlchemy,也是本篇主要要討論的重點。
SQLAlchemy
SQLAlchemy是Python下一款ORM架構,使用MIT授權發行。由於Flask 本身不支援直接對資料庫進行操作,Flask-SQLAlchemy解決了這問題,它適度的包裝了SQLAlchemy。透過這個套件,可以簡化 Flask 開發人員對資料庫的操作。
Flask-SQLAlchemy套件
使用Flask-SQLAlchemy的方式需要以下三個步驟:安裝、載入需要的設定以及建立SQLAlchemy物件。
安裝Flask-SQLAlchemy
首先,我們使用pip將 flask-sqlalchemy套件安裝在自己的電腦中。
$ pip install flask-sqlalchemy
載入 Flask-SQLAlchemy
安裝完畢之後,即可在python中載入 Flask-SQLAlchemy套件。別忘了除了載入 Flask-SQLAlchemy套件外,仍然需要載入Flask套件。
from flask_sqlalchemy import SQLAlchemy
config:設定資料庫連線
接著要進行初始化設定。加上下面兩個config來設定資料庫的連線。
其中,SQLALCHEMY_DATABASE_URI用來設定SQLite資料庫檔案路徑。(sqlite:///users.sqlite3)
app.config['SQLALCHEMY_DATABASE_URI']='sqlite:///users.sqlite3'app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Falsedb = SQLAlchemy(app)
一旦建立了物件之後,就會提供一個名為Model的類別,此類別可以用於宣告Model。
Model:建立User類別
因此,我們建立一個User類別,該類別繼承了db.Model,這個類別可以提供我們未來與資料庫進行溝通傳遞:
class Users(db.Model): _id = db.Column('id', db.Integer, primary_key=True) name = db.Column('name', db.String(100)) email = db.Column(db.String(100)) def __init__(self, name, email): self.name =name self.email = email
Column:資料庫的欄位
在上面的User類別中,我們可以看到三行有db.Column的參數,Column表示資料庫的欄位,欄位名稱原則上為變數名稱,如果想要與變數名稱不同的話,可以在Column()中加上第一個參數,例如本例 _id = db.Column(‘id’)時,欄位的名稱為id而非_id。
_id = db.Column('id', db.Integer, primary_key=True)
name = db.Column('name', db.String(100))
email = db.Column(db.String(100))
由上面的設定看來,目前資料欄位其依序為id、name與email三個。其中id為primary_key(因為在Column中間加上了primary_key=True ),在輸入資料時會自動產生。
下面是可以選用的各種欄位型態:
如本例:db.String(100)
表示欄位中(name與email)可以放置最大長度為100的字串;而db.Integer
表示該欄位(id)的內容為整數。
由於id為自動產生的primary_key,我們不需要每次當作參數輸入。在__init__
初始化中只需要兩個參數name與email。
def __init__(self, name, email): self.name =name self.email = email
create_all:建立初始化的資料庫
在程式最後面加上db.create_all()。
if __name__ =="__main__": db.create_all() app.run(debug=True)
執行db.create_all()就可以建立初始化的資料庫。可以透過DB Browser for SQLLite,看到自己建立的資料庫Schema。
實作
我們準備製作一個網站,首頁(http://127.0.0.1:5000/)的部分有一個「登入系統」的按鈕。
點選按鈕之後會進入login頁面(http://127.0.0.1:5000/login),並且有個輸入表單填寫姓名(須由26英文字母組成的名稱)。
填完姓名並按下確認送出之後,網頁會進入user使用者專區( http://127.0.0.1:5000/user)。我們計畫在頁面中只放一個input表單,在表單中可以輸入使用者的email。
使用者輸入完email之後,可以到列表頁面,查看所有已經輸入的姓名與電子郵件。這些資料都是存在SQLite資料庫裡面。
以上所呈現的畫面,為整個網站大致上的流程。另外,我們之前討論過的session,也會在後面的實作中加進來。
我們就從使用者頁面的樣板開始吧!
user頁的表單
我們在templates資料夾裡面的user.html ,加上一個<form>表單,裡面有一個<input>可以用來輸入email,與一個<button>按鈕可以用來submit傳送資料。<form>表單傳送資料的方式採用POST(method=”post”)。
簡化的form表單如下:
<form action="#" method="post">
<div>
<input type="email" name="email" placeholder="請輸入email" value="{{email if email}}">
</div>
<div>
<button type="submit">確認送出</button>
</div>
</form>
如果加上CSS裝飾後,user.html樣板如下:
{% extends "base.html" %}{% block title %}User Page{% endblock %}{% block body %}
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Hello {{user}}</h1>
<p class="lead">歡迎 {{user}}來到會員專區</p>
<form action="#" method="post">
<div class="form-group">
<input type="email" name="email" class="form-control" placeholder="請輸入email" value="{{email if email}}">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">確認送出Email</button>
</div>
</form>
</div>
</div>{% endblock %}
在Python程式部分,我們要調整form.py。在user()的request方式,要加上POST與GET兩種方法:
@app.route("/user", methods=["POST","GET"])
接下來調整user()函式,先設定email為None:
email = None
判斷式:
if request.method == "POST":
email = request.form["email"]
session["email"] = email
else:
if "email" in session:
email = session["email"]return render_template("user.html",user=user, email=email)
如果是採取POST方法的時候(if request.method == "POST":
),就去取得頁面上input輸入欄位裡面輸入的email(email = request.form["email"]
),並且將取得的email值設定在session中(session["email"] = email
)。
反之,若條件不成立(沒有使用POST方法)時,就取得session中的email值。
if "email" in session:
email = session["email"]
如果以上兩者皆非,既沒有POST也沒有session,就直接顯示user.html樣板頁。
return render_template("user.html",user=user, email=email)
接下來,資料庫要進場:
在if request.method == “POST”:
的程式區段,我們要加上資料庫相關程式。前面我們設定了資料庫為sqlite:///users.sqlite3
。因此,要從資料庫裡面尋找name欄位是否已經存在user,如果有的話即設定email為輸入的email,並且存入資料庫中,存入資料庫前必須先commit(db.session.commit())。
found_user = Users.query.filter_by(name=user).first()
found_user.email = email
db.session.commit()
於是,整個user函式如下:
def user():
email = None
if "user" in session:
user = session["user"]
if request.method == "POST":
email = request.form["email"]
session["email"] = email
found_user = Users.query.filter_by(name=user).first()
found_user.email = email
db.session.commit()
else:
if "email" in session:
email = session["email"]
return render_template("user.html", email=email)
else:
return redirect(url_for("login"))
列表頁面
接著,要做一個view的列表頁面,頁面中顯示所有進入user資料庫的紀錄。view.html樣板如下:
{% extends "base.html" %}{% block title %}View All Users{% endblock %}{% block body %}
<div class="jumbotron jumbotron-fluid mt-5">
<div class="container">
<h1 class="display-4">查看所有名單</h1><ul class="list-group">
{% for item in values%}
<li class="list-group-item">
<h5 class="mb-1">姓名: {{item.name}}</H5>
<p class="mb-1">電子郵件: {{item.email}}</p>
</li>
{% endfor %}
</ul></div></div>
{% endblock %}
樣板中,我們透過for迴圈顯示values裡面個別的name與email(如下面程式碼部分):
{% for item in values%}
<li class="list-group-item">
<h5 class="mb-1">姓名: {{item.name}}</H5>
<p class="mb-1">電子郵件: {{item.email}}</p>
</li>
{% endfor %}
而這個values則是從下面的values=Users.query.all()
而來。query.all()會對資料庫進行資料的取出。
@app.route("/view")
def view():
return render_template("view.html", values=Users.query.all())
修改login
最後我們要回過來修改login的部分。在這裡我們要加上與資料庫有關的程式碼:
found_user = Users.query.filter_by(name=user).first()
if found_user:
session["email"]= found_user.email
else:
usr = users(user,"")
db.session.add(usr)
db.session.commit()
透過這一段程式碼,我們可以找到資料庫中的user是否與登入的使用者一樣。反之,就存入資料庫成為一個新的紀錄。
完整的login函式如下:
@app.route("/login", methods=["POST","GET"])
def login():
if request.method == "POST":
session.permanent = True
user = request.form["nm"]
session["user"] = user
found_user = Users.query.filter_by(name=user).first()
if found_user:
session["email"]= found_user.email
else:
usr = users(user,"")
db.session.add(usr)
db.session.commit()
return redirect(url_for("user"))
else:
if "user" in session:
return redirect(url_for("user"))
return render_template("login.html")
請使用下面步驟執行看看:
- 先進入首頁:http://127.0.0.1:5000/
- 再到登入頁:http://127.0.0.1:5000/login
- 輸入名稱,按下「確認送出」進入http://127.0.0.1:5000/user頁
- 輸入email後,按下「確認送出Email」
- 自行進入http://127.0.0.1:5000/view頁,看看剛剛輸入的email是否存在清單中?
執行的結果是否與原來計畫的一樣?