基于flask的microBlog开发笔记(三)

3.数据库

###1.flask中的数据库

  • 数据库迁移,使用 SQLAlchemy-migrate 来跟踪数据库的更新。这只是在开始建立数据库的时候比较花费工作时间,以后就再不用人工进行数据的迁移了。

    1
    2
    $ source flask/bin/activate
    $ pip install SQLAlchemy-migrate
  • 数据库配置,针对我们小型的应用,我们将采用 sqlite 数据库,sqlite 数据库是小型应用的最方便的选择,每一个数据库都是存储在单个文件里,这里对config.py进行再次配置。

    1
    2
    3
    4
    5
    import os
    basedir = os.path.abspath(os.path.dirname(__file__))

    sqlalchemy_database_url = 'sqlite:///' + os.path.join(basedir, 'app.db')
    sqlalchemy_migrate_repo = os.path.join(basedir, 'db_repository')

    sqlalchemy_database_url是 Flask-aqlalchemy 扩展需要的,存储我们数据库文件的路径,sqlalchemy_migrate_repo 是文件夹,存储数据库文件。对init.py文件更新。

    1
    2
    3
    4
    5
    6
    7
    8
    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。
    
  • 数据库模型(app/models.py)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #-*- coding:utf-8 -*-
    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 类包含一些字段,这些字段被定义成类的变量,repr 方法告诉 Python 如何打印这个类的对象。

  • 创建数据库,创建数据库脚本文件(db_create.py)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #-*- coding:utf-8 -*-

    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))

    运行这个脚本文件,python db_create.py,运行完后在app下会发现新的app.db文件,这是一个空的sqlite数据库,创建后就支持迁移,还有一个db_repository文件,这是SQLAlchemy-migrate 存储它的数据文件的地方。

###2.第一次迁移

这是我们第一次迁移,我们将从一个空数据库迁移到一个能存储用户的数据库上,用db_migrate.py实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#-*- coding:utf-8 -*-

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),两者间的不同将会被记录成一个迁移脚本存放在迁移仓库中。

  • 数据库的升级db_upgrade.py和回退db_downgrade.py
1
2
3
4
5
6
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))

如果有数据库迁移的支持,当你准备发布新版的时候,你只需要录制一个新的迁移,拷贝迁移脚本到生产服务器上接着运行脚本,所有事情就完成了,数据库升级也只需要一点 Python 脚本。

1
2
3
4
5
6
7
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。方式就是通过在 posts 增加一个字段,这个字段包含了编写 blog 的用户的 id。这个 id 称为一个外键,users 表中的 id 与 posts 表中的 user_id,这种关系称为一对多,一个用户编写多篇 blog。
对模板进行修改,(app/models.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 知道这个字段是连接到用户上。

###3.数据库操作

  • 首先创建一个新用户名为john
    1
    2
    3
    4
    5
    6
    7
    $ python
    # 进入交互式界面
    >>> 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()

在会话的上下文中完成对数据库的更改。多个的更改可以在一个会话中累积,当所有的更改已经提交,你可以发出一个db.session.commit(),这能原子地写入更改。如果在会话中出现错误的时候, db.session.rollback() 可以使得数据库回到会话开始的状态;若没有 commit 也没有 rollback 发生,系统默认情况下会回滚会话。会话保证数据库将永远保持一致的状态。

  • 添加另一个用户susan

    1
    2
    3
    >>> u2 = User(nickname='susan', email='susan@email.com', role=ROLE_USER)
    >>> db.session.add(u2)
    >>> db.session.commit()
  • 查询用户

    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> 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
    >>>
  • 提交一篇 blog

    1
    2
    3
    4
    5
    >>> 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()

设置我们的 timestamp 为UTC 时区,所有存储在数据库的时间戳都会是 UTC,世界上不同地方的用户因此需要有个统一的时间单位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> u = User.query.get(1)
>>> print u
<User u'john'>
>>> posts = u.posts.all()
>>> print posts
[<Post u'my first post!'>]

# 获得john的个人信息和所有博客内容
>>> for p in posts:
... print p.id,p.author.nickname,p.body
...
1 john my first post!

>>> u = User.query.get(2)
>>> print u
<User u'susan'>
>>> print u.posts.all()