1. 程式人生 > 實用技巧 >09.flask部落格專案實戰四之資料庫

09.flask部落格專案實戰四之資料庫

配套視訊教程

本文B站配套視訊教程

資料庫能為應用程式提供有效檢索的持久資料。這是本章學習的內容。

Flask中的資料庫

Flask本身並不支援資料庫。意味著可以自由選擇適合應用程式的資料庫。

在Python中對於資料庫,有很多選擇,並且很多帶有Flask擴充套件,可很好地與Flask Web應用程式整合。資料庫分為兩大類:關係型、非關係型。後者稱為NoSQL,即它們沒有實現流行的關係查詢語言SQL。關係型資料庫適合具有結構化資料的應用程式,如使用者列表、部落格帖子等;而NoSQL資料庫適合具有結構不太明確的。

本章將再使用兩個Flask擴充套件:Flask-SQLAlchemyFlask-Migrate

其中,Flask-SQLAlchemy為流行的SQLAlchemy包提供了一個Flask-friendly包裝器,它是一個ORM。ORM允許應用程式使用高階實體(如類、物件、方法)而不是表和SQL來管理資料庫,它的工作是將高階操作轉換為資料庫命令。

關於SQLAlchemy的好處是:它不是一個ORM,而是許多關係資料庫。SQLAlchemy支援很多資料庫引擎,包括流行的MySQL、PostgreSQL、SQLite。這非常強大,因為可以使用不需要伺服器的簡單SQLite資料庫進行開發,然後在生產伺服器上部署應用程式時,可以選擇更強大的MySQL或PostgreSQL伺服器,而無需改變應用程式。

PS:學到一個單詞alchemy譯為鍊金術、魔力,SQLAlchemy是個有魔力的東西。

在虛擬環境中安裝Flask-SQLAlchemy:pip install flask-sqlalchemy,將會自動附帶安裝sqlalchemy包。

庫名稱 版本號 簡要說明
sqlalchemy 1.2.10 SQLAlchemy不是資料庫,而是操作資料庫的工具包、是對資料庫進行操作的一種框架,簡化了資料庫管理的操作。也是一個強大的關係型資料庫框架。
flask-sqlalchemy 2.3.2 Flask-SQLAlchemy 也是一種資料庫框架,是一個Flask擴充套件,它包裝了SQLAlchemy,支援多種資料庫後臺。無須關心SQL處理細節,一個基本關係對應一個類,而一個實體對應類的例項物件,通過呼叫方法操作資料庫。
(venv) D:\microblog>pip install flask-sqlalchemy
Collecting flask-sqlalchemy
  Using cached https://files.pythonhosted.org/packages/a1/44/294fb7f6bf49cc7224417cd0637018db9fee0729b4fe166e43e2bbb1f1c8/Flask_SQLAlchemy-2.3.2-py2.py3-none-any.whl
Requirement already satisfied: Flask>=0.10 in d:\microblog\venv\lib\site-packages (from flask-sqlalchemy)
Collecting SQLAlchemy>=0.8.0 (from flask-sqlalchemy)
  Downloading https://files.pythonhosted.org/packages/8a/c2/29491103fd971f3988e90ee3a77bb58bad2ae2acd6e8ea30a6d1432c33a3/SQLAlchemy-1.2.10.tar.gz (5.6MB)
    100% |████████████████████████████████| 5.6MB 208kB/s
Requirement already satisfied: Werkzeug>=0.14 in d:\microblog\venv\lib\site-packages (from Flask>=0.10->flask-sqlalchemy)
Requirement already satisfied: click>=5.1 in d:\microblog\venv\lib\site-packages (from Flask>=0.10->flask-sqlalchemy)
Requirement already satisfied: Jinja2>=2.10 in d:\microblog\venv\lib\site-packages (from Flask>=0.10->flask-sqlalchemy)
Requirement already satisfied: itsdangerous>=0.24 in d:\microblog\venv\lib\site-packages (from Flask>=0.10->flask-sqlalchemy)
Requirement already satisfied: MarkupSafe>=0.23 in d:\microblog\venv\lib\site-packages (from Jinja2>=2.10->Flask>=0.10->flask-sqlalchemy)
Installing collected packages: SQLAlchemy, flask-sqlalchemy
  Running setup.py install for SQLAlchemy ... done
