Python Web Flask — 使用SQLAlchemy資料庫

Sean Yeh
Python Everywhere -from Beginner to Advanced
16 min readNov 13, 2020

--

在我們建立網站的時候,如果希望網站不是單純的靜態展示網站,資料庫就扮演了不可或缺的角色。所謂資料庫(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/)的部分有一個「登入系統」的按鈕。

index首頁

點選按鈕之後會進入login頁面(http://127.0.0.1:5000/login),並且有個輸入表單填寫姓名(須由26英文字母組成的名稱)。

login登入頁

填完姓名並按下確認送出之後,網頁會進入user使用者專區( http://127.0.0.1:5000/user)。我們計畫在頁面中只放一個input表單,在表單中可以輸入使用者的email。

user使用者專頁

使用者輸入完email之後,可以到列表頁面,查看所有已經輸入的姓名與電子郵件。這些資料都是存在SQLite資料庫裡面。

view列表頁

以上所呈現的畫面,為整個網站大致上的流程。另外,我們之前討論過的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是否存在清單中?

執行的結果是否與原來計畫的一樣?

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。