chapter 2 - chapter 3 - chapter 4 - 源碼
概念剖析-flask數(shù)據(jù)庫(kù)操作
數(shù)據(jù)庫(kù)分類
SQL數(shù)據(jù)庫(kù)
基于關(guān)系模型拯坟,用表來(lái)模擬不同的實(shí)體磅叛,列定義代表所屬實(shí)體的數(shù)據(jù)屬性馍刮,每個(gè)表有個(gè)特殊的列稱為 主鍵慷丽,其值是各行的唯一標(biāo)識(shí)符赎瑰,不能重復(fù)召庞,表中還有 外鍵锡垄,引用同一表或不同表的主鍵沦零,這種聯(lián)系稱為 關(guān)系。 特點(diǎn):支持聯(lián)結(jié)操作货岭,數(shù)據(jù)存儲(chǔ)高效路操,數(shù)據(jù)一致性好,避免重復(fù)千贯,但是設(shè)計(jì)比較復(fù)雜私痹。常見(jiàn):MySQL, Oracal, SQLiteNoSQL數(shù)據(jù)庫(kù)
使用 集合 代替表雳殊,使用 文檔 代替記錄钞护,特點(diǎn)數(shù)據(jù)重復(fù)查詢效率高驶沼,一致性差。常見(jiàn):MongoDB
SQL數(shù)據(jù)庫(kù)
# 表 roles 表 users
# ------------------ ------------------
# id : 主鍵 id : 主鍵
# name username
# password
# role_id : 外鍵
# ------------------ ------------------
NoSQL 數(shù)據(jù)庫(kù)
# users
# -----------------
# id
# username
# password
# role: 有大量重復(fù)
# ------------------
python數(shù)據(jù)庫(kù)框架
- 數(shù)據(jù)引擎的Python包 大多數(shù)的數(shù)據(jù)庫(kù)引擎都有對(duì)應(yīng)的Python包敦第。
- 數(shù)據(jù)庫(kù)抽象層 如
SQLAlchemy
和MongoEngine
通過(guò)ORM(Object-Relational Mapper)
或者ODM(Object-Document Mapper)
將處理表峰弹、文檔或查詢語(yǔ)言等數(shù)據(jù)庫(kù)實(shí)體操作轉(zhuǎn)換成為高層的Python對(duì)象的操作,極大地提升開(kāi)發(fā)效率芜果。- 數(shù)據(jù)庫(kù)框架評(píng)價(jià)指標(biāo):易用性鞠呈,性能,可移植性师幕。Flask集成度粟按。本書(shū)使用的是 Flask-SQLAlchemy
使用 Flask-SQLAlchemy
管理數(shù)據(jù)庫(kù)
- 安裝
pip install flask-sqlalchemy
- 通過(guò)URL指定數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)引擎 | URL |
---|---|
MYSQL | mysql://uername:password@hostname/database |
SQLite(Unix) | sqlite:///absolute/path/to/database |
SQLite(Win) | sqlite:///c:/absolute/path/to/database |
設(shè)置 SQLlite 數(shù)據(jù)庫(kù)的鏈接
hello.py
文件, 設(shè)置app.config['SQLALCHEMY_DATABASE_URI']
,app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']
## 設(shè)置 SQLite 數(shù)據(jù)庫(kù) URI
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir,'data.sqlite')
## 每次請(qǐng)求提交后,自動(dòng)提交數(shù)據(jù)庫(kù)的修改
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
## 獲取數(shù)據(jù)庫(kù)對(duì)象
db = SQLAlchemy(app)
定義模型
模型表示程序使用的持久化實(shí)體霹粥,在
ORM
中灭将,模型一般是一個(gè) Python 類,類的屬性對(duì)應(yīng)數(shù)據(jù)表中的列后控,類的操作會(huì)在底層自動(dòng)轉(zhuǎn)換成為對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作庙曙。
hello.py
中,定義 Role 和 User 模型
# 定 Role 模型
class Role(db.Model):
""" database table class Role """
# 表名浩淘,一般采用 復(fù)數(shù) 形式
__tablename__ = 'roles'
# 類變量即數(shù)據(jù)表的字段捌朴,由 db.Column創(chuàng)建
# primary_key = True 定義主鍵
# unique = True 不允許出現(xiàn)重復(fù)的值
id = db.Column(db.Integer, primary_key = True )
name = db.Column(db.String(64), unique = True )
# 返回表示模型的字符串,供調(diào)試和測(cè)試使用
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
""" database table class User """
__tablename__ = '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
創(chuàng)建模型之間的關(guān)系
hello.py
文件中张抄,User中定義外鍵 role_id
的同時(shí)定義了關(guān)系砂蔽,可以在關(guān)系的另一端Role
中定義關(guān)系,添加反向引用署惯。
class User(db.Model):
...
# 創(chuàng)建外鏈左驾,同時(shí)創(chuàng)建了關(guān)系,引用 表 roles 的 id 字段
role_id = db.Column(db.Integer, db.ForeignKey( 'roles.id' ) )
...
class Role(db.Model):
...
# backref 在關(guān)系的另一個(gè)模型中极谊,添加反向引用
# 添加到 Role 中的 users 屬性代表了關(guān)系的面向?qū)ο笠暯牵?# 將返回與角色相關(guān)聯(lián)的用戶的列表诡右,第一個(gè)參數(shù) 用字符串表示關(guān)系另一端的模型
# backref='role' 向User類添加了 role 屬性, role_id 返回的是外鍵的值,
# role返回的是模型Role的對(duì)象
users = db.relationship('User', backref='role')
...
數(shù)據(jù)庫(kù)操作
與git
的操作十分相似
-
創(chuàng)建數(shù)據(jù)表
db.create_all()
創(chuàng)建sqlite
數(shù)據(jù)庫(kù)文件data.sqlite
轻猖,改動(dòng)模型后帆吻,更新數(shù)據(jù)庫(kù)時(shí)只能刪除舊表db.drop_all()
,然后重新db.create_all()
創(chuàng)建咙边,但是會(huì)導(dǎo)致原有數(shù)據(jù)的丟失猜煮。 -
插入記錄 只需要調(diào)用模型的關(guān)鍵字參數(shù)形式的構(gòu)造函數(shù),如
admin_role = Role(name='Admin)'
败许,主鍵id
由Flask-SQLAlchemy
管理友瘤,不需要明確設(shè)置 -
同步模型改動(dòng)到數(shù)據(jù)庫(kù) 使用
db.session
數(shù)據(jù)庫(kù)事務(wù)對(duì)象管理對(duì)數(shù)據(jù)庫(kù)的改動(dòng),如添加記錄到數(shù)據(jù)庫(kù)db.session.add(admin_role)
-
提交操作
db.session.commit()
-
修改記錄 首先修改模型對(duì)象屬性值檐束,然后
db.session.add(), db.session.commit()
-
刪除記錄 首先
db.session.delete( model obj)
然后提交到倉(cāng)庫(kù)db.session.commit()
hello.py
文件添加
# 數(shù)據(jù)庫(kù)對(duì)象的創(chuàng)建及初始化
def Create_database():
# 創(chuàng)建數(shù)據(jù)庫(kù)文件及表辫秧,
# ? 程序如何識(shí)別所有需要?jiǎng)?chuàng)建數(shù)據(jù)表的對(duì)象 ?
db.create_all()
# 插入行
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 )
# 添加到會(huì)話
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 )
# db.session.add_all( [admin_role, mod_role, user_role, user_john , user_susan, user_david] )
# 提交到數(shù)據(jù)庫(kù)
db.session.commit()
# db.session.rollback() 將添加到數(shù)據(jù)庫(kù)會(huì)話中的所有對(duì)象還原到他們?cè)跀?shù)據(jù)庫(kù)中的狀態(tài),相當(dāng)于git中的checkout
# 刪除數(shù)據(jù)
# db.session.delete(mod_role)
# db.session.commit()
數(shù)據(jù)庫(kù)的查詢query
對(duì)象
SQLALchemy-查詢篇
Python SQLAlchemy基本操作和常用技巧
Flask-SQLAlchemy
為每個(gè)模型類都提供一個(gè)query
對(duì)象被丧,
而 過(guò)濾器 在query
上調(diào)用盟戏,返回更加精確的query
對(duì)象 ,過(guò)濾器返回query
對(duì)象
利用關(guān)系創(chuàng)建的模型對(duì)象1 *
User.query.filter_by(role=user_role).all()
甥桂,role是關(guān)系*添加的模型對(duì)象柿究,str( User.query.filter_by(role=user_role))
查詢ORM自動(dòng)生成的查詢語(yǔ)句
利用關(guān)系創(chuàng)建的模型對(duì)象2user_role.users
默認(rèn)情況下返回的是query
對(duì)象的查詢結(jié)果,設(shè)置lazy = dynamic
可以得到查詢對(duì)象
cmd 中 shell 方式執(zhí)行查找:
>>> python hello.py shell
>>> from hello import db, Role, User
>>> User.query.all()
[<User 'john'>, <User 'susan'>, <User 'david'>]
git add. git commit -m "sqlalchemy first demo"
視圖函數(shù)中調(diào)用數(shù)據(jù)庫(kù)
hello.py
文件
# 路由 index
@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)
# add 到 session
db.session.add(user)
session['known'] = False
else:
session['known'] = True
session['name'] = form.name.data
return redirect( url_for('index'))
return render_template('index.html', form=form, name=session.get('name'), known=session.get('known', False))
git add. git commit -m "sqlalchemy in view "
, git tag 5a
集成 Python shell
可以通過(guò)Shell 來(lái)調(diào)試模塊黄选,據(jù)說(shuō)蝇摸,文檔
每次啟動(dòng) shell 都要導(dǎo)入數(shù)據(jù)庫(kù)實(shí)例和模型婶肩,通過(guò)設(shè)置讓Flask-Script
的shell
自動(dòng)導(dǎo)入特定的對(duì)象
from flask_script import shell
# 創(chuàng)建 shell 的上下文環(huán)境
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
# 配置 manager 的命令行
manager.add_command("shell", Shell(make_context = make_shell_context))
數(shù)據(jù)庫(kù)表的修改
開(kāi)發(fā)過(guò)程中有時(shí)候需要修改數(shù)據(jù)庫(kù)模型,并更新到數(shù)據(jù)庫(kù)貌夕,需要用到數(shù)據(jù)庫(kù)遷移框架律歼,推薦
Alembic
,及Flask - Migrate
擴(kuò)展(封裝了Alembic
)啡专,所有的操作通過(guò)Flask-Script
完成
- 安裝
pip install flask-migrate
-
hello.py
文件修改
from flask_migrate import Migrate, MigrateCommand
...
# 創(chuàng)建數(shù)據(jù)庫(kù)遷移對(duì)象
Migrate(app, db)
# 配置 flask_script 命令
manager.add('db', MigrateCommand)
創(chuàng)建遷移倉(cāng)庫(kù)
>>> python hello.py db init
险毁,會(huì)創(chuàng)建migrations
文件夾,放置所有的遷移腳本们童,遷移倉(cāng)庫(kù)中的要進(jìn)行版本控制-
數(shù)據(jù)庫(kù)的遷移用 遷移腳本 表示畔况,腳本中有兩個(gè)函數(shù)
upgrade()
和downgrade()
前者應(yīng)用修改,后者刪除修改慧库,數(shù)據(jù)庫(kù)可以重置到修改歷史的任一點(diǎn)跷跪。-
revision
手動(dòng)創(chuàng)建遷移,upgrade()
和downgrade()
都是空的齐板,開(kāi)發(fā)者需要使用Operations
對(duì)象指令實(shí)現(xiàn)具體操作 -
migrate
命令自動(dòng)創(chuàng)建域庇,會(huì)根據(jù)模型定義和數(shù)據(jù)庫(kù)當(dāng)前狀態(tài)之間的差異自動(dòng)生成upgrade()
和downgrade()
的內(nèi)容
-
此處沒(méi)有修改數(shù)據(jù)庫(kù),只是刪除數(shù)據(jù)庫(kù)
data.sqlite
, 自動(dòng)創(chuàng)建遷移腳本>>> python hello.py db migrate -m "initial migration"
, 在$project-dir \ migrations \ versions
出現(xiàn)文件ab116a6e32ed_initial_migration.py
內(nèi)容如下:
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('roles',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=64), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=64), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_table('users')
op.drop_table('roles')
# ### end Alembic commands ###
- 應(yīng)用遷移命令覆积,
>>>python hello.py db upgrade
輸出Running upgrade -> ab116a6e32ed, initial migrateion
, 刪除遷移>>>python hello.py db downgrade
輸出Running downgrade ab116a6e32ed -> , initial migrateion
PROBELM 前文說(shuō)道听皿,遷移
而不是刪除表格,重新創(chuàng)建表格
能夠保存數(shù)據(jù)庫(kù)中的數(shù)據(jù)宽档,那么如何指定新列于舊列的對(duì)應(yīng)關(guān)系尉姨,及其他更精細(xì)的操作?
git add. git commit "flask database demo"
, git tag 5b
附錄
常用的SQLAlchemy 的列類型
類型名稱 | python類型 | 描述 |
---|---|---|
Integer | int | 常規(guī)整形吗冤,通常為32位 |
SmallInteger | int | 短整形又厉,通常為16位 |
BigInteger | int或long | 精度不受限整形 |
Float | float | 浮點(diǎn)數(shù) |
Numeric | decimal.Decimal | 定點(diǎn)數(shù) |
String | str | 可變長(zhǎng)度字符串 |
Text | str | 可變長(zhǎng)度字符串,適合大量文本 |
Unicode | unicode | 可變長(zhǎng)度Unicode字符串 |
Boolean | bool | 布爾型 |
Date | datetime.date | 日期類型 |
Time | datetime.time | 時(shí)間類型 |
Interval | datetime.timedelta | 時(shí)間間隔 |
Enum | str | 字符列表 |
PickleType | 任意Python對(duì)象 | 自動(dòng)Pickle序列化 |
LargeBinary | str | 二進(jìn) |
常用的 SQLAlchemy 的列選項(xiàng)
可選參數(shù) | 描述 |
---|---|
primary_key | 如果設(shè)置為T(mén)rue椎瘟,則為該列表的主鍵 |
unique | 如果設(shè)置為T(mén)rue覆致,該列不允許相同值 |
index | 如果設(shè)置為T(mén)rue,為該列創(chuàng)建索引肺蔚,查詢效率會(huì)更高 |
nullable | 如果設(shè)置為T(mén)rue煌妈,該列允許為空。如果設(shè)置為False宣羊,該列不允許空值 |
default | 定義該列的默認(rèn)值 |
常用的 SQLAlchemy 的關(guān)系選項(xiàng)
選項(xiàng)名 | 說(shuō)明 |
---|---|
backref | 在關(guān)系的另一個(gè)模型中添加反向引用 |
primaryjoin | 明確指定另個(gè)模型之間使用的關(guān)系的聯(lián)結(jié)條件 |
lazy | 指定如何加載相關(guān)記錄璧诵,可選項(xiàng)有 select( 首次訪問(wèn)按需加載 ),immediate( 源對(duì)象加載后立即加載 )仇冯,joined( 加載記錄且使用聯(lián)結(jié) )之宿,subquery( 立即加載,使用子查詢 )苛坚,noload( 永不加載 )比被,dynamic( 不加載記錄色难,提供加載記錄的查詢 ) |
uselist | 默認(rèn)為真,對(duì)應(yīng)一對(duì)多等缀,返回列表枷莉,若為 False 對(duì)應(yīng)一對(duì)一,返回標(biāo)量值 |
order_by | 關(guān)系中記錄的排序方式 |
secondary | 指定 多對(duì)多 關(guān)系中關(guān)系表的名字 |
secondaryjoin | 指定 多對(duì)多 關(guān)系中 二級(jí)聯(lián)結(jié) 條件 |
常用的 SQLAlchemy 查詢過(guò)濾器
過(guò)濾器 | 說(shuō)明 |
---|---|
filter() |
將過(guò)濾器添加到原查詢上项滑,返回新查詢 |
filter_by() |
將等值過(guò)濾器添加到原查詢上,返回新查詢 |
limit() |
使用指定的值限制返回的結(jié)果數(shù)量涯贞,返回新查詢 |
offset() |
偏移原查詢的結(jié)果枪狂,返回新查詢 |
oredr_by() |
采用指定條件對(duì)原查詢的結(jié)果排序,返回新查詢 |
group_by |
采用指定條件對(duì)原查詢結(jié)果進(jìn)行分組宋渔,返回新查詢 |
常用的 query 對(duì)象的操作
操作 | 說(shuō)明 |
---|---|
all() |
列表形式返回所有查詢結(jié)果 |
first() |
返回查詢結(jié)果的第一個(gè)州疾,如果沒(méi)有返回None
|
first_or_404() |
返回查詢結(jié)果的第一個(gè),如果沒(méi)有終止請(qǐng)求皇拣,返回404 |
get() |
返回主鍵對(duì)應(yīng)的行严蓖,如果沒(méi)有返回 None
|
get_or_404() |
返回主鍵對(duì)應(yīng)的行,如果沒(méi)有氧急,終止請(qǐng)求颗胡,返回404 |
count() |
返回查詢結(jié)果的數(shù)量 |
paginate() |
paginate (為書(shū)或者手稿標(biāo)頁(yè)數(shù)),返回一個(gè)paginate 對(duì)象吩坝,包含指定范圍的結(jié)果 |