Successfully installed SQLAlchemy-1.2.10 flask-sqlalchemy-2.3.2

資料庫遷移

大多數資料庫教程都涵蓋資料庫的建立、使用,但沒有充分解決在應用程式在需要更改或增長時對現有資料庫進行更新的問題。這是一個難點,因為關係資料庫是以結構化資料為中心,在結構發生更改時,資料庫中已有的資料需要遷移到修改後的結構中。

Flask-Migrate擴充套件Alembic(PS:Alembic是SQLAlchemy作者編寫的資料庫遷移工具)的Flask包裝器,是SQLAlchemy的資料庫遷移框架。雖然使用資料庫遷移為啟動資料庫添加了一些工作,但這是一個很小的代價,將為未來對資料庫進行更改提供強大方法。
在虛擬環境中安裝Flask-Migrate:pip install flask-migrate,將會自動附帶安裝Mako、alembic、python-dateutilpython-editorsix

庫名稱 版本號 簡要說明
Flask-Migrate 2.2.1 Flask的資料庫遷移擴充套件
alembic 1.0.0 SQLAlchemy作者編寫的資料庫遷移工具
Mako 1.0.7 是一種嵌入式Python(即Python伺服器頁面)語言,提供一種熟悉的非XML語法
python-dateutil 2.7.3 對datetime模組的強大擴充套件
python-editor 1.0.3 以程式設計方式開啟編輯器,捕獲結果
six 1.11.0 Python 2.x和3.x相容庫,目的是編寫相容兩個Python版本的Python程式碼
C:\Users\Administrator>d:

D:\>cd D:\microblog\venv\Scripts

D:\microblog\venv\Scripts>activate
(venv) D:\microblog\venv\Scripts>cd D:\microblog

(venv) D:\microblog>pip install flask-migrate
Collecting flask-migrate
  Using cached https://files.pythonhosted.org/packages/59/97/f681c9e43d2e2ace4881fa588d847cc25f47cc98f7400e237805d20d6f79/Flask_Migrate-2.2.1-py2.py3-none-any.whl
Requirement already satisfied: Flask-SQLAlchemy>=1.0 in d:\microblog\venv\lib\site-packages (from flask-migrate)
Collecting alembic>=0.7 (from flask-migrate)
  Downloading https://files.pythonhosted.org/packages/96/c7/a4129db460c3e0ea8fea0c9eb5de6680d38ea6b6dcffcb88898ae42e170a/alembic-1.0.0-py2.py3-none-any.whl (158kB)
    100% |████████████████████████████████| 163kB 246kB/s
Requirement already satisfied: Flask>=0.9 in d:\microblog\venv\lib\site-packages (from flask-migrate)
Requirement already satisfied: SQLAlchemy>=0.8.0 in d:\microblog\venv\lib\site-packages (from Flask-SQLAlchemy>=1.0->flask-migrate)
Collecting python-dateutil (from alembic>=0.7->flask-migrate)
  Using cached https://files.pythonhosted.org/packages/cf/f5/af2b09c957ace60dcfac112b669c45c8c97e32f94aa8b56da4c6d1682825/python_dateutil-2.7.3-py2.py3-none-any.whl
Collecting Mako (from alembic>=0.7->flask-migrate)
  Using cached https://files.pythonhosted.org/packages/eb/f3/67579bb486517c0d49547f9697e36582cd19dafb5df9e687ed8e22de57fa/Mako-1.0.7.tar.gz
Collecting python-editor>=0.3 (from alembic>=0.7->flask-migrate)
  Using cached https://files.pythonhosted.org/packages/65/1e/adf6e000ea5dc909aa420352d6ba37f16434c8a3c2fa030445411a1ed545/python-editor-1.0.3.tar.gz
