第五章 數(shù)據(jù)庫(kù)
序:什么是數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)按規(guī)則保存程序數(shù)據(jù),
程序發(fā)起查詢(xún)?nèi)』財(cái)?shù)據(jù)。
Web 程序最常使用基于關(guān)系模型的數(shù)據(jù)庫(kù),
這種數(shù)據(jù)庫(kù)也稱(chēng)為 SQL 數(shù)據(jù)庫(kù),
因?yàn)樗鼈兪褂媒Y(jié)構(gòu)化查詢(xún)語(yǔ)言。
不過(guò)最近幾年文檔數(shù)據(jù)庫(kù)和鍵值對(duì)數(shù)據(jù)庫(kù)成了流行的替代選擇,
這兩種數(shù)據(jù)庫(kù)合稱(chēng) NoSQL數(shù)據(jù)庫(kù)。
5.1 SQL數(shù)據(jù)庫(kù)
關(guān)系型數(shù)據(jù)庫(kù)把數(shù)據(jù)存儲(chǔ)在表中,
表模擬程序中不同的實(shí)體。
例如,訂單管理程序的數(shù)據(jù)庫(kù)中可能有表 customers、 products 和 orders。
表的列數(shù)是固定的, 行數(shù)是可變的。
列定義表所表示的實(shí)體的數(shù)據(jù)屬性。
例如, customers表中可能有 name、 address、 phone 等列。
表中的行定義各列對(duì)應(yīng)的真實(shí)數(shù)據(jù)。
表中有個(gè)特殊的列, 稱(chēng)為主鍵,
其值為表中各行的唯一標(biāo)識(shí)符。
表中還可以有稱(chēng)為外鍵的列,
引用同一個(gè)表或不同表中某行的主鍵。
行之間的這種聯(lián)系稱(chēng)為關(guān)系,
這是關(guān)系型數(shù)據(jù)庫(kù)模型的基礎(chǔ)。
5.2 NoSQL數(shù)據(jù)庫(kù)
所有不遵循上節(jié)所述的關(guān)系模型的數(shù)據(jù)庫(kù)統(tǒng)稱(chēng)為 NoSQL 數(shù)據(jù)庫(kù)。
NoSQL 數(shù)據(jù)庫(kù)一般使用集合代替表,
使用文檔代替記錄。
NoSQL 數(shù)據(jù)庫(kù)采用的設(shè)計(jì)方式使聯(lián)結(jié)變得困難,
所以大多數(shù)數(shù)據(jù)庫(kù)根本不支持這種操作。
5.3 使用SQL還是NoSQL
SQL 數(shù)據(jù)庫(kù)擅于用高效且緊湊的形式存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)。
這種數(shù)據(jù)庫(kù)需要花費(fèi)大量精力保證數(shù)據(jù)的一致性。
NoSQL 數(shù)據(jù)庫(kù)放寬了對(duì)這種一致性的要求,
從而獲得性能上的優(yōu)勢(shì)。
對(duì)不同類(lèi)型數(shù)據(jù)庫(kù)的全面分析、 對(duì)比超出了本書(shū)范疇。
對(duì)中小型程序來(lái)說(shuō),
SQL 和 NoSQL數(shù)據(jù)庫(kù)都是很好的選擇,
而且性能相當(dāng)。
5.4 python數(shù)據(jù)庫(kù)框架
可選擇的數(shù)據(jù)庫(kù)框架
大多數(shù)的數(shù)據(jù)庫(kù)引擎都有對(duì)應(yīng)的 Python 包,
包括開(kāi)源包和商業(yè)包。
Flask 并不限制你使用何種類(lèi)型的數(shù)據(jù)庫(kù)包,
因此可以根據(jù)自己的喜好選擇使用
MySQL、 Postgres、 SQLite、
Redis、 MongoDB 或者 CouchDB。
如果這些都無(wú)法滿(mǎn)足需求,
還有一些數(shù)據(jù)庫(kù)抽象層代碼包供選擇,
例如 SQLAlchemy 和MongoEngine。
你可以使用這些抽象包直接處理高等級(jí)的 Python 對(duì)象,
而不用處理如表、文檔或查詢(xún)語(yǔ)言此類(lèi)的數(shù)據(jù)庫(kù)實(shí)體。
選擇時(shí)需要考慮的因素
易用性
如果直接比較數(shù)據(jù)庫(kù)引擎和數(shù)據(jù)庫(kù)抽象層,顯然后者取勝。
性能
一般情況下, ORM 和 ODM 對(duì)生產(chǎn)率的提
升遠(yuǎn)遠(yuǎn)超過(guò)把對(duì)象業(yè)務(wù)轉(zhuǎn)換成數(shù)據(jù)庫(kù)業(yè)務(wù)的損耗。
真正的關(guān)鍵點(diǎn)在于如何選擇一個(gè)能直接操作低層數(shù)據(jù)庫(kù)的抽象層,
以防特定的操作需要直接使用數(shù)據(jù)庫(kù)原生指令優(yōu)化。
可移稙性
選擇數(shù)據(jù)庫(kù)時(shí),必須考慮其是否能在你的開(kāi)發(fā)平臺(tái)和生產(chǎn)平臺(tái)中使用。
SQLAlchemy ORM 就是一個(gè)很好的例子,
它支持很多關(guān)系型數(shù)據(jù)庫(kù)引擎,
包括流行的 MySQL、 Postgres 和 SQLite。
Flask集成度
專(zhuān)門(mén)為Flask 開(kāi)發(fā)的擴(kuò)展是你的首選,
選擇這些框架可以節(jié)省你編寫(xiě)集成代碼的時(shí)間。
5.5 使用Flask-SQLAlchemy管理數(shù)據(jù)庫(kù)
什么是Flask-SQLAlchemy
Flask-SQLAlchemy 是一個(gè) Flask 擴(kuò)展,
簡(jiǎn)化了在 Flask 程序中使用 SQLAlchemy 的操作。
SQLAlchemy 是一個(gè)很強(qiáng)大的關(guān)系型數(shù)據(jù)庫(kù)框架,
支持多種數(shù)據(jù)庫(kù)后臺(tái)。
SQLAlchemy 提供了高層 ORM,
也提供了使用數(shù)據(jù)庫(kù)原生 SQL 的低層功能。
如何安裝
$ pip install flask-sqlalchemy
數(shù)據(jù)庫(kù)URL
表5-1 FLask-SQLAlchemy數(shù)據(jù)庫(kù)URL
| 數(shù)據(jù)庫(kù)引擎 | URL |
|---|---|
| MySQL | mysql://username:password@hostname/database |
| Postgres | postgresql://username:password@hostname/database |
| SQLite( Unix) | sqlite:////absolute/path/to/database |
| SQLite( Windows) | sqlite:///c:/absolute/path/to/database |
配置數(shù)據(jù)庫(kù)
from flask_sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
__file__是當(dāng)前文件的文件路徑(包含文件名),
dirname可以提取出不包含文件名的路徑,
abspath可以把路徑轉(zhuǎn)換為絕對(duì)路徑,
join可以把兩個(gè)路徑合并為一個(gè)路徑。
db對(duì)象是 SQLAlchemy類(lèi)的實(shí)例,
表示程序使用的數(shù)據(jù)庫(kù),
同時(shí)還獲得了 Flask-SQLAlchemy提供的所有功能。
配置對(duì)象中還有一個(gè)很有用的選項(xiàng),
即SQLALCHEMY_COMMIT_ON_TEARDOWN鍵,
將其設(shè)為 True時(shí),
每次請(qǐng)求結(jié)束后都會(huì)自動(dòng)提交數(shù)據(jù)庫(kù)中的變動(dòng)。
5.6 定義模型
什么是模型
模型這個(gè)術(shù)語(yǔ)表示程序使用的持久化實(shí)體。
在 ORM 中,模型一般是一個(gè) Python 類(lèi),
類(lèi)中的屬性對(duì)應(yīng)數(shù)據(jù)庫(kù)表中的列。
Flask-SQLAlchemy 創(chuàng)建的數(shù)據(jù)庫(kù)實(shí)例
為模型提供了一個(gè)基類(lèi)以及一系列輔助類(lèi)和輔助函數(shù),
可用于定義模型的結(jié)構(gòu)。
示例5-2 hello.py:定義Role和User模型
class Role(db.Model):
__tabname__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tabname__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
def __repr__(self):
return '<User %r>' % self.username
類(lèi)變量tablename定義在數(shù)據(jù)庫(kù)中使用的表名。
如果沒(méi)有定義tablename,
Flask-SQLAlchemy 會(huì)使用一個(gè)默認(rèn)名字,
但默認(rèn)的表名沒(méi)有遵守使用復(fù)數(shù)形式進(jìn)行命名的約定,
所以最好由我們自己來(lái)指定表名。
其余的類(lèi)變量都是該模型的屬性,
被定義為 db.Column類(lèi)的實(shí)例。
表5-2 最常用的SQLAlchemy列類(lèi)型
| 類(lèi)型名 | python類(lèi)型 | 說(shuō)明 |
|---|---|---|
| Integer | int | 普通整數(shù),一般是 32 位 |
| SmallInteger | int | 取值范圍小的整數(shù),一般是 16 位 |
| BigInteger | int 或 long | 不限制精度的整數(shù) |
| Float | float | 浮點(diǎn)數(shù) |
| Numeric | decimal.Decimal | 定點(diǎn)數(shù) |
| String | str | 變長(zhǎng)字符串 |
| Text | str | 變長(zhǎng)字符串,對(duì)較長(zhǎng)或不限長(zhǎng)度的字符串做了優(yōu)化 |
| Unicode | unicode | 變長(zhǎng) Unicode 字符串 |
| UnicodeText | unicode | 變長(zhǎng) Unicode 字符串,對(duì)較長(zhǎng)或不限長(zhǎng)度的字符串做了優(yōu)化 |
| Boolean | bool | 布爾值 |
| Date | datetime.date | 日期 |
| Time | datetime.time | 時(shí)間 |
| DateTime | datetime.datetime | 日期和時(shí)間 |
| Interval | datetime.timedelta | 時(shí)間間隔 |
| Enum | str | 一組字符串 |
| PickleType | 任何 Python 對(duì)象 | 自動(dòng)使用 Pickle 序列化 |
| LargeBinary | str | 二進(jìn)制文件 |
5-3 最常用的SQLAlchemy列選項(xiàng)
| 選項(xiàng)名 | 說(shuō)明 |
|---|---|
| primary_key | 如果設(shè)為 True,這列就是表的主鍵 |
| unique | 如果設(shè)為 True,這列不允許出現(xiàn)重復(fù)的值 |
| index | 如果設(shè)為 True,為這列創(chuàng)建索引,提升查詢(xún)效率 |
| nullable | 如果設(shè)為 True,這列允許使用空值;如果設(shè)為 False,這列不允許使用空值 |
| default | 為這列定義默認(rèn)值 |
5.7 關(guān)系
一對(duì)多的關(guān)系
角色到用戶(hù)是一對(duì)多關(guān)系,
因?yàn)橐粋€(gè)角色可屬于多個(gè)用戶(hù),
而每個(gè)用戶(hù)都只能有一個(gè)角色。
示例5-3:一對(duì)多的代碼
class User(db.Model):
# ...
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
class Role(db.Model):
# ...
users = db.relationship('User', backref='role')
User中的 role_id 列被定義為外鍵(db.ForeignKey),
它的參數(shù) 'roles.id' 表明,
這列等于__tabname__為roles的id列。
對(duì)于一個(gè) Role 類(lèi)的實(shí)例,
其 users 屬性將返回與角色相關(guān)聯(lián)的用戶(hù)組成的列表。
也就是說(shuō),如果當(dāng)前類(lèi)A的實(shí)例是a,
另一個(gè)和A相關(guān)的的類(lèi)是B,
則返回所有和a相關(guān)的B的實(shí)例b。
db.relationship() 的第一個(gè)參數(shù)表明這個(gè)關(guān)系的另一端是哪個(gè)模型,
也就是B。
如果模型類(lèi)尚未定義,
可使用字符串形式指定。
backref 參數(shù)向 B類(lèi)中添加一個(gè)屬性,
從而定義反向關(guān)系。
這一屬性可替代外鍵訪(fǎng)問(wèn)B類(lèi),
此時(shí)獲取的是B的實(shí)例列表,而不是外鍵的值。
大多數(shù)情況下,
db.relationship() 都能自行找到關(guān)系中的外鍵,
但有時(shí)卻無(wú)法決定把哪一列作為外鍵。 例如,
如果 User 模型中有兩個(gè)或以上的列定義為 Role 模型的外鍵,
SQLAlchemy 就不知道該使用哪列。
如果無(wú)法決定外鍵,
你就要為 db.relationship() 提供額外參數(shù),
從而確定所用外鍵。
表5-4 常用的SQLAlchemy關(guān)系選項(xiàng)
| 選項(xiàng)名 | 說(shuō)明 |
|---|---|
| backref | 在關(guān)系的另一個(gè)模型中添加反向引用 |
| primaryjoin | 明確指定兩個(gè)模型之間使用的聯(lián)結(jié)條件。只在模棱兩可的關(guān)系中需要指定 |
| lazy | 指定如何加載相關(guān)記錄??蛇x值有 select(首次訪(fǎng)問(wèn)時(shí)按需加載)、 immediate(源對(duì)象加載后就加載)、 joined(加載記錄,但使用聯(lián)結(jié))、 subquery(立即加載,但使用子查詢(xún)),noload(永不加載)和 dynamic(不加載記錄,但提供加載記錄的查詢(xún)) |
| uselist | 如果設(shè)為 Fales,不使用列表,而使用標(biāo)量值 |
| order_by | 指定關(guān)系中記錄的排序方式 |
| secondary | 指定多對(duì)多關(guān)系中關(guān)系表的名字 |
| secondaryjoin | SQLAlchemy 無(wú)法自行決定時(shí),指定多對(duì)多關(guān)系中的二級(jí)聯(lián)結(jié)條件 |
一對(duì)一關(guān)系可以用前面介紹的一對(duì)多關(guān)系表示,
但調(diào)用 db.relationship() 時(shí)要把 uselist 設(shè)為 False,
把“多”變成“一”。
多對(duì)一關(guān)系也可使用一對(duì)多表示,
對(duì)調(diào)兩個(gè)表即可,
或者把外鍵和 db.relationship() 都放在“多”這一側(cè)。
最復(fù)雜的關(guān)系類(lèi)型是多對(duì)多,
需要用到第三張表, 這個(gè)表稱(chēng)為關(guān)系表。
你將在第 12 章學(xué)習(xí)多對(duì)多關(guān)系。
5.8 數(shù)據(jù)庫(kù)操作
5.8.1 創(chuàng)建表
>>> from hello import db
>>> db.create_all()
>>> db.drop_all()
5.8.2 插入行
>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=user_role)
這些新建對(duì)象的 id屬性并沒(méi)有明確設(shè)定,
因?yàn)橹麈I是由 Flask-SQLAlchemy 管理的。
現(xiàn)在這些對(duì)象只存在于Python 中,
還未寫(xiě)入數(shù)據(jù)庫(kù)。因此 id 尚未賦值:
>>> print(admin_role.id)
None
>>> print(mod_role.id)
None
>>> print(user_role.id)
None
通過(guò)數(shù)據(jù)庫(kù)會(huì)話(huà)管理對(duì)數(shù)據(jù)庫(kù)所做的改動(dòng),
在 Flask-SQLAlchemy 中,會(huì)話(huà)由 db.session表示。
準(zhǔn)備把對(duì)象寫(xiě)入數(shù)據(jù)庫(kù)之前,
先要將其添加到會(huì)話(huà)中:
數(shù)據(jù)庫(kù)會(huì)話(huà) db.session 和第 4 章介紹的 Flasksession 對(duì)象沒(méi)有關(guān)系。
數(shù)據(jù)庫(kù)會(huì)話(huà)也稱(chēng)為事務(wù)。
>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)
或者簡(jiǎn)寫(xiě)成:
>>> db.session.add_all([admin_role, mod_role, user_role,
... user_john, user_susan, user_david])
為了把對(duì)象寫(xiě)入數(shù)據(jù)庫(kù),
我們要調(diào)用commit()方法提交會(huì)話(huà):
db.session.commit()
再次查看 id 屬性,現(xiàn)在它們已經(jīng)賦值了:
>>> print(admin_role.id)
1
>>> print(mod_role.id)
2
>>> print(user_role.id)
3
如果在寫(xiě)入會(huì)話(huà)的過(guò)程中發(fā)生了錯(cuò)誤,
整個(gè)會(huì)話(huà)都會(huì)失效。
如果你始終把相關(guān)改動(dòng)放在會(huì)話(huà)中提交,
就能避免因部分更新導(dǎo)致的數(shù)據(jù)庫(kù)不一致性。
數(shù)據(jù)庫(kù)會(huì)話(huà)也可回滾。
調(diào)用 db.session.rollback() 后,
添加到數(shù)據(jù)庫(kù)會(huì)話(huà)中的所有對(duì)象
都會(huì)還原到它們?cè)跀?shù)據(jù)庫(kù)時(shí)的狀態(tài)。
5.8.3 修改行
在數(shù)據(jù)庫(kù)會(huì)話(huà)上調(diào)用 add() 方法也能更新模型。
下面這個(gè)例子把 "Admin" 角色重命名為 "Administrator":
>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
5.8.4 刪除行
數(shù)據(jù)庫(kù)會(huì)話(huà)還有個(gè) delete() 方法。
下面這個(gè)例子把 "Moderator" 角色從數(shù)據(jù)庫(kù)中刪除:
>>> db.session.delete(mod_role)
>>> db.session.commit()
注意,刪除與插入和更新一樣,
提交數(shù)據(jù)庫(kù)會(huì)話(huà)后才會(huì)執(zhí)行。
5.8.5 查詢(xún)行
Flask-SQLAlchemy 為每個(gè)模型類(lèi)都提供了 query 對(duì)象。
最基本的模型查詢(xún)是取回對(duì)應(yīng)表中的所有記錄:
>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
>>> User.query.all()
[<User u'john'>, <User u'susan'>, <User u'david'>]
使用過(guò)濾器可以配置 query 對(duì)象進(jìn)行更精確的數(shù)據(jù)庫(kù)查詢(xún)。
下面這個(gè)例子查找角色為"User" 的所有用戶(hù):
>>> User.query.filter_by(role=user_role).all()
[<User u'susan'>, <User u'david'>]
若要查看 SQLAlchemy 為查詢(xún)生成的原生 SQL 查詢(xún)語(yǔ)句,
只需把 query 對(duì)象轉(zhuǎn)換成字符串:
>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username,
users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'
如果你退出了 shell 會(huì)話(huà),
前面這些例子中創(chuàng)建的對(duì)象就不會(huì)以 Python 對(duì)象的形式存在,
而是作為各自數(shù)據(jù)庫(kù)表中的行。
下面這個(gè)例子發(fā)起了一個(gè)查詢(xún),
加載名為 "User" 的用戶(hù)角色:
>>> user_role = Role.query.filter_by(name='User').first()
filter_by() 等過(guò)濾器在 query 對(duì)象上調(diào)用,
返回一個(gè)更精確的 query 對(duì)象。
多個(gè)過(guò)濾器可以一起調(diào)用,直到獲得所需結(jié)果。
表5-5 常用的SQLAlchemy查詢(xún)過(guò)濾器
| 過(guò)濾器 | 說(shuō)明 |
|---|---|
| filter() | 把過(guò)濾器添加到原查詢(xún)上,返回一個(gè)新查詢(xún) |
| filter_by() | 把等值過(guò)濾器添加到原查詢(xún)上,返回一個(gè)新查詢(xún) |
| limit() | 使用指定的值限制原查詢(xún)返回的結(jié)果數(shù)量,返回一個(gè)新查詢(xún) |
| offset() | 偏移原查詢(xún)返回的結(jié)果,返回一個(gè)新查詢(xún) |
| order_by() | 根據(jù)指定條件對(duì)原查詢(xún)結(jié)果進(jìn)行排序,返回一個(gè)新查詢(xún) |
| group_by() | 根據(jù)指定條件對(duì)原查詢(xún)結(jié)果進(jìn)行分組,返回一個(gè)新查詢(xún) |
在查詢(xún)上應(yīng)用指定的過(guò)濾器后,
通過(guò)調(diào)用 all() 執(zhí)行查詢(xún),
以列表的形式返回結(jié)果。
除了all() 之外,還有其他方法能觸發(fā)查詢(xún)執(zhí)行。
表 5-6 列出了執(zhí)行查詢(xún)的其他方法。
表5-6 常用的SQLAlchemy查詢(xún)執(zhí)行函數(shù)
| 方法 | 說(shuō)明 |
|---|---|
| all() | 以列表形式返回查詢(xún)的所有結(jié)果 |
| first() | 返回查詢(xún)的第一個(gè)結(jié)果,如果沒(méi)有結(jié)果,則返回 None |
| first_or_404() | 返回查詢(xún)的第一個(gè)結(jié)果,如果沒(méi)有結(jié)果,則終止請(qǐng)求,返回 404 錯(cuò)誤響應(yīng) |
| get() | 返回指定主鍵對(duì)應(yīng)的行,如果沒(méi)有對(duì)應(yīng)的行,則返回 None |
| get_or_404() | 返回指定主鍵對(duì)應(yīng)的行,如果沒(méi)找到指定的主鍵,則終止請(qǐng)求,返回 404 錯(cuò)誤響應(yīng) |
| count() | 返回查詢(xún)結(jié)果的數(shù)量 |
| paginate() | 返回一個(gè) Paginate 對(duì)象,它包含指定范圍內(nèi)的結(jié)果 |
>>> users = user_role.users
>>> users
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>
這個(gè)例子中的 user_role.users 查詢(xún)有個(gè)小問(wèn)題。
執(zhí)行 user_role.users 表達(dá)式時(shí),
隱含的查詢(xún)會(huì)調(diào)用 all() 返回一個(gè)用戶(hù)列表。
query 對(duì)象是隱藏的,
因此無(wú)法指定更精確的查詢(xún)過(guò)濾器。
在示例5-4 中,我們修改了關(guān)系的設(shè)置,
加入了 lazy = 'dynamic' 參數(shù),
從而禁止自動(dòng)執(zhí)行查詢(xún)。
示例5-4 hello.py:動(dòng)態(tài)關(guān)系
class Role(db.Model):
# ...
users = db.relationship('User', backref='role', lazy='dynamic')
# ...
這樣配置關(guān)系之后,
user_role.users 會(huì)返回一個(gè)尚未執(zhí)行的查詢(xún),
因此可以在其上添加過(guò)濾器:
>>> user_role.users.order_by(User.username).all()
[<User u'david'>, <User u'susan'>]
>>> user_role.users.count()
2
5.9 在視圖函數(shù)中操作數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)操作可以直接在視圖函數(shù)中進(jìn)行,
示例 5-5 展示了首頁(yè)路由的新版本,
已經(jīng)把用戶(hù)輸入的名字寫(xiě)入了數(shù)據(jù)庫(kù)。
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
if user is None:
user = User(username=form.name.data)
db.session.add(user)
session['known'] = False
else:
session['known'] = True
session['name'] = form.name.data
form.name.data = ''
return redirect(url_for('index'))
return reder_template('index.html',
form = form, name = session.get('name'),
known = session.get('known', False))
在這個(gè)修改后的版本中,
提交表單后,
程序會(huì)使用 filter_by() 查詢(xún)過(guò)濾器在數(shù)據(jù)庫(kù)中查找提交的名字。
變量 known 被寫(xiě)入用戶(hù)會(huì)話(huà)中,
因此重定向之后,可以把數(shù)據(jù)傳給模板,
用來(lái)顯示自定義的歡迎消息。
示例5-6 templates/index.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
<p>Happy to see you again!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
5.10 集成Python shell
次啟動(dòng) shell 會(huì)話(huà)都要導(dǎo)入數(shù)據(jù)庫(kù)實(shí)例和模型,
這真是份枯燥的工作。
為了避免一直重復(fù)導(dǎo)入,
我們可以做些配置,
讓 Flask-Script 的 shell 命令自動(dòng)導(dǎo)入特定的對(duì)象。
若想把對(duì)象添加到導(dǎo)入列表中,
我們要為 shell 命令注冊(cè)一個(gè) make_context 回調(diào)函數(shù),
如示例 5-7 所示。
示例 5-7 hello.py: 為 shell 命令添加一個(gè)上下文
from flask_script import Shell
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
manager.add_command("shell", Shell(make_context=make_shell_context))
make_shell_context() 函數(shù)注冊(cè)了程序、數(shù)據(jù)庫(kù)實(shí)例以及模型,
因此這些對(duì)象能直接導(dǎo)入 shell:
$ python hello.py shell
>>> app
<Flask 'app'>
>>> db
<SQLAlchemy engine='sqlite:////home/flask/flasky/data.sqlite'>
>>> User
<class 'app.User'>
5.11 使用alembic數(shù)據(jù)庫(kù)遷移
安裝
Alembic是SQLAlchemy作者編寫(xiě)的Python數(shù)據(jù)庫(kù)遷移工具。
通過(guò)pip安裝,會(huì)自動(dòng)安裝依賴(lài)包SQLAlchemy、Mako和MarkupSafe。
pip install alembic
安裝完成后就可以使用alembic命令,
所有Alembic操作均由該命令實(shí)現(xiàn)(類(lèi)似Git)。
初始化
cd到你的應(yīng)用程序的路徑,
然后在命令行輸入:
alembic init alembic
第一個(gè)alembic是聲明要使用alembic的命令,
第二個(gè)alembic可以隨便命名,是用來(lái)存放數(shù)據(jù)庫(kù)環(huán)境配置文件的。
輸入命令后,數(shù)據(jù)庫(kù)環(huán)境創(chuàng)建完成。
這時(shí)在目錄下出現(xiàn)了一個(gè)alembic的文件夾。
在進(jìn)行初始化時(shí),
根據(jù)使用多數(shù)據(jù)庫(kù)、單數(shù)據(jù)庫(kù)或者pylons,
選擇不同的選項(xiàng),默認(rèn)是單數(shù)據(jù)庫(kù)。
如果要使用pylons項(xiàng)目,可以使用下列命令:
alembic init --template pylons ./scripts
配置數(shù)據(jù)庫(kù)地址
在alembic的同級(jí)目錄下,
同樣生成了一個(gè)alembic.ini配置文件。
當(dāng)alembic腳本激活時(shí),
會(huì)查找這個(gè)配置文件的選項(xiàng)并應(yīng)用。
也可以利用--config選項(xiàng)來(lái)指定.ini文件的位置。
在初始化結(jié)束后,需要配置數(shù)據(jù)庫(kù)連接URL,
在alembic.ini中設(shè)置:
sqlalchemy.url = sqlite:///data.sqlite
sqlite:////是絕對(duì)地址,sqlite:///是相對(duì)地址。
配置自動(dòng)遷移
在alembic文件夾的env.py中,
有一個(gè)選項(xiàng)是target_metadata = None。
修改這個(gè)選項(xiàng)可以自動(dòng)生成遷移腳本,
我們將它修改為:
from app import db
target_metadata = db.metadata
但運(yùn)行腳本時(shí)會(huì)提示還不知道app模塊的位置。
我們?cè)谶@兩行代碼前,把a(bǔ)pp的位置添加到系統(tǒng)環(huán)境變量。
import os
import sys
root = os.path.dirname(__file__) + '/../'
print(root)
sys.path.append(root)
現(xiàn)在就可以使用自動(dòng)遷移選項(xiàng)了。
自動(dòng)遷移檢查哪些數(shù)據(jù)庫(kù)變化
自動(dòng)遷移會(huì)檢查
- Table的增加、刪除
- Column的增加、刪除
- Column的空狀態(tài)變化
- Index的基本變化和明確命名的UniqueConstraint 0.6.1增加
- ForeignKey constraint的基本變化 0.7.1增加
可選擇性的檢查
- Column類(lèi)型的變化
- Server default的更改
要檢測(cè)Column類(lèi)型的變化,
需要把python包中的alembic/runtime/evironment.py中的
configure函數(shù)的compare_type設(shè)為T(mén)rue。
不會(huì)檢查
- Tablename的改變
- Column name的改變
- 匿名constraints
- 特殊的SQLAlchemy類(lèi)型,例如Enum在不支持的引擎中使用
創(chuàng)建版本
用alembic revision --autogenerate -m "注釋"來(lái)創(chuàng)建數(shù)據(jù)庫(kù)版本。
自動(dòng)遷移和手動(dòng)遷移的區(qū)別在于,
是否自動(dòng)在遷移腳本中提供upgrade和downgrade函數(shù)的功能,
這兩個(gè)函數(shù)用來(lái)升級(jí)或者回滾數(shù)據(jù)庫(kù)版本。
加上--autogenerate就是自動(dòng)遷移,否則是手動(dòng)遷移。
遷移腳本位于alembic文件夾下的revision文件夾中,
以header信息加注釋的形式命名。
升級(jí)和回滾
使用alembic upgrade進(jìn)行升級(jí),
使用alembic downgrade進(jìn)行回滾。
例如:
$ alembic upgrade head
刪除歷史記錄
Alembic在數(shù)據(jù)庫(kù)中僅保存當(dāng)前版本號(hào),
其余信息均從文件讀取。
刪除歷史記錄,
只需要將數(shù)據(jù)庫(kù)表刪除,
并刪除versions下的所有文件。
而alembic.ini和env.py中的設(shè)置無(wú)需更改,
可以再次使用。