chapter5 - Web資料庫
5.1 Python資料庫框架
Flask允許自己選擇需要的資料庫框架,但在選擇時,應考慮這些因素:
- 易用性 這裡要注重的兩個概念是ORM或ODM,也就是對象關係映射和對象文檔映射。它們用於把高層的面向對象操作轉換成低層的資料庫指令。
- 性能
- 可移植性 是否可在多個平台中平移
- Flask集成度 使用集成了Flask的框架可以簡化配置和操作
因此本書選擇的是Flask-SQLAlchemy,這個Flask擴展包裝了SQLAlchemy框架
5.2 使用Flask-SQLAlchemy管理資料庫
安裝
可使用pip安裝:(venv) $ pip install flask-sqlalchemy。在Flask-SQLAlchemy中,資料庫使用URL指定,也就是以URL的形式來連接資料庫,而不是sql原生的connect之類的操作。具體格式如下表:
資料庫引擎URLMySQLmysql://username:password@hostname/databasePostgrespostgresql://userSQLite (Unix)sqlite:////absolute/path/to/databaseSQLite (Windows)sqlite:///c:/absolute/path/to/database
hostname表示MySQL服務所在的主機,可以是本地主機或遠程伺服器 database表示要使用的資料庫名稱 username和password表示需要有到的資料庫用戶密令
本書使用的是SQLite資料庫,它不需要伺服器,因此URL中的database是硬碟上的文件名
配置資料庫
程序使用的資料庫URL必須保存到Flask配置對象的SQLALCHEMY_DATABASE_URI中,另外還有個要設置的是SQLALCHEMY_COMMIT_ON_TEARDOWN鍵,將其設為True時,每次執行SQL請求後會自動提交。
經個人實測發現,如果僅做了以上兩個設置,在運行時會有警告:
/home/cavin/Code/Python/flask/lib64/python3.5/site-packages/flask_sqlalchemy/__init__.py:800: UserWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True to suppress this warning. warnings.warn(SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True to suppress this warning.)
按提示說的,把這個變數配置成True即可消除這個警告:
app.config[SQLACHEMY_TRACK_MODIFICANTS] = True
因此,配置後的hello.py文件為:
from flask_sqlalchemy import SQLAlchemybasedir = 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] = Trueapp.config[SQLALCHEMY_TRACK_MODIFICANTS] = Truedb = SQLAlchemy(app)
注意以上(除了app = Flask(__name__))外都是新添加到文件中的內容,其它內容不變basedir變數可以獲取當前文件的絕對路徑。 db是SQLAlchemy類的一個實例,表示程序使用的資料庫,同時也獲得了Flask-SQLAlchemy提供的所有功能。
5.3 定義模型
其實這裡所說到的「模型」,在某種程度上可看做是傳統SQL中的「數據表」。如下可定義Role和User模型:
class Role(db.Model): __tablename__ = roles id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) def __repr__(self): return <Role %r> % self.nameclass User(db.Model): __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
其中,__tablename__定義了在資料庫中使用的表名。其餘的id、name、username這些都是模型的屬性,相當於數據表中的欄位,用db.Column構造函數實現,其第一個參數是資料庫列和模型屬性的類型。模型中的__repr()__方法並不是強制要求的,這裡定義了並返回一個具有可讀性的字元串表示模型,可方便調試和測試。 常用的SQLAlchemy列類型如下:
類型名Python類型說明Integerint普通整數,一般是32位SmallIntegerint取值範圍小的整數,一般是16位BigIntegerint或long不限制精度的整數Floatfloat浮點數Numericdecimal.Decimal定點數Stringstr變長字元串Textstr變長字元串,對較長或不限長度的字元串做了優化Unicodeunicode變長Unicode字元串UnicodeTextunicode變長Unicode字元串,對較長或不限長度的字元串做了優化Booleanbool布爾值Datedatetime.date日期Timedatetime.time時間DateTimedatetime.datetime日期和時間Intervaldatetime.timedelta時間間隔Enumstr一組字元串PickleType任何Python對象自動使用Pickle序列化LargeBinarystr二進位文件
db.Column中其餘的參數指定屬性的配置選項,常用的可選項如下:
選項名說明primary_key如果設置為True,這列就是表的主鍵unique如果設置為True,這列不允許出現復生值index如果設置為True,為這列創建索引nullable如果設置為True,這列允許為空;為False則不允許空default為這列定義默認值Flask-SQLAlchemy要求每個模型都要定義主鍵,通常命名為id
5.4 關係
關係也即不同表之間的聯繫。例如,一個論壇中的用戶有其自己的id,其歸屬於一個分組,這個組也有組的id,用戶信息表和分組表可以通這兩個各自的id來關聯起來。一個組可以有多個用戶,但一個用戶同時只能歸屬於一個組。這就是一對多的關係。 在模型中表示如下: 修改hello.py
class Role(db.Model): # 之前的內容 users = db.relationship(User, backref=role)class User(db.Model): # 之前的內容 role_id = db.Column(db.Integer, db.ForeignKey(roles.id))
User模型中的role_id表被定義為外鍵,建立起了關係,傳給db.ForeignKey()的參數roles.id表明,這列的值是roles表中的id值。 在Role模型中,users屬性返回與角色相關聯的用戶組成的列表,db.relationship()的第一個參數表明這個關係連接的是哪一個表。第二個參數backref向User模型添加了一個role屬性,定義了反向關係。 定義關係時(db.relationship())常用的配置選項有:
選項名說明backref在關係的另一個模型中添加反向引用primaryjoin明確指定兩個模型之間使用的聯結條件lazy指定如何載入相關記錄。可選值有select-首次訪問時按需載入;immediate-源對象載入後就載入;joined-載入記錄,但使用聯結;subquery-立即載入,但使用子查詢;noload-永不載入、dynamic-不載入記錄,但提供載入記錄的查詢uselist如果設為False,不使用列表,而使用標量值order_by指定關係中記錄的排序方式secondary指定多對多關係中關係表的名字secondaryjoinSQLAlchemy無法自行決定時,指定多對多關係中的二級聯結條件5.5 資料庫操作
5.5.1 創建表
(venv) $ python hello.py shell>>> from hello import db>>> db.create_all()
此時,就可在程序目錄中新建了一個名為data.sqlite的文件。
5.5.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)
此時,這些對象的id屬性並沒有被明確設定,因為主鍵是Flask-SQLAlchemy管理的。而且這些對象目前也只存在於Python中,並沒有寫入到資料庫。可以通過資料庫會話來管理這些改動。在Flask-SQLAlchemy中,會話由db.session表示。在寫入資料庫前,要先把這些對象添加到會話中:
>>> 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])
寫入資料庫:
>>> db.session.commit()
應注意的是,這裡的資料庫會話db.session和第4章中提到的Flask session對象沒有關係,它只是資料庫會話,或稱為事務。資料庫會話可以保持資料庫操作的一致性。 當然,它也有回滾:db.session.rollback()
5.5.3 修改行
繼續在前面的shell會話中操作。把Admin角色重命名為Administrator
>>> admin_role.name = Administrator>>> db.session.add(admin_role)>>> db.session.commit()
5.5.4 刪除行
如下可刪除Moderator
>>> db.session.delete(mod_role)>>> db.session.commit()
5.5.5 查詢行
Flask-SQLAlchemy為每個模型類都提供了query對象。
- 查詢所有記錄 相當於原生SQL語句的select * from
>>> Role.query.all()[<Role uAdministrator>, <Role uUser>]
- 使用過濾器 相當於原生SQL語句中的where 查找角色為User的所有用戶:
>>> User.query.filter_by(role=user_role).all()[<User ususan>, <User udavid>]
/* 2017年3月16日,更新資料庫遷移說明 */
5.6 使用Flask-Migrate實現資料庫遷移
所謂資料庫遷移,其實主要是資料庫(包含其結構與欄位屬性等)的更新。比如說新增了表、欄位等。使用Flask-Migrate可以很好地實現資料庫平移更新,不改變原有結構和數據,也不需要刪除重建。
5.6.1 創建遷移倉庫
先要安裝Flask-Migrate:
(venv) $ pip install flask-migrate
在程序入口文件hello.py中增加對資料庫遷移的支持:
from flask_migrate import Migrate, MigrateCommand# .其它原有代碼migrate = Migrate(app, db)manager.add_command(db, MigrateCommand)# 其它代碼
然後才是使用init子命令來創建遷移倉庫:
(venv) $ python hello.py db init
這個命令會創建migrations文件夾,所有的遷移腳本都會放在裡面
5.6.2 創建遷移腳本
(venv) $ python hello.py db migrate -m "initial migration"
5.6.3 更新資料庫
(venv) $ python hello.py db update
推薦閱讀:
※有了danmu,二十行代碼輕鬆愉快對彈幕進行二次開發
※Python GUI教程(七):轉換qt設計師的ui代碼為Python代碼
※利用 pwntools 編寫 socket 腳本