在前面的章节中,咱们已经建立了登陆表单,完成了提交以及验证。在这篇文章中,咱们要建立咱们的数据库,并设置它,这样咱们就能够保存咱们的用户。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。