flask的操作数据库和redis flask与数据库_数据迁移


Flask中的数据库

Flask并不原生支持数据库,而是通过Python包以及Flask数据库插件。

数据库分为关系型数据库和非关系型数据库,这里我们使用关系型数据库。我们使用Flask-SQLAlchemy插件,它是SQLAlchemy的Flask插件包,基于对象关系映射ORM。SQLAlchemy支持很多数据引擎,包括MySSQL、PostgreSQL、SQLite等。通过下面的命令安装Flask-SQLAlchemy插件:


(venv) $ pip install flask-sqlalchemy


数据迁移

大部分数据库教程都是关于创建数据库,使用数据库的,很少有讲解对已有的数据库进行升级改造的。关系型数据库如果需要改变结构,需要进行数据迁移,这需要Flask-Migrate,一个SQLAlchemy的迁移工具。安装方式:


(venv) $ pip install flask-migrate


Flask-SQLAlchemy配置

这里我用了SQLite数据库,只需要一个本地文件,而不用安装MySQL数据库。我们在config.py中增加配置如下:


import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config(object):
    # ...
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 
        'sqlite:///' + os.path.join(basedir, 'app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False


SQLALCHEMY_DATABASE_URI指定了数据库文件的存储位置,SQLALCHEMY_TRACK_MODIFICATIONS屏蔽了不需要的Flask-SQLAlchemy的数据够新通知特性。

数据库实例

在应用中,数据库用实例表示,我们在app/init.py文件中进行实例化:


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) # 数据库实例
migrate = Migrate(app, db) # 数据迁移引擎实例化

from app import routes, models # models定义数据库结构,对应下面新建的models.py。


数据库模型

借助SQLAlchemy数据在应用中表示成不同的类,SQLAlchemy负责将这些类表达成表。用户表的设计如下:


flask的操作数据库和redis flask与数据库_flask_02


出于安全考虑,这里并没有明文存储用户密码。

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


创建数据迁移仓库

如果需要对数据库的结构进行改变,需用上数据迁移工具。运行flask db init命令,可以自动创建当前版本的数据迁移脚本。


(venv) $ flask db init
  Creating directory /home/miguel/microblog/migrations ... done
  Creating directory /home/miguel/microblog/migrations/versions ... done
  Generating /home/miguel/microblog/migrations/alembic.ini ... done
  Generating /home/miguel/microblog/migrations/env.py ... done
  Generating /home/miguel/microblog/migrations/README ... done
  Generating /home/miguel/microblog/migrations/script.py.mako ... done
  Please edit configuration/connection/logging settings in
  '/home/miguel/microblog/migrations/alembic.ini' before proceeding.


在项目目录中,增加了一个migrations的文件夹,其中的文件应加入git进行跟踪。

数据迁移

有了数据迁移仓库就有了数据迁移脚本,我们可以进行第一次数据迁移,即创建用户数据表,这有两种方法:自动方法和手动方法。对于自动方法,Alembic会比较数据模型和数据库内的数据表,在数据迁移脚本中增加数据迁移命令。当前没有数据表,所以在数据迁移脚本中会增加在数据库中创建数据表的命令。修改数据迁移脚本命令如下:


(venv) $ flask db migrate -m "users table"
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 /home/miguel/microblog/migrations/versions/e517276bb1c2_users_table.py ... done


命令中的-m "users table" 是可选的,起到注释作用。这条命令只是产生了一个py文件,用来进行数据迁移,并没有修改数据库。要修改数据库,需要运行如下命令:


(venv) $ 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  -> e517276bb1c2, users table


如果你使用了Mysql或者PostgreSQL,在运行flask db upgrade之前应该先创建user表。Flask-SQLAlchemy会根据数据类的名称自动转换和创建数据表名称,日如User类编程user表,AddressAndPhone类变成address_and_phone表。如果需要另选表名称,可以在数据类中增加__tablename__参数。

数据库升级和降级

在实际工作中可能存在一个开发环境(本机)和一个在线环境(服务器)。如果你开发的新版应用需要修改表结构,那么你运行flask db migrate可以自动产生数据迁移的命令。在git中更新后,在线环境直接运行flask db upgrade就能同步升级数据库。

如果你发现新部署的应用有问题,可以用flask db downgrade回退到上一个版本。

数据表关系

关系型数据库的表之间存在联系,比如博客中的文章需要记录是谁发布的,或者查询一个用户发了哪些帖子。我们增加一个post表用来记录帖子。


flask的操作数据库和redis flask与数据库_flask_03


帖子表中的user_id是一个外键,用来和用户表进行连接,这种连接是一种一对多的连接。我们修改app/models.py如下:


from datetime import datetime
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))
    posts = 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('user.id')) # 外键

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


在更新了应用中的数据模型后,需要产生新的数据迁移脚本:


(venv) $ flask db migrate -m "posts table"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'post'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_post_timestamp' on '['timestamp']'
  Generating /home/miguel/microblog/migrations/versions/780739b227a7_posts_table.py ... done


然后执行数据迁移脚本,实际修改数据:


(venv) $ 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 e517276bb1c2 -> 780739b227a7, posts table


记得把这些数据迁移脚本加入git。