配套視頻教程
數據庫能為應用程序提供有效檢索的持久數據。這是本章學習的內容。
Flask中的數據庫
Flask本身並不支持數據庫。意味着可以自由選擇適合應用程序的數據庫。
在Python中對於數據庫,有很多選擇,並且很多帶有Flask擴展,可很好地與Flask Web應用程序集成。數據庫分為兩大類:關系型、非關系型。后者稱為NoSQL,即它們沒有實現流行的關系查詢語言SQL。關系型數據庫適合具有結構化數據的應用程序,如用戶列表、博客帖子等;而NoSQL數據庫適合具有結構不太明確的。
本章將再使用兩個Flask擴展:Flask-SQLAlchemy、Flask-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-dateutil、python-editor、six。
庫名稱 | 版本號 | 簡要說明 |
---|---|---|
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='susan@example.com')
>>> u
<User susan>
創建遷移存儲庫
上一節中創建的模型類定義了這個應用程序的初始數據庫結構(或模式schema)。但隨着應用程序的不斷發展,將可能需要更改結構、添加新內容,還看修改或刪除項目。Alembic(Flask-Migrate使用的遷移框架)將以不需要從頭開始重新創建數據庫的方式做到這些模式變化。
為了完成這個看似困難且麻煩的任務,Alembic維護了一個遷移存儲庫,它是一個存儲其遷移腳本的目錄。每次對數據庫模式進行更改時,都會將遷移腳本添加到存儲庫,其中包含更改的詳細信息。若要遷移運用於數據庫,這些遷移腳本將按創建順序執行。
Flask-Migrate通過flask命令
公開其命令。運行程序時的flask run
是Flask原生子命令。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\versions下1f1d69541c8c_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 upgrade
。Alembic將檢測到生產數據庫未更新到最新版本的模式,並運行在先前版本之后創建的所有新遷移腳本。
flask db downgrade
命令,可撤銷上次遷移。雖然我們不太可能在生產系統上需要它,但可能在開發過程非常有用。例:已生成遷移腳本並將其運用,但發現所做的更改並非所需要的。這種情況下,可降級數據庫,刪除遷移腳本,然后生成一個新的替換它。
數據庫關系
關系數據庫善於存儲數據項之間的關系。考慮用戶撰寫博客文章的情況,用戶將在users表
中有一條記錄,該帖子在posts表
中也將有一條記錄。記錄撰寫特定帖子的人最有效的方法是鏈接兩個相關記錄。
一旦建立了用戶、帖子之間的鏈接,數據庫就可以回答有關此鏈接的查詢。最簡單的一個查詢:當有博客帖子並且知道需要知道用戶寫了什么。更復雜的查詢與此相反:可能想知道某用戶所寫的所有帖子。Flask-SQLAlchemy將幫助處理這兩種類型的查詢。
擴展數據庫以存儲博客文章,並查看它們的關系。以下是新posts表
的結構:
這個posts表
有id、body、timestamp
字段。還有一個額外的字段user_id
,將帖子連接到作者。所有用戶都有一個主鍵id
,是唯一的。將博客帖子鏈接到所創作它的用戶的方法是添加對用戶的引用id,這正是user_id
字段,稱之為 外鍵
。上述圖中顯示了作為外鍵的字段 user_id
與id
字段之間的鏈接,這種關系稱之為一對多,即一個用戶可寫多篇帖子。
更新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='john@example.com')
>>> 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='susan@example.com')
>>> 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