《Flask 入門教程》第 5 章:數據庫

大部分程序都須要保存數據,因此不可避免要使用數據庫。用來操做數據庫的數據庫管理系統(DBMS)有不少選擇,對於不一樣類型的程序,不一樣的使用場景,都會有不一樣的選擇。在這個教程中,咱們選擇了屬於關係型數據庫管理系統(RDBMS)的 SQLite,它基於文件,不須要單獨啓動數據庫服務器,適合在開發時使用,或是在數據庫操做簡單、訪問量低的程序中使用。html

使用 SQLAlchemy 操做數據庫

爲了簡化數據庫操做,咱們將使用 SQLAlchemy——一個 Python 數據庫工具(ORM,即對象關係映射)。藉助 SQLAlchemy,你能夠經過定義 Python 類來表示數據庫裏的一張表(類屬性表示表中的字段 / 列),經過對這個類進行各類操做來代替寫 SQL 語句。這個類咱們稱之爲模型類,類中的屬性咱們將稱之爲字段python

Flask 有大量的第三方擴展,這些擴展能夠簡化和第三方庫的集成工做。咱們下面將使用一個叫作 Flask-SQLAlchemy 的官方擴展來集成 SQLAlchemy。git

首先使用 Pipenv 安裝它:github

$ pipenv install flask-sqlalchemy複製代碼

大部分擴展都須要執行一個「初始化」操做。你須要導入擴展類,實例化並傳入 Flask 程序實例:sql

from flask_sqlalchemy import SQLAlchemy  # 導入擴展類

app = Flask(__name__)

db = SQLAlchemy(app)  # 初始化擴展,傳入程序實例 app複製代碼

設置數據庫 URI

爲了設置 Flask、擴展或是咱們程序自己的一些行爲,咱們須要設置和定義一些配置變量。Flask 提供了一個統一的接口來寫入和獲取這些配置變量:Flask.config 字典。配置變量的名稱必須使用大寫,寫入配置的語句通常會放到擴展類實例化語句以前。shell

下面寫入了一個 SQLALCHEMY_DATABASE_URI 變量來告訴 SQLAlchemy 數據庫鏈接地址:數據庫

import os

# ...

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////' + os.path.join(app.root_path, 'data.db')複製代碼

注意 這個配置變量的最後一個單詞是 URI,而不是 URL。flask

對於這個變量值,不一樣的 DBMS 有不一樣的格式,對於 SQLite 來講,這個值的格式以下:服務器

sqlite:////數據庫文件的絕對地址複製代碼

數據庫文件通常放到項目根目錄便可,app.root_path 返回程序實例所在模塊的路徑(目前來講,即項目根目錄),咱們使用它來構建文件路徑。數據庫文件的名稱和後綴你能夠自由定義,通常會使用 .db、.sqlite 和 .sqlite3 做爲後綴。session

