Flask课程4-数据库Flask-SQLAlchemy、Flask-Migrate

1,795 阅读3分钟

Flask-SQLAlchemy: 支持包含MySQL、PostgreSQL和SQLite在内的很多数据库软件,可以在开发的时候使用简单易用且无需另起服务的SQLite,需要部署应用到生产服务器上时,则选用更健壮的MySQL或PostgreSQL服务,并且不需要修改应用代码(译者注:只需修改应用配置)。

Flask-Migrate: 这个插件是Alembic的一个Flask封装,是SQLAlchemy的一个数据库迁移框架。 使用数据库迁移增加了启动数据库时候的一些工作,但这对将来的数据库结构稳健变更来说,是一个很小的代价。

对现有数据库更新或者添加表结构,这是一项困难的工作,因为关系数据库是以结构化数据为中心的,所以当结构发生变化时,数据库中的已有数据需要被迁移到修改后的结构中。

安装

$ pip install flask-sqlalchemy
$ pip install flask-migrate

Flask-SQLAlchemy配置

开发阶段,使用SQLite数据库,SQLite数据库是开发小型乃至中型应用最方便的选择,因为每个数据库都存储在磁盘上的单个文件中,并且不需要像MySQL和PostgreSQL那样运行数据库服务。

给配置文件添加两个新的配置项

config.py

import os

baseDir = os.path.abspath(os.path.dirname(__file__))

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'my-secret-key'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_RUL') or 'sqlite:///'+os.path.join(baseDir, 'app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

Flask-SQLAlchemy插件从SQLALCHEMY_DATABASE_URI配置变量中获取应用的数据库的位置。首先从环境变量获取配置变量,未获取到就使用默认值,这样做是一个好习惯。从DATABASE_URL环境变量中获取数据库URL,如果没有定义,将其配置为basedir变量表示的应用顶级目录下的一个名为app.db的文件路径。

SQLALCHEMY_TRACK_MODIFICATIONS配置项用于设置数据发生变更之后是否发送信号给应用,当前不需要这项功能,因此将其设置为False。

数据库在应用的表现形式是一个数据库实例,数据库迁移引擎同样如此。它们将会在应用实例化之后进行实例化和注册操作。app/init.py文件变更如下:

import os
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app=app)
migrate = Migrate(app=app, db=db)

from app import routes

添加了一个db对象来表示数据库,添加了数据库迁移引擎migrate.

底部导入了一个名为models的模块,这个模块将会用来定义数据库结构

数据库模型

app/models.py

from app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))

    def __repr__(self):
        return '<User {}>'.format(self.username)

创建数据库迁移存储库

创建的模型类定义了此应用程序的初始数据库结构(元数据)后,Alembic(Flask-Migrate使用的迁移框架)将以一种不需要重新创建数据库的方式进行数据库结构的变更。

$ flask db init

运行迁移初始化命令后,自动生成app/migrations目录

第一次数据库迁移

包含映射到User数据库模型的用户表的迁移存储库生成后,是时候创建第一次数据库迁移了。

有两种方法来创建数据库迁移:手动或自动。 要自动生成迁移,Alembic会将数据库模型定义的数据库模式与数据库中当前使用的实际数据库模式进行比较。 然后,使用必要的更改来填充迁移脚本,以使数据库模式与应用程序模型匹配。 当前情况是,由于之前没有数据库,自动迁移将把整个User模型添加到迁移脚本中。 flask db migrate子命令生成这些自动迁移:

$ flask db migrate -m "users table"

    'Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set. '
    INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
    INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
    INFO  [alembic.autogenerate.compare] Detected added table 'user'
    INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'
    INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'
      Generating /Users/loki/Desktop/work/proj_py/proj_flask/les1_simpleflask/migrations/versions/12530a416ea8_users_table.py ...  done

生成的迁移脚本现在是你项目的一部分了,需要将其合并到源代码管理中。 如果你好奇,并检查了它的代码,就会发现它有两个函数叫 upgrade()downgrade()upgrade()函数应用迁移,downgrade()函数回滚迁移。 Alembic通过使用降级方法可以将数据库迁移到历史中的任何点,甚至迁移到较旧的版本。

flask db migrate命令不会对数据库进行任何更改,只会生成迁移脚本。 要将更改应用到数据库,必须使用flask db upgrade命令。

$ flask db upgrade 
    INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
    INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
    INFO  [alembic.runtime.migration] Running upgrade  -> 12530a416ea8, users table

因为本应用使用SQLite,所以upgrade命令检测到数据库不存在时,会创建它(在这个命令完成之后,你会注意到一个名为app.db的文件,即SQLite数据库)。 在使用类似MySQL和PostgreSQL的数据库服务时,必须在运行upgrade之前在数据库服务器上创建数据库。

数据库关系

创建一对多的关系,user->post一个用户会创建多个动态

app/models.py

from app import db
from datetime import datetime

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    psots = db.relationship('Post', backref='author', lazy='dynamic')
    
    def __repr__(self):
        return '<User {}>'.format(self.username)

class Post(db.Model):
    id=db.Column(db.Integer, primary_key=True)
    body=db.Column(db.String(140))
    timestamp=db.Column(db.DateTime, index=True, default=datetime.utcnow)
    user_id=db.Column(db.Integer, db.ForeignKey('usr.id'))

    def __repr__(self):
        return '<Post {}>'.format(self.body)

User类有一个新的posts字段,用db.relationship初始化。这不是实际的数据库字段,而是用户和其动态之间关系的高级视图,因此它不在数据库图表中。对于一对多关系,db.relationship字段通常在“一”的这边定义,并用作访问“多”的便捷方式。

因此,如果我有一个用户实例u,表达式u.posts将运行一个数据库查询,返回该用户发表过的所有动态。 db.relationship的第一个参数表示代表关系“多”的类。 backref参数定义了代表“多”的类的实例反向调用“一”的时候的属性名称。这将会为用户动态添加一个属性post.author,调用它将返回给该用户动态的用户实例。 lazy参数定义了这种关系调用的数据库查询是如何执行的,这个我会在后面讨论。不要觉得这些细节没什么意思,本章的结尾将会给出对应的例子。

# 一旦我变更了应用模型,就需要生成一个新的数据库迁移:
$ flask db migrate -m "add post table"
# 将这个迁移应用到数据库
$ flask db upgrade

迁移失败记录

ERROR [root] Error: Target database is not up to date.
1. 查看migrate的状态
$: flask db heads
2. 查看当前的状态
$ flask db current
发现 版本号不一致

$ flask db migrate -m"msg"
$ flask db upgrade
flask db stamp heads
flask db downgrade