Requirement already satisfied: itsdangerous>=0.24 in d:\microblog\venv\lib\site-packages (from Flask>=0.9->flask-migrate)
Requirement already satisfied: click>=5.1 in d:\microblog\venv\lib\site-packages (from Flask>=0.9->flask-migrate)
Requirement already satisfied: Jinja2>=2.10 in d:\microblog\venv\lib\site-packages (from Flask>=0.9->flask-migrate)
Requirement already satisfied: Werkzeug>=0.14 in d:\microblog\venv\lib\site-packages (from Flask>=0.9->flask-migrate)
Collecting six>=1.5 (from python-dateutil->alembic>=0.7->flask-migrate)
  Using cached https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl
Requirement already satisfied: MarkupSafe>=0.9.2 in d:\microblog\venv\lib\site-packages (from Mako->alembic>=0.7->flask-migrate)
Installing collected packages: six, python-dateutil, Mako, python-editor, alembic, flask-migrate
  Running setup.py install for Mako ... done
  Running setup.py install for python-editor ... done
Successfully installed Mako-1.0.7 alembic-1.0.0 flask-migrate-2.2.1 python-dateutil-2.7.3 python-editor-1.0.3 six-1.11.0

Flask-SQLAlchemy配置

在開發過程中,這將使用SQLite資料庫,它是開發小型應用程式的方便選擇,有時甚至不是那麼小,因為每個資料庫都儲存在磁碟上的單個檔案中,並且不需要執行像MySQL、PostgreSQL的資料庫伺服器。

新增兩個配置項到config.py中:
microblog/config.py:Flask-SQLAlchemy配置

import os

basedir = os.path.abspath(os.path.dirname(__file__))#獲取當前.py檔案的絕對路徑

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you will never guess'

	SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 'app.db')
	SQLALCHEMY_TRACK_MODIFICATIONS = False

Flask-SQLAlchemy擴充套件SQLALCHEMY_DATABASE_URI變數中獲取應用程式資料庫的位置。從環境變數中設定配置是一種很好的做法,並在環境中未定義變數時提供回退值。在這種情況下,將從DATABASE_URL環境變數中獲取資料庫URL;如果沒有定義,將在應用程式主目錄下配置一個名為app.db的資料庫,該資料庫儲存在basedir變數中。

SQLALCHEMY_TRACK_MODIFICATIONS配置選項設定為False,意為禁用我不需要的Flask-SQLAlchemy的該特徵,即追蹤物件的修改並且傳送訊號。
更多的Flask-SQLAlchemy配置選項可參考官網

資料庫將在應用程式中通過資料庫例項表示。資料庫遷移引擎也將有一個例項。這些是需要在應用程式之後需要建立的物件(即在app/init.py)。

app/init.py:初始化Flask-SQLAlchemy和Flask-Migrate,更新程式碼

from flask import Flask
from config import Config#從config模組匯入Config類

from flask_sqlalchemy import SQLAlchemy#從包中匯入類
from flask_migrate import Migrate

app = Flask(__name__)
app.config.from_object(Config)

db = SQLAlchemy(app)#資料庫物件
migrate = Migrate(app, db)#遷移引擎物件

from app import routes,models#匯入一個新模組models,它將定義資料庫的結構,目前為止尚未編寫

資料庫模型

將儲存在資料庫中的資料由一組來表示,通常稱之為資料庫模型SQLAlchemy中的ORM層將做從這些類建立的物件對映到適當的資料庫表的行所需的轉換。

建立代表使用者的模型,下圖表示在users表中使用的資料。

id欄位通常在所有模型中,用作主鍵,將為資料庫中的每個使用者分配一個唯一的id值,該值儲存在id欄位中。大多情況下,主鍵由資料庫自動分配,因此只需提供id欄位並標記為主鍵。
username、email、password_hash欄位都被定義為字串(VARCHAR),並指定最大長度。其中,password_hash欄位值得關注,我們要確保應用程式採用安全性最佳實踐,因此不能將使用者密碼儲存在資料庫中。不直接寫密碼,而是密碼雜湊,大大提高安全性。

現在,已知道想要的使用者表,將其轉換為新模板models中的程式碼:
app/models.py:使用者資料庫模型

from app import db

