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

這裡應該要提出的是,在本書後面,作者在需要進行更新資料庫的時候,只會提到需要讀者執行更新資料庫的命令:python hello.py update,卻沒有說到需要執行創建遷移腳本命令。這是因為作者默認讀者是從本書的github倉庫上pull代碼的,而pull下來的代碼裡面已經包含有資料庫遷移腳本了,所以不需要再創建。如果是自己更新代碼而不是從github上下載代碼的話,那麼就必須要先執行創建遷移腳本的命令,然後再執行更新資料庫的命令才會有效。切記!


推薦閱讀:

有了danmu,二十行代碼輕鬆愉快對彈幕進行二次開發
Python GUI教程(七):轉換qt設計師的ui代碼為Python代碼
利用 pwntools 編寫 socket 腳本

TAG:Flask | Python | Python入門 |