在前面的章節中,咱們已經建立了登陸表單,完成了提交以及驗證。在這篇文章中,咱們要建立咱們的數據庫,並設置它,這樣咱們就能夠保存咱們的用戶。html
咱們接下來說述的正是咱們上一章離開的地方,因此你可能要確保應用程序 microblog 正確地安裝和工做。python
在這一章中咱們會寫一些腳本用來簡化數據庫的管理。在咱們開始編寫腳本以前,先來溫習下 Python 腳本如何在命令行中執行。git
若是你使用 Linux 或者 OS X 系統的話,腳本必須給予必定的權限,像這樣:github
chmod a+x script.py
腳本中有一個 shebang ,它指明應該使用的解釋器。一個腳本若是被賦予了執行權限而且有一個 shebang 行可以被簡單地像這樣執行:sql
./script.py <arguments>
在 Windows 上,上面的操做是沒有做用的,相反你必須提供腳本做爲選擇的 Python 解釋器的一個參數:數據庫
flask\Scripts\python script.py <arguments>
爲了不鍵入 Python 解釋器的路徑,你能夠把 microblog/flask/Scripts 加入到系統路徑中,可是務必讓它在你的 Python 解釋器以前。編程
從如今起,在本教程中的 Linux / OS X 的語法將用於縮寫。若是你是在 Windows 上,記得適當的語法轉換。flask
咱們將使用 Flask-SQLAlchemy 擴展來管理咱們應用程序的數據。這個擴展封裝了 SQLAlchemy 項目,這是一個 對象關係映射器 或者 ORM。api
ORMs 容許數據庫應用程序與對象一塊兒工做,而不是表以及 SQL。執行在對象的操做會被 ORM 翻譯成數據庫命令。這就意味着咱們將不須要學習 SQL,咱們將讓 Flask-SQLAlchemy 代替 SQL。服務器
我見過的大多數數據庫教程會涉及到建立和使用一個數據庫,但沒有充分講述隨着應用程序擴大更新數據庫的問題。一般狀況下,每次你須要進行更新,你最終不得不刪除舊的數據庫和建立一個新的數據庫,而且失去了全部的數據。若是數據不能容易地被從新建立,你可能會被迫本身編寫導出和導入腳本。
幸運地,咱們還有一個更好的選擇。
咱們將使用 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。接下來咱們將編寫這個模塊。
咱們存儲在數據庫中數據將會以類的集合來表示,咱們稱之爲數據庫模型。ORM 層須要作的翻譯就是將從這些類建立的對象映射到適合的數據庫表的行。
讓咱們建立一個表示用戶的模型。使用 WWW SQL Designer 工具,我製做以下的圖來表示咱們用戶的表:
id 字段一般會在全部模型中,而且用於做爲主鍵。在數據庫的每個用戶會被賦予一個不一樣的 id 值,存儲在這個字段中。幸虧這是自動完成的,咱們僅僅須要的是提供 id 這個字段。
nickname 以及 email 字段是被定義成字符串,而且指定了最大的長度以便數據庫能夠優化空間佔用。
如今咱們已經決定用戶表的樣子,剩下的工做就是把它轉換成代碼(文件 app/models.py):
from app import db 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) def __repr__(self): return '<User %r>' % (self.nickname)
咱們剛剛建立的 User 類包含一些字段,這些字段被定義成類的變量。字段是被做爲 db.Column 類的實例建立的,db.Column 把字段的類型做爲參數,而且還有一些其它可選的參數,好比代表字段是否惟一。
__repr__ 方法告訴 Python 如何打印這個類的對象。咱們將用它來調試。
配置以及模型都已經到位了,是時候準備建立數據庫文件。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))
爲了建立數據庫,你須要運行這個腳本(記得若是在 Windows 上命令有些不一樣):
./db_create.py
在運行上述命令以後你會發現一個新的 app.db 文件。這是一個空的 sqlite 數據庫,建立一開始就支持遷移。一樣你還將有一個 db_repository 文件夾,裏面還有一些文件,這是 SQLAlchemy-migrate 存儲它的數據文件的地方。請注意,咱們不會再生的存儲庫,若是它已經存在。這將使咱們從新建立數據庫,同時保留現有的存儲庫,若是咱們須要。
如今,咱們已經定義了咱們的模型,咱們能夠將其合併到咱們的數據庫中。咱們會把應用程序數據庫的結構任何的改變看作成一次遷移,所以這是咱們第一次遷移,咱們將從一個空數據庫遷移到一個能存儲用戶的數據庫上。
爲了實現遷移,咱們須要編寫一小段 Python 代碼(文件 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 中獲取)與咱們模型的結構(從文件 app/models.py 獲取)。二者間的不一樣將會被記錄成一個遷移腳本存放在遷移倉庫中。遷移腳本知道如何去遷移或撤銷它,因此它始終是可能用於升級或降級一個數據庫。
然而在使用上面的腳本自動地完成遷移的時候也不是沒有問題的,我見過有時候它很難識別新老格式的變化。爲了讓 SQLAlchemy-migrate 容易地識別出變化,我毫不會重命名存在的字段,我僅限於增長或者刪除模型或者字段,或者改變已存在字段的類型。固然我一直會檢查生成的遷移腳本,確保它是正確。
毋庸置疑你不該該在沒有備份下去嘗試遷移數據庫。固然也不能在生產環境下直接運行遷移腳本,必須在開發環境下確保遷移運轉正常。
所以讓咱們繼續進行,記錄下遷移:
./db_migrate.py
腳本的輸出以下:
New migration saved as db_repository/versions/001_migration.py Current database version: 1
腳本會打印出遷移腳本存儲在哪裏,也會打印出目前數據庫版本。空數據庫的版本是0,在咱們遷移到包含用戶的數據庫後,版本爲1.
到如今你可能想知道爲何完成記錄數據庫遷移的這項使人麻煩的事情是這麼重要。
假設你有一個應用程序在開發機器上,同時有一個拷貝部署在到線上的生產機器上。在下一個版本中,你的數據模型有一個變化,好比新增了一個表。若是沒有遷移腳本,你可能必需要琢磨着如何修改數據庫格式在開發和生產機器上,這會花費很大的工做。
若是有數據庫遷移的支持,當你準備發佈新版的時候,你只須要錄製一個新的遷移,拷貝遷移腳本到生產服務器上接着運行腳本,全部事情就完成了。數據庫升級也只須要一點 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))
這個腳本會回退數據庫一個版本。你能夠運行屢次來回退多個版本。
關係型數據能夠很好的存儲數據項之間的關係。考慮一個用戶寫了一篇 blog 的例子。在 users 表中有一條用戶的數據,在 posts 表中有一條 blog 數據。記錄是誰寫了這篇 blog 的最有效的方式就是鏈接這兩條相關的數據項。
一旦在用戶和文章(post)的聯繫被創建,有兩種類型的查詢是咱們可能須要使用的。最經常使用的查詢就是查詢 blog 的做者。複雜一點的查詢就是一個用戶的全部的 blog。Flask-SQLAlchemy 將會幫助咱們完成這兩種查詢。
讓咱們擴展數據庫以便存儲 blog。爲此咱們回到數據庫設計工具而且建立一個 posts 表。
咱們的 posts 表中有必須得 id 字段,以及 blog 的 body 以及一個 timestamp。這裏沒有多少新東西。只是對 user_id 字段須要解釋下。
咱們說過想要鏈接用戶和他們寫的 blog。方式就是經過在 posts 增長一個字段,這個字段包含了編寫 blog 的用戶的 id。這個 id 稱爲一個外鍵。咱們的數據庫設計工具把外鍵顯示成一個連線,這根連線鏈接於 users 表中的 id 與posts 表中的 user_id。這種關係稱爲一對多,一個用戶編寫多篇 blog。
讓咱們修改模型以反映這些變化(app/models.py):
from app import dbclass 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) 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
輸出:
New migration saved as db_repository/versions/002_migration.py Current database version: 2
咱們花了不少時間定義咱們的數據庫,可是咱們仍沒有看到它是如何工做的。由於咱們的應用程序中尚未關於數據庫的代碼,讓咱們先在 Python 解釋器上試用下咱們全新的數據庫。
讓咱們先啓動 Python。在 Linux 或者 OS X 上:
flask/bin/python
或者在 Windows 上:
flask\Scripts\python
一旦啓動 Python,在 Python 提示符中輸入以下語句:
>>> from app import db, models >>>
這將會把咱們的數據庫和模型載入內存中。
首先建立一個新用戶:
>>> u = models.User(nickname='john', email='john@email.com') >>> db.session.add(u) >>> db.session.commit() >>>
在會話的上下文中完成對數據庫的更改。多個的更改能夠在一個會話中累積,當全部的更改已經提交,你能夠發出一個 db.session.commit(),這能原子地寫入更改。若是在會話中出現錯誤的時候, db.session.rollback() 能夠是數據庫回到會話開始的狀態。若是即沒有 commit 也沒有 rollback 發生,系統默認狀況下會回滾會話。會話保證數據庫將永遠保持一致的狀態。
讓咱們添加另外一個用戶:
>>> u = models.User(nickname='susan', email='susan@email.com') >>> db.session.add(u) >>> db.session.commit() >>>
如今咱們能夠查詢用戶:
>>> users = models.User.query.all() >>> print users [<User u'john'>, <User u'susan'>] >>> for u in users: ... print u.id,u.nickname ... 1 john2 susan >>>
對於查詢用戶,咱們使用 query 成員,這是對全部模型類都是可用的。
這是另一種查詢。若是你知道用戶的 id ,咱們可以找到這個用戶的數據像下面這樣:
>>> u = models.User.query.get(1) >>> print u <User u'john'> >>>
如今讓咱們提交一篇 blog:
>>> import datetime >>> u = models.User.query.get(1) >>> p = models.Post(body='my first post!', timestamp=datetime.datetime.utcnow(), author=u) >>> db.session.add(p) >>> db.session.commit() >>>
這裏咱們設置咱們的 timestamp 爲 UTC 時區。全部存儲在數據庫的時間戳都會是 UTC。咱們有來自世界上不一樣地方的用戶所以須要有個統一的時間單位。在後面的教程中會以當地的時間呈現這些時間在用戶面前。
你可能注意到了咱們並無設置 user_id 字段。相反咱們在 author 字段上存儲了一個 User 對象。ORM 層將會知道怎麼完成 user_id 字段。
讓咱們多作一些查詢:
# get all posts from a user >>> u = models.User.query.get(1) >>> print u <User u'john'> >>> posts = u.posts.all() >>> print posts [<Post u'my first post!'>] # obtain author of each post >>> for p in posts: ... print p.id,p.author.nickname,p.body ... 1 john my first post! # a user that has no posts >>> u = models.User.query.get(2) >>> print u <User u'susan'> >>> print u.posts.all() [] # get all users in reverse alphabetical order >>> print models.User.query.order_by('nickname desc').all() [<User u'susan'>, <User u'john'>] >>>
Flask-SQLAlchemy 文檔可能會提供更多有幫助的信息。
在結束以前,須要清除一下剛纔建立的數據,以便在下一章中會有一個乾淨的數據庫:
>>> users = models.User.query.all() >>> for u in users: ... db.session.delete(u) ... >>> posts = models.Post.query.all() >>> for p in posts: ... db.session.delete(p) ... >>> db.session.commit() >>>
這是一個漫長的教程。咱們已經學會了使用數據庫的基本知識,但咱們尚未歸入到咱們的應用程序的數據庫。在下一章中,咱們將會把咱們所學到的全部關於數據庫的知識用於實踐。
若是你想要節省時間的話,你能夠下載 microblog-0.4.zip。