英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iv-database
中文翻譯地址:http://www.pythondoc.com/flask-mega-tutorial/database.html
開源中國社區:www.oschina.net/translate/the-flask-mega-tutorial-part-iv-database
一、Flask中的數據庫
教程:將使用 Flask-SQLAlchemy 擴展來管理我們應用程序的數據。這個擴展封裝了 SQLAlchemy 項目,這是一個 對象關系映射器 或者 ORM。
大多數數據庫教程會涉及到創建和使用一個數據庫,但沒有充分講述隨着應用程序擴大更新數據庫的問題。通常情況下,每次你需要進行更新,你最終不得不刪除舊 的數據庫和創建一個新的數據庫,並且失去了所有的數據。如果數據不能容易地被重新創建,你可能會被迫自己編寫導出和導入腳本。
我們將使用 SQLAlchemy-migrate 來跟蹤數據庫的更新。它只是在開始建立數據庫的時候多花費些工作,這只是很小的代價,以后就再不用擔心人工數據遷移了。
二、配置
教程采用的數據庫是sqlite 數據庫。sqlite 數據庫是小型應用的最方便的選擇,每一個數據庫都是存儲在單個文件里。
有許多新的配置項需要添加到配置文件中(文件 config.py):
import os basedir = os.path.abspath(os.path.dirname(__file__)) SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
SQLALCHEMY_DATABASE_URI 是 Flask-SQLAlchemy 擴展需要的。這是我們數據庫文件的路徑。
SQLALCHEMY_MIGRATE_REPO 是文件夾,我們將會把 SQLAlchemy-migrate 數據文件存儲在這里。
當我們初始化應用程序的時候,我們也必須初始化數據庫。修改初始化文件(文件 app/__init__.py):
from flask import Flask from flask.ext.sqlalchemy import SQLAlchemy app = Flask(__name__) app.config.from_object('config') db = SQLAlchemy(app) from app import views, models
這里的改變有兩個。一是創建了一個 db 對象,這是我們的數據庫,接着導入一個新的模塊,叫做 models。接下來我們將編寫 models。
三、數據庫模型
我假設大家能學過數據庫。
用戶表如下圖:
表中共有4個字段。
id:主鍵
nickname :字符串,並且指定了最大的長度以便數據庫可以優化空間占用。
email :字符串,並且指定了最大的長度以便數據庫可以優化空間占用。
role :整型,表示哪個用戶是管理員,哪個不是。
把它轉換成代碼(文件 app/models.py):
from app import db ROLE_USER = 0 ROLE_ADMIN = 1 class User(db.Model): id = db.Column(db.Integer, primary_key = True) nickname = db.Column(db.String(64), index = True, unique = True) email = db.Column(db.String(120), index = True, unique = True) role = db.Column(db.SmallInteger, default = ROLE_USER) def __repr__(self): return '<User %r>' % (self.nickname)
User類把我們剛剛創建的幾個字段定義為類變量。字段使用db.Column類創建實例,字段的類型作為參數,另外還提供一些其他可選參數。例如,標識字段唯一性和索引的參數.
__repr__方法告訴Python如何打印class對象,方便我們調試使用。
四、創建數據庫
教程:配置以及模型都已經到位了,是時候准備創建數據庫文件。SQLAlchemy-migrate 包自帶命令行和 APIs,這些 APIs 以一種將來允許容易升級的方式來創建數據庫。我發現命令行使用起來比較別扭,因此我們自己編寫一些 Python 腳本來調用遷移的 APIs。
創建數據庫的腳本(文件 db_create.py):
#!flask/bin/python from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO from app import db import os.path db.create_all() if not os.path.exists(SQLALCHEMY_MIGRATE_REPO): api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository') api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) else: api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))
這個文件放在根目錄下。
注意這個腳本是完全通用的,所有的應用路徑名都是從配置文件讀取的。當你用在自己的項目時,你可以把腳本拷貝到你app`s目錄下就能正常使用了。
創建數據庫:
chmod a+x db_create.py ./db_create.py
運行這條命令之后,文件夾中就多了一個新的app.db文件,這是個支持遷移的空sqlite數據庫。同時也會生成一個帶有幾個文件的 db_repository目錄,這是SQLAlchemy-migrate存儲數據庫文件的地方,注意如果數據庫已存在它就不會再重新生成了。這將幫助 我們在丟失了現有的數據庫后,再次自動創建出來。
五、第一次遷移
既然已經定義好了model,也把它和數據庫做了關聯,接下來初次嘗試下做一個改變應用數據庫結構的一次遷移,這將幫助我們從一個空的數據庫變成一個可以存儲users信息的數據庫。
首先需要一個遷移腳本(文件 db_migrate.py):
#!flask/bin/python import imp from migrate.versioning import api from app import db from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO migration = SQLALCHEMY_MIGRATE_REPO + '/versions/%03d_migration.py' % (api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + 1) tmp_module = imp.new_module('old_model') old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) exec old_model in tmp_module.__dict__ script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata) open(migration, "wt").write(script) api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) print 'New migration saved as ' + migration print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))
腳本代碼略長,慢慢看。
教程:SQLAlchemy-migrate通過對比數據庫的結構(從app.db文件讀取)和models結構(從app/models.py文件讀取)的方 式來創建遷移任務,兩者之間的差異將作為一個遷移腳本記錄在遷移庫中,遷移腳本知道如何應用或者撤銷一次遷移,所以它可以方便的升級或者降級一個數據庫的 格式。
然而在使用上面的腳本自動地完成遷移的時候也不是沒有問題的,我見過有時候它很難識別新老格式的變化。為了讓 SQLAlchemy-migrate 容易地識別出變化,我絕不會重命名存在的字段,我僅限於增加或者刪除模型或者字段,或者改變已存在字段的類型。當然我一直會檢查生成的遷移腳本,確保它是 正確。
毋庸置疑你不應該在沒有備份下去嘗試遷移數據庫。當然也不能在生產環境下直接運行遷移腳本,必須在開發環境下確保遷移運轉正常。
然后,執行吧:
chmod a+x db_migrate.py ./db_migrate.py
六、數據庫升級和回退
假設你有一個應用程序在開發機器上,同時有一個拷貝部署在到線上的生產機器上。在下一個版本中,你的數據模型有一個變化,比如新增了一個表。如果沒有遷移腳本,你可能必須要琢磨着如何修改數據庫格式在開發和生產機器上,這會花費很大的工作。
如果有數據庫遷移的支持,當你准備發布新版的時候,你只需要錄制一個新的遷移,拷貝遷移腳本到生產服務器上接着運行腳本,所有事情就完成了。數據庫升級也只需要一點 Python 腳本(文件 db_upgrade.py):
#!flask/bin/python from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))
當你運行上述腳本的時候,數據庫將會升級到最新版本。
通常情況下,沒有必要把數據庫降低到舊版本,但是,SQLAlchemy-migrate 支持這么做(文件 db_downgrade.py):
#!flask/bin/python from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v - 1) print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))
這個腳本會回退數據庫一個版本。運行多次則可以回退多個版本。
七、數據庫關系
學過數據庫應該知道數據庫關系指的是什么,我假設大家能看到下面的圖:
修改(app/models.py):
from app import db ROLE_USER = 0 ROLE_ADMIN = 1 class User(db.Model): id = db.Column(db.Integer, primary_key = True) nickname = db.Column(db.String(64), unique = True) email = db.Column(db.String(120), unique = True) role = db.Column(db.SmallInteger, default = ROLE_USER) posts = db.relationship('Post', backref = 'author', lazy = 'dynamic') def __repr__(self): return '<User %r>' % (self.nickname) class Post(db.Model): id = db.Column(db.Integer, primary_key = True) body = db.Column(db.String(140)) timestamp = db.Column(db.DateTime) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) def __repr__(self): return '<Post %r>' % (self.body)
我們添加了一個 Post 類,這是用來表示用戶編寫的 blog。在 Post 類中的 user_id 字段初始化成外鍵,因此 Flask-SQLAlchemy 知道這個字段是連接到用戶上。
值得注意的是我們已經在 User 類中添加一個新的字段稱為 posts,它是被構建成一個 db.relationship 字段。這並不是一個實際的數據庫字段,因此是不會出現在上面的圖中。對於一個一對多的關系,db.relationship 字段通常是定義在“一”這一邊。在這種關系下,我們得到一個 user.posts 成員,它給出一個用戶所有的 blog。
運行遷移腳本:
./db_migrate.py
八、測試