另外,若是你使用 Windows 系統,上面的 URI 前綴部分須要寫入三個斜線(即 sqlite:///)。在本書的示例程序代碼裏,作了一些兼容性處理,另外還新設置了一個配置變量,實際的代碼以下:

import os
import sys

from flask import Flask

WIN = sys.platform.startswith('win')
if WIN:  # 若是是 Windows 系統,使用三個斜線
    prefix = 'sqlite:///'
else:  # 不然使用四個斜線
    prefix = 'sqlite:////'

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(app.root_path, 'data.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 關閉對模型修改的監控複製代碼

若是你固定在某一個操做系統上進行開發,部署時也使用相同的操做系統,那麼能夠不用這麼作,直接根據你的須要寫出前綴便可。

提示 你能夠訪問 Flask 文檔的配置頁面查看 Flask 內置的配置變量;一樣的,在 Flask-SQLAlchemy 文檔的配置頁面能夠看到 Flask-SQLAlchemy 提供的配置變量。

建立數據庫模型

在 Watchlist 程序裏,目前咱們有兩類數據要保存:用戶信息和電影條目信息。下面分別建立了兩個模型類來表示這兩張表:

app.py:建立數據庫模型

class User(db.Model):  # 表名將會是 user(自動生成,小寫處理)
    id = db.Column(db.Integer, primary_key=True)  # 主鍵
    name = db.Column(db.String(20))  # 名字


class Movie(db.Model):  # 表名將會是 movie
    id = db.Column(db.Integer, primary_key=True)  # 主鍵
    title = db.Column(db.String(60))  # 電影標題
    year = db.Column(db.String(4))  # 電影年份複製代碼

模型類的編寫有一些限制:

  • 模型類要聲明繼承 db.Model
  • 每個類屬性(字段)要實例化 db.Column,傳入的參數爲字段的類型,下面的表格列出了經常使用的字段類。
  • db.Column() 中添加額外的選項(參數)能夠對字段進行設置。好比,primary_key 設置當前字段是否爲主鍵。除此以外,經常使用的選項還有 nullable(布爾值,是否容許爲空值)、index(布爾值,是否設置索引)、unique(布爾值,是否容許重複值)、default(設置默認值)等。

經常使用的字段類型以下表所示:

  • db.Integer 整型
  • db.String (size) 字符串,size 爲最大長度,好比db.String(20)
  • db.Text 長文本
  • db.DateTime 時間日期,Pythondatetime對象
  • db.Float 浮點數
  • db.Boolean 布爾值

建立數據庫表

模型類建立後,還不能對數據庫進行操做,由於咱們尚未建立表和數據庫文件。下面在 Python Shell 中建立了它們:

$ flask shell
>>> from app import db
>>> db.create_all()複製代碼

打開文件管理器,你會發現項目根目錄下出現了新建立的數據庫文件 data.db。這個文件不須要提交到 Git 倉庫,咱們在 .gitignore 文件最後添加一行新規則:

*.db複製代碼

若是你改動了模型類,想從新生成表模式,那麼須要先使用 db.drop_all() 刪除表,而後從新建立:

>>> db.drop_all()
>>> db.create_all()複製代碼

注意這會一併刪除全部數據,若是你想在不破壞數據庫內的數據的前提下變動表的結構,須要使用數據庫遷移工具,好比集成了 AlembicFlask-Migrate 擴展。

提示 上面打開 Python Shell 使用的是 flask shell命令,而不是 python。使用這個命令啓動的 Python Shell 激活了「程序上下文」,它包含一些特殊變量,這對於某些操做是必須的(好比上面的 db.create_all()調用)。請記住,後續的 Python Shell 都會使用這個命令打開。

flask shell相似,咱們能夠編寫一個自定義命令來自動執行建立數據庫表操做:

import click

@app.cli.command()  # 註冊爲命令
@click.option('--drop', is_flag=True, help='Create after drop.')  # 設置選項
def initdb(drop):
    """Initialize the database."""
    if drop:  # 判斷是否輸入了選項
        db.drop_all()
    db.create_all()
    click.echo('Initialized database.')  # 輸出提示信息複製代碼

默認狀況下,函數名稱就是命令的名字,如今執行 flask initdb 命令就能夠建立數據庫表:

$ flask initdb複製代碼

使用 --drop 選項能夠刪除表後從新建立:

$ flask initdb --drop複製代碼

建立、讀取、更新、刪除

在前面打開的 Python Shell 裏,咱們來測試一下常見的數據庫操做。你能夠跟着示例代碼來操做,也能夠自由練習。

建立

下面的操做演示瞭如何向數據庫中添加記錄:

>>> from app import User, Movie  # 導入模型類
>>> user = User(name='Grey Li')  # 建立一個 User 記錄
>>> m1 = Movie(title='Leon', year='1994')  # 建立一個 Movie 記錄
>>> m2 = Movie(title='Mahjong', year='1996')  # 再建立一個 Movie 記錄
>>> db.session.add(user)  # 把新建立的記錄添加到數據庫會話
>>> db.session.add(m1)
>>> db.session.add(m2)
>>> db.session.commit()  # 提交數據庫會話,只須要在最後調用一次便可複製代碼

提示 在實例化模型類的時候,咱們並無傳入 id 字段(主鍵),由於 SQLAlchemy 會自動處理這個字段。

最後一行 db.session.commit() 很重要,只有調用了這一行纔會真正把記錄提交進數據庫,前面的 db.session.add() 調用是將改動添加進數據庫會話(一個臨時區域)中。

讀取

經過對模型類的 query 屬性調用可選的過濾方法和查詢方法,咱們就能夠獲取到對應的單個或多個記錄(記錄以模型類實例的形式表示)。查詢語句的格式以下:

<模型類>.query.<過濾方法(可選)>.<查詢方法>複製代碼

下面是一些經常使用的過濾方法:

  • filter() 使用指定的規則過濾記錄,返回新產生的查詢對象
  • filter_by() 使用指定規則過濾記錄(以關鍵字表達式的形式),返回新產生的查詢對象
  • order_by() 根據指定條件對記錄進行排序,返回新產生的查詢對象
  • group_by() 根據指定條件對記錄進行分組,返回新產生的查詢對象

下面是一些經常使用的查詢方法:

  • all() 返回包含全部查詢記錄的列表
  • first() 返回查詢的第一條記錄,若是未找到,則返回None
  • get(id) 傳入主鍵值做爲參數,返回指定主鍵值的記錄,若是未找到,則返回None
  • count() 返回查詢結果的數量
  • first_or_404() 返回查詢的第一條記錄,若是未找到,則返回404錯誤響應
  • get_or_404(id) 傳入主鍵值做爲參數,返回指定主鍵值的記錄,若是未找到,則返回404錯誤響應
  • paginate() 返回一個Pagination對象,能夠對記錄進行分頁處理

下面的操做演示瞭如何從數據庫中讀取記錄,並進行簡單的查詢:

>>> from app import Movie  # 導入模型類
>>> movie = Movie.query.first()  # 獲取 Movie 模型的第一個記錄(返回模型類實例)
>>> movie.title  # 對返回的模型類實例調用屬性便可獲取記錄的各字段數據
'Leon'
>>> movie.year
'1994'
>>> Movie.query.all()  # 獲取 Movie 模型的全部記錄,返回包含多個模型類實例的列表
[<Movie 1>, <Movie 2>]
>>> Movie.query.count()  # 獲取 Movie 模型全部記錄的數量
2
>>> Movie.query.get(1)  # 獲取主鍵值爲 1 的記錄
<Movie 1>
>>> Movie.query.filter_by(title='Mahjong').first()  # 獲取 title 字段值爲 Mahjong 的記錄
<Movie 2>
>>> Movie.query.filter(Movie.title=='Mahjong').first()  # 等同於上面的查詢,但使用不一樣的過濾方法
<Movie 2>複製代碼

提示 咱們在說 Movie 模型的時候,實際指的是數據庫中的 movie 表。表的實際名稱是模型類的小寫形式(自動生成),若是你想本身指定表名,能夠定義 __tablename__ 屬性。

對於最基礎的 filter() 過濾方法,SQLAlchemy 支持豐富的查詢操做符,具體能夠訪問文檔相關頁面查看。除此以外,還有更多的查詢方法、過濾方法和數據庫函數可使用,具體能夠訪問文檔的 Query API 部分查看。

更新

下面的操做更新了 Movie 模型中主鍵爲 2 的記錄:

>>> movie = Movie.query.get(2)
>>> movie.title = 'WALL-E'  # 直接對實例屬性賦予新的值便可
>>> movie.year = '2008'
>>> db.session.commit()  # 注意仍然須要調用這一行來提交改動複製代碼

刪除

下面的操做刪除了 Movie 模型中主鍵爲 1 的記錄:

>>> movie = Movie.query.get(1)
>>> db.session.delete(movie)  # 使用 db.session.delete() 方法刪除記錄,傳入模型實例
>>> db.session.commit()  # 提交改動複製代碼

在程序裏操做數據庫

通過上面的一番練習,咱們能夠在 Watchlist 裏進行實際的數據庫操做了。

在主頁視圖讀取數據庫記錄

由於設置了數據庫,負責顯示主頁的 index 能夠從數據庫裏讀取真實的數據:

@app.route('/')
def index():
    user = User.query.first()  # 讀取用戶記錄
    movies = Movie.query.all()  # 讀取全部電影記錄
    return render_template('index.html', user=user, movies=movies)複製代碼

index 視圖中,原來傳入模板的 name 變量被 user 實例取代,模板 index.html 中的兩處 name 變量也要相應的更新爲 user.name 屬性:

{{ user.name }}'s Watchlist複製代碼

生成虛擬數據

由於有了數據庫,咱們能夠編寫一個命令函數把虛擬數據添加到數據庫裏。下面是用來生成虛擬數據的命令函數:

import click

@app.cli.command()
def forge():
    """Generate fake data."""
    db.create_all()
    
    # 全局的兩個變量移動到這個函數內
    name = 'Grey Li'
    movies = [
        {'title': 'My Neighbor Totoro', 'year': '1988'},
        {'title': 'Dead Poets Society', 'year': '1989'},
        {'title': 'A Perfect World', 'year': '1993'},
        {'title': 'Leon', 'year': '1994'},
        {'title': 'Mahjong', 'year': '1996'},
        {'title': 'Swallowtail Butterfly', 'year': '1996'},
        {'title': 'King of Comedy', 'year': '1999'},
        {'title': 'Devils on the Doorstep', 'year': '1999'},
        {'title': 'WALL-E', 'year': '2008'},
        {'title': 'The Pork of Music', 'year': '2012'},
    ]
    
    user = User(name=name)
    db.session.add(user)
    for m in movies:
        movie = Movie(title=m['title'], year=m['year'])
        db.session.add(movie)
    
    db.session.commit()
    click.echo('Done.')複製代碼

如今執行 flask forge 命令就會把全部虛擬數據添加到數據庫裏:

$ flask forge複製代碼

本章小結

本章咱們學習了使用 SQLAlchemy 操做數據庫,後面你會慢慢熟悉相關的操做。結束前,讓咱們提交代碼:

$ git add .
$ git commit -m "Add database support with Flask-SQLAlchemy"
$ git push複製代碼

提示 你能夠在 GitHub 上查看本書示例程序的對應 commit:7f167d2

進階提示

  • 在生產環境,你能夠更換更合適的 DBMS,由於 SQLAlchemy 支持多種 SQL 數據庫引擎,一般只須要改動很是少的代碼。
  • 咱們的程序只有一個用戶,因此沒有將 User 表和 Movie 表創建關聯。訪問 Flask-SQLAlchemy 文檔的」聲明模型「章節能夠看到相關內容
  • 《Flask Web 開發實戰》第 5 章詳細介紹了 SQLAlchemy 和 Flask-Migrate 的使用,第 8 章和第 9 章引入了更復雜的模型關係和查詢方法。
  • 閱讀 SQLAlchemy 官方文檔和教程詳細瞭解它的用法。注意咱們在這裏使用 Flask-SQLAlchemy 來集成它,因此用法和單獨使用 SQLAlchemy 有一些不一樣。做爲參考,你能夠同時閱讀 Flask-SQLAlchemy 官方文檔
相關文章
相關標籤/搜索