class User(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	username = db.Column(db.String(64), index=True, unique=True)
	email = db.Column(db.String(120), index=True, unique=True)
	password_hash = db.Column(db.String(128))

	def __repr__(self):
		return '<User {}>'.format(self.username)

上述建立的User類繼承自db.Model,它是Flask-SQLAlchemy所有模型的基類。這個類定義了幾個欄位作為類變數,這些欄位是db.Column類建立的例項,它將欄位型別作為引數、及其他可選引數,例 指定哪些欄位是唯一的、索引的,這些對資料庫搜尋是非常重要的、有效的。

repr 方法用於告知Python如何列印此類的物件,這對除錯很有用。在下面的Python解釋會話中可看到__repr__()方法的實際應用:

>>> from app.models import User
>>> u = User(username='susan', email='[email protected]')
>>> u
<User susan>

建立遷移儲存庫

上一節中建立的模型類定義了這個應用程式的初始資料庫結構(或模式schema)。但隨著應用程式的不斷髮展,將可能需要更改結構、新增新內容,還看修改或刪除專案。Alembic(Flask-Migrate使用的遷移框架)將以不需要從頭開始重新建立資料庫的方式做到這些模式變化。

為了完成這個看似困難且麻煩的任務,Alembic維護了一個遷移儲存庫,它是一個儲存其遷移指令碼的目錄。每次對資料庫模式進行更改時,都會將遷移指令碼新增到儲存庫,其中包含更改的詳細資訊。若要遷移運用於資料庫,這些遷移指令碼將按建立順序執行。

Flask-Migrate通過flask命令公開其命令。執行程式時的flask runFlask原生子命令flask db子命令由Flask-Migrate新增到管理有關資料庫遷移的一切。因此,通過執行flask db init命令為微部落格建立遷移儲存庫:

(venv) D:\microblog>flask db init
Creating directory D:\microblog\migrations ... done
Creating directory D:\microblog\migrations\versions ... done
Generating D:\microblog\migrations\alembic.ini ... done
Generating D:\microblog\migrations\env.py ... done
Generating D:\microblog\migrations\README ... done
Generating D:\microblog\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'D:\\microblog\\migrations\\alembic.ini' before proceeding.

執行完該命令後,專案資料夾microblog下新增migrations目錄,它就是遷移目錄,其中包含一些檔案、一個版本子目錄。從此刻開始,所有這些檔案都應視為專案的一部分,且應該新增到原始碼管理中。

flask命令是依賴於FLASK_APP環境變數來知道Flask應用程式的位置。對於這個應用程式,正是FLASK_APP = microblog.py

第1次資料庫遷移

有了遷移儲存庫,就可建立第一個資料庫遷移,其中包括對映到User資料庫模型的users表。有兩種方法可建立資料庫遷移:手動、自動。為了自動生成遷移,Alembic將資料庫模型定義的資料庫模式與資料庫中當前使用的實際資料庫模式進行比較。然後,它會使遷移指令碼填充必要的更改,以使資料庫模式與應用程式模型匹配。在當前情況下,由於此前沒有資料庫,自動遷移會將整個User模型新增到遷移指令碼中。flask db migrate子命令生成這些自動遷移:

(venv) D:\microblog>flask db migrate -m "users table"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'user'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'
Generating D:\microblog\migrations\versions\1f1d69541c8c_users_table.py ... done

flask db migrate -m "users table"將輸出Alembic在遷移中包含的內容。前兩行是資訊性的,通常可忽略。接著,說檢測到一個使用者表、兩個索引。最後,它告知編寫的遷移指令碼的位置。這個1f1d69541c8c_users_table.py檔案(位於migrations\versions目錄下)是用於遷移自動生成的唯一程式碼。使用-m選項提供註釋的可選的,它可為遷移新增一個簡短的描述性文字。

此時,microblog目錄下將新增app.db檔案(即SQLite資料庫)、microblog\migrations\versions1f1d69541c8c_users_table.py檔案。生成的這個遷移指令碼現已是專案的一部分,需要合併到原始碼管理中。它裡頭有兩個函式:upgrade()用於遷移;downgrade()用於刪除。這允許Alembic使用降級路徑將資料庫遷移到歷史記錄的任何位置,甚至遷移到舊版本。

flask db migrate命令不對資料庫進行任何更改,它只生成遷移指令碼。要將更改運用於資料庫,必須使用flask db upgrade命令。
因為這個應用程式使用SQLite,所以upgrade命令將檢測資料庫不存在,並將建立它。如果使用的是MySQL或PostgreSQL等資料庫伺服器,得在執行upgrade命令之前在資料庫伺服器建立資料庫。

(venv) D:\microblog>flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 1f1d69541c8c, users table

此時,microblog目錄下的app.db將更新。

PS:預設情況下,Flask-SQLAlchemy對資料庫表使用“snake case”命名約定。對於上述User模型,在資料庫中相應的表將被命名為user。對於AddressAndPhone模型類,將會被命名為address_and_phone。如果想選擇自己的表名,可在模型類中新增一個名為__tablename__的屬性,並將其設定為一個字串。

資料庫升級和降級工作流程

現在這個Flask應用程式還處於起步階段,但討論未來的資料庫遷移策略並無壞處。如應用程式在開發機器上,而且還有一個副本部署到線上和正在使用的生產伺服器上。

假設應用程式的下個版本需對模型進行更改,例如新增新表。如果沒有遷移,則需要弄清楚如何在開發機器和再次在伺服器上去改變資料庫模式,這可能需要做很多的工作。

但是,通過資料庫遷移的支援,在修改了應用程式的模型後,將會生成一個新的遷移指令碼(flask db migrate),我們可能會檢查它以確保自動生成做正確的事,然後將改變運用於開發資料庫(flask db upgrade)。我們將遷移指令碼新增到原始碼管理、並提交它。

當準備將新版本的應用程式釋出到生產伺服器時,需做的是獲取應用程式的更新版本(包括新的遷移指令碼)並執行flask db upgradeAlembic將檢測到生產資料庫未更新到最新版本的模式,並執行在先前版本之後建立的所有新遷移指令碼。

flask db downgrade命令,可撤銷上次遷移。雖然我們不太可能在生產系統上需要它,但可能在開發過程非常有用。例:已生成遷移指令碼並將其運用,但發現所做的更改並非所需要的。這種情況下,可降級資料庫,刪除遷移指令碼,然後生成一個新的替換它。

資料庫關係

關係資料庫善於儲存資料項之間的關係。考慮使用者撰寫部落格文章的情況,使用者將在users表中有一條記錄,該帖子在posts表中也將有一條記錄。記錄撰寫特定帖子的人最有效的方法是連結兩個相關記錄。

一旦建立了使用者、帖子之間的連結,資料庫就可以回答有關此連結的查詢。最簡單的一個查詢:當有部落格帖子並且知道需要知道使用者寫了什麼。更復雜的查詢與此相反:可能想知道某使用者所寫的所有帖子。Flask-SQLAlchemy將幫助處理這兩種型別的查詢。

擴充套件資料庫以儲存部落格文章,並檢視它們的關係。以下是新posts表的結構:

這個posts表id、body、timestamp欄位。還有一個額外的欄位user_id,將帖子連線到作者。所有使用者都有一個主鍵id,是唯一的。將部落格帖子連結到所創作它的使用者的方法是新增對使用者的引用id,這正是user_id欄位,稱之為外來鍵。上述圖中顯示了作為外來鍵的欄位user_idid欄位之間的連結,這種關係稱之為一對多,即一個使用者可寫多篇帖子。

更新models模組,app/models.py:帖子的資料庫表和關係

from app import db
from datetime import datetime

class User(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	username = db.Column(db.String(64), index=True, unique=True)
	email = db.Column(db.String(120), index=True, unique=True)
	password_hash = db.Column(db.String(128))
	posts = db.relationship('Post', backref='author', lazy='dynamic')

	def __repr__(self):
		return '<User {}>'.format(self.username)

class Post(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	body = db.Column(db.String(140))
	timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
	user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

	def __repr__(self):
		return '<Post {}'.format(self.body)

新建的Post類表示使用者撰寫的部落格文章。timestamp欄位被 編入索引,比如按時間順序檢索帖子,這將非常有用;還添加了default引數,並傳給它datetime.utcnow函式(得到的是格林威治時間 (GMT),即北京時間減8個時區的時間),在此,要注意一點,即將函式作為預設引數傳遞時,SQLAlchemy會將該欄位設定為呼叫該函式的值(utcnow後沒有括號()),傳遞的是函式本身,而不是呼叫它的結果。通常,在伺服器應用程式中使用UTC日期和時間,這可確保使用統一的時間戳,無論使用者立於何處,這些時間戳都可在顯示時轉換為使用者的本地時間。
user_id欄位已作為一個外來鍵初始化了,這表明它引用了users表中的id值。在這個引用中user表示模型中資料庫表的名稱。這有一個不幸的不一致:在某些情況下,如db.relationship()呼叫中,模型由模型類引用,模型類通常以大寫字元開頭;而在其他情況下,如db.ForeignKey()中,模型由資料庫表名稱給出,SQLAlchemy自動使用小寫字元,對於多字模型名稱將使用“snake case”命名約定。

User類有一個新的posts欄位,是用db.relationship初始化的。這不是一個真正的資料庫欄位,而是使用者、帖子之間關係的高階檢視,因此它不在資料庫圖中。對於一對多關係,db.relationship欄位通常在“一”側定義,並且用作訪問“多”的便捷方式。因此,舉例,如果有一個使用者儲存u,表示式u.posts將執行一個數據庫查詢,返回該使用者的所有帖子。db.relationship的第一個引數表示關係“多”的模型類。如果模型稍後在模組中定義,則此引數可作為帶有類名的字串提供。第二個引數backref定義將新增到“多”類的物件的欄位的名稱,該類指向“一”物件。這將新增一個post.author表示式,它將返回給定帖子的使用者。第三個引數lazy定義瞭如何釋出對關係的資料庫查詢,這將在稍後討論。

由於我們對應用程式的模型進行了更新,因此需要生成新的資料庫遷移:

(venv) D:\microblog>flask db migrate -m "posts table"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'post'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_post_timestamp' on '['timestamp']'
Generating D:\microblog\migrations\versions\c0139b2ef594_posts_table.py ... done

遷移需要應用於資料庫:

(venv) D:\microblog>flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 1f1d69541c8c -> c0139b2ef594, posts table

Show Time

上述所有內容均是講述如何定義資料庫,但還未告知如何運作的。由於應用程式還沒有任何資料庫邏輯,接下來將在Python直譯器中使用資料庫來熟悉它。在啟用虛擬環境的前提下,啟動Python直譯器,進入Python提示後,匯入資料庫例項、模型:

(venv) D:\microblog>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from app import db
>>> from app.models import User, Post

首先,建立一個新使用者:

>>> u = User(username='john', email='[email protected]')
>>> db.session.add(u)
>>> db.session.commit()

對資料庫的更改在會話session的上下文中完成,即作為db.session()訪問。可以在會話session中累積多次更改,一旦所有的更改註冊了,就可以用db.session.commit()傳送一個訊號,它以原子方式寫入所有更改。
如果在會話session期間任何時間有一個錯誤,都可呼叫db.session.rollback()中止會話session並刪除儲存在其中的任何更改。
請注意的是:更改只會在db.session.commit()呼叫時寫入資料庫。會話session保證了資料庫永遠不會處於不一致狀態。

新增另一個使用者:

>>> u = User(username='susan', email='[email protected]')
>>> db.session.add(u)
>>> db.session.commit()

資料庫可回答一個返回所有使用者的查詢:

>>> users = User.query.all()
>>> users
[<User john>, <User susan>]
>>> for u in users:
...     print(u.id, u.username)
...
1 john
2 susan

所有模型都有一個query屬性,它是執行資料庫查詢的入口點。最基本的查詢是返回該類的所有元素,它被適當地命名為all()。上述例中:這兩個使用者被新增時,id欄位會被自動地設定為1和2。
如果知道使用者id,還可通過如下方式檢索該使用者:

>>> u = User.query.get(1)
>>> u
<User john>

為id 為1的使用者新增一篇博文:

>>> u = User.query.get(1)
>>> p = Post(body='my first post come!', author=u)
>>> db.session.add(p)
>>> db.session.commit()

不需要為timestamp欄位設定值,因為它有預設值(模型中定義了)。對於user_id欄位,User類向用戶新增posts欄位中db.relationship還把author屬性給了帖子。這裡使用author虛擬欄位將作者分配給帖子,而不是必須處理使用者ID。SQLAlchemy在這方面做的很好,它提供了對關係、外來鍵的高階抽象。

再看幾個資料庫查詢:

>>> #取得一個使用者寫的所有帖子
...
>>> u = User.query.get(1)
>>> u
<User john>
>>> posts = u.posts.all()
>>> posts
[<Post my first post come!]
>>> #同樣,看看沒有寫帖子的使用者
...
>>> u = User.query.get(2)
>>> u
<User susan>
>>> u.posts.all()
[]
>>> #對所有帖子列印其作者、內容
...
>>> posts = Post.query.all()
>>> for p in posts:
...     print(p.id, p.author.username, p.body)
...
1 john my first post come!
>>> #以反字母順序列印所有使用者
...
>>> User.query.order_by(User.username.desc()).all()
[<User susan>, <User john>]

Flask_SQLAlchemy文件可學習到更多查詢資料庫的知識。

刪除上述建立的測試使用者、帖子,以便資料庫乾淨,併為下一章做準備

>>> users = User.query.all()
>>> for u in users:
...     db.session.delete(u)
...
>>> posts = Post.query.all()
>>> for p in posts:
...     db.session.delete(p)
...
>>> db.session.commit()

Shell上下文

在上一節開啟Python直譯器後,做的第一件事是執行一些匯入:

>>> from app import db
>>> from app.models import User, Post

在處理應用程式時,經常需要在Python shell中進行測試,如果每次都要重複上述匯入,將變得極其乏味、麻煩。flask shell命令flask命令傘中另一個非常有用的工具。shell命令是在run之後,Flask執行的第二個核心命令。這個命令的目的是在應用程式的上下文中啟動Python直譯器。以下示例助理解:

(venv) d:\microblog>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> app
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'app' is not defined
>>>exit()
(venv) d:\microblog>flask shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
App: app [production]
Instance: d:\microblog\instance
>>> app
<Flask 'app'>

使用常規直譯器會話(python)時,app符號除非顯示匯入了,否則它是不可知的,會報錯!但是使用flask shell命令,它預先匯入應用程式例項。flask shell的好處是:不是它預先匯入app,而是我們可配置一個“shell context”(即shell上下文,它是一個預先匯入的其他符號的列表)。

模組microblog.py新增給函式,它建立了一個shell 上下文,將資料庫例項、模型新增到shell會話中:

from app import app, db
from app.models import User, Post

@app.shell_context_processor
def make_shell_context():
    return {'db': db, 'User': User, 'Post': Post}

上述app.shell_context_processor裝飾器註冊了一個作為shell 上下文功能的函式。當執行flask shell命令時,它將呼叫此函式並在shell會話中註冊它返回的項。函式返回字典而不是列表的原因是:對於每個專案,我們還須提供一個名稱,在這個名稱下,它將在shell中被引用,由字典的鍵給出。

由於上述更新了microblog.py檔案,則必須重新設定FLASK_APP環境變數,否則會報錯NameError。添加了shell 上下文處理器功能後,我們可以方便地使用資料庫實體,而無需匯入它們:

(venv) d:\microblog>set FLASK_APP=microblog.py

(venv) d:\microblog>flask shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
App: app [production]
Instance: d:\microblog\instance
>>> db
<SQLAlchemy engine=sqlite:///d:\microblog\app.db>
>>> User
<class 'app.models.User'>
>>> Post
<class 'app.models.Post'>

目前為止,專案結構:

microblog/
    app/
        templates/
            base.html
            index.html
            login.html
        __init__.py
        forms.py
        models.py
        routes.py
    migrations/
    venv/
    app.db
    config.py
    microblog.py

參考:
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iv-database