这章数据库的内容比较多(一个一个代码敲下来的啊),主要涉及数据库的配置,博客的数据库结构设计以及数据库的操作。
和django不一样的地方是flask的数据库管理使用的是Flask-SQLAlchemy扩展,而django使用的是内置的数据库系统,更新到1.7之后也自带了migration系统还蛮方便的。
一、数据库的配置
包括配置,建立数据库,迁移,升级和降级三个部分。
1.配置(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')
小型的应用我们使用sqlite数据库。SQLALCHEMY_DATABASE_URI 是 Flask-SQLAlchemy 扩展需要的。这是我们数据库文件的路径。 SQLALCHEMY_MIGRATE_REPO 是文件夹,我们将会把 SQLAlchemy-migrate 数据文件存储在这里。
2.初始化数据库(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中编写代码来描述博客系统中的对象在数据库中的存储形式。这些内容在后面再写,接下来是数据库的创建,管理和维护。
在models编写完成并且格式无误之后,便可以应用models来创建数据库了,SQLAlchemy-migrate 包自带命令行和 APIs,这些 APIs 以一种将来允许容易升级的方式来创建数据库。然而在命令行中输入代码不太方便,在这里我们编写几个脚本来进行相关的操作。
3.创建数据库(db_create.py)
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))
代码中我们导入了SQLAlchemy提供的api,初始化时创建的db对象,以及数据库和迁移文件的存储路径。
后面一堆的意思是我想是先创建一个数据库,然后如果不存在数据库的存储文件夹,就创建一个,并且将它和数据库绑定起来,如果有一个就直接绑定(大约是这个意思吧(:з」∠))
注意:存储库是不会再生的,如果它已经存在。这将使我们重新创建数据库,同时保留现有的存储库,如果我们需要。
4.第一次迁移(db_migrate.py)
我们会把应用程序数据库的结构任何的改变看做成一次迁移,因此这是我们第一次迁移,我们将从一个空数据库迁移到一个能存储用户的数据库上。
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))
在这段代码中,导入了一些配置文件,定义了迁移文件的存储位置,migration,中间那段代码应该是把新旧数据模型作对比的然而并不太懂。两者间的不同将会被记录成一个迁移脚本存放在迁移仓库中。迁移脚本知道如何去迁移或撤销它,所以它始终是可能用于升级或降级一个数据库。
5.数据库升级和回退(db_upgrade.py,db_downgrade.py)
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))
可以看出api直接提供了一个upgrade方法来完成这项操作。
回退同理:
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))
这个脚本运行一次将会使数据库回退一个版本。
关于数据库的操作主要就是这些,接下来我们将数据库和我们有关于博客的数据模型结合起来,没错 ,就是前面提到的models.。
二、数据模型设计及实例操作
1.数据模型定义(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)
在这里我们定义了User和Post两个类,代表用户和博文两个对象。
其中用户对象有id,nickname,email,role几个基本字段,而posts是和Post对象有关联的字段,它是被构建成一个db.relationship字段。这并不是一个实际的数据库字段,对于一个一对多(一个用户会有多篇博文)的关系,db.relationship字段通常是定义在“一”这一边。在这种关系下,我们得到一个 user.posts 成员。
而在后面的Post类中,Post对象有id,body,timestamp,user_id几个属性,其中user_id是一个外键,它链接到User的id字段,用来表示Post的作者。和User中的posts是相对应的。
2.数据库操作
我们已经按照数据模型的内容把数据库创建完成了,但是这个数据库现在还是空的没有任何内容,下面我们来操作一些实例。
在数据库文件的同级目录下打开python console,再导入数据库和我们在数据模型中定义好的模块。
>>> from app import db
>>> from app.models import User, Post, ROLE_USER, ROLE_ADMIN
①创建一个新用户
>>> u1 = User(nickname='john', email='john@email.com', role=ROLE_USER)
>>> db.session.add(u1)
>>> db.session.commit()
第一句实例化一个用户对象,后面两句表示在会话中添加这个用户对象和提交(感觉和git有点像)。
在会话的上下文中完成对数据库的更改。多个的更改可以在一个会话中累积,当所有的更改已经提交,你可以发出一个 db.session.commit(),这能原子地写入更改。如果在会话中出现错误的时候, db.session.rollback() 可以是数据库回到会话开始的状态。如果即没有 commit 也没有 rollback 发生,系统默认情况下会回滚会话。会话保证数据库将永远保持一致的状态。
②查询
>>> users = User.query.all()
>>> print users
[<User u'john'>, <User u'susan'>]
>>> for u in users:
... print u.id,u.nickname
...
1 john
2 susan
对于查询用户,我们使用 query 成员,这是对所有模型类都是可用的。
根据字段查询可以像这样:
>>> u = User.query.get(1)
>>> print u
<User u'john'>
③提交一篇blog
>>> import datetime
>>> u = User.query.get(1)
>>> p = Post(body='my first post!', timestamp=datetime.datetime.utcnow(), author=u)
>>> db.session.add(p)
>>> db.session.commit()
你可能注意到了我们并没有设置 user_id 字段。相反我们在 author 字段上存储了一个 User 对象。ORM 层将会知道怎么完成 user_id 字段。
④删除数据
>>> users = User.query.all()
>>> for u in users:
... db.session.delete(u)
...
>>> posts = Post.query.all()
>>> for p in posts:
... db.session.delete(p)
...
>>> db.session.commit()