the flask mega tutorial自学记录 之 第四章 数据库迁移


在实际网站开发中,开发者在本地进行开发,测试成功后再把新版本的功能发布的正式应用环境。不断更新升级的过程,怎么能快速的在本地及正式环境中同步数据呢?那就需要下边的知识——数据库迁移技术。


一、 Flask架构中的数据库

其实在FLASK中,是没有自带数据库功能,但是,它可以与各种流行的数据库软件轻易结合,它把这种选择留给开发人员,这正是Flask的灵活支持。
虽然可供选择的数据库很多,比如关系型数据:mysql、PostgreSQL、mssql、oracle等,还有非关系型数据库。但是,本示例还是选用轻巧的关系型数据库sqlite(没有数据库引擎)。如果要把此应用发布到生产环境,仅需要把数据库引擎切换到其他类型的数据库。
第三章,我们就引进了几个flask的扩充库。这次,为了更好的管理数据库操作,需要一个第三方的库flask-sqlalchemy。

介绍下ORM
ORM 全称 Object Relational Mapping, 翻译过来叫对象关系映射。简单的说,ORM 将数据库中的表与面向对象语言中的类建立了一种对应关系。这样,我们要操作数据库,数据库中的表或者表中的一条记录就可以直接通过操作类、对象、方法或者类实例来完成。
SQLAlchemy 是Python 社区最知名的 ORM 工具之一,为高效和高性能的数据库访问设计,实现了完整的企业级持久模型。它可以适用各种数据库。

安装flask-sqlalchemy。 注意:先要激活虚拟环境再安装。

pip install flask-sqlalchemy

二、DATABASE Migrate数据库迁移

个人理解:这里的迁移不是仅指数据库文件的整体迁移(备份及还原、附加数据库等),也包含微小数据结构改变操作。
实际应该是所有数据库操作动作对应的语言脚本,通过这些脚本可以快速重复同样的操作。
很多的数据教程会提到怎么新建、使用数据库操作,但是当APP应用的数据结构变化或扩展后的相关操作,教程很少会提及。
尤其关系型数据库的关系结构复杂,一旦结构改变,相应的数据就需要对应改变。
安装flask-migrate

pip install flask-migrate    注意:先要激活虚拟环境再安装。

三、Flask-SQLAlchemy设置

1、设置SQLITE数据库引擎uri

如前边的章节介绍一样,把此处的设置内容,增添到config.py文件中。
path.dirname:去掉路径中的文件名称。
os.path.abspath:获取绝对路径。
file:当前文件的路径。

import os
basedir = os.path.abspath(os.path.dirname(__file__))  #__file__用来获得模块所在的路径.

class Config(object):
    # ...
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \    #获取环境变量为DATABASE_URL值(windows系统中环境变量的path参数)
        'sqlite:///' + os.path.join(basedir, 'app.db')   #拼接一个sqlite接口引擎
    SQLALCHEMY_TRACK_MODIFICATIONS = False    #设置为False后,不会记录数据的变更记录。

2、初始化DB对象及迁移对象

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)                  #创建DB引擎
migrate = Migrate(app, db)            #创建Migratey引擎

from app import routes, models    #models是下边将建立的模块

Tip:更多的FLASK扩展也会像db、migrate这两个一样的初始化。

四、数据库模型(类似关系型数据库表结构)

用户表:id、username、email、password_hash。
为什么hash化password。主要是如果直接把密码明文存储,一旦数据库被黑客攻陷,那这对用户来说是灾难性的。所以,通过hash化密码,可以提升安全性。具体的讲解放到后边的章节。
这里写图片描述
新建一个模块:app/models.py,增加两个类(表结构):
这里请注意:如果完全按照教程中的代码,会遇到一个坑:在提交的时候,后台语句会报错,提示sqlite数据库中没有user表。所以,先用db.create_all()启动。
db.Column的参数说明:
primary_key: 如果设为 True,这列就是表的主键;
unique:如果设为 True,这列不允许出现重复的值;
index:如果设为 True,为这列创建索引,提升查询效率;
nullable:如果设为 True,这列允许使用空值;如果设为 False,这列不允许使用空值;
default:为这列定义默认值.

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作者所作)可以不用重新构建表而改变schema(结构)。
Alembic包含一个迁移库,这个库有一个由这些迁移脚本组成目录。只要数据库结构一改变,记录这些变动的迁移脚本将添加到迁移库中。
类似第一章运行flask run 一样:

flask db init

另外用Pycharm配置后直接运行:
在这里插入图片描述
初始化后flask自动生成的语句如下:

 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.

六、第一个数据库迁移

上边建立迁移库,现在我们开始建立一个数据的迁移。
Alembic 会自动检测数据库model与实际数据库里的变动差异。由于先前没有建立过user的schema,迁移脚步会自动增加建立一个完整的user的脚本。
-m “users table”:增加migrate的简短描述。

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 
#e517276bb1c2是系统自动生成的迁移版本的版本号  /home/miguel/microblog/migrations/versions/e517276bb1c2_users_table.py ... done 

虽然运行了migrate,但是,此语法不能影响实际数据库的数据,它仅仅是生成了迁移脚本。
如果要通过迁移改变实际数据库的数据,需要用到如下两个函数
upgrade()和downgrade()。
upgrade():运用迁移脚本;
downgrade():移除当前脚本,可回溯到以往的历史节点。

七、数据库升级和降级

假设你开发一个APP应用,先是在本地进行开发。如果model发生了变化,在没有迁移功能情况下,你就需要在开发环境中修改model,同时在生产环境中修改model。
有了迁移功能,在开发环境中,我们就先执行migrate,然后审查这些变动能否正常运行。最后运行upgrade来更新这些变动。
如果发现有问题,可以运行downgrade回到先前的某个更新节点。

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

Tip:Flask-SQLAlchemy采用蛇形命名规则(用下划线连接多个单词)来命名数据库表名称。
例如上边的User模型,则会在数据库中建立一个user表。
如果要改变这默认的表名称,可以在模型类增加一个属性__tablename__,并赋值你所期望的名称。

八、数据库表关系

在关系数据库中,可以在“一”表中建立一个变量Post,用来构建user与Post的对应关系。

from datetime import datetime
from app import db              #db是上边创建
from flask_login import UserMixin

#注意添加UserMixin,否则在提交注册的时候报错:User没有active这个属性。
class User(db.Model,UserMixin):  
	db.create_all()     #这个语句是教程中没有,必须添加
    id = db.Column(db.Integer, primary_key=True)    #db.Column定义列字段:第一个参数字段类型,第二个是主键
    username = db.Column(db.String(64), index=True, unique=True)  #unique代表唯一性
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    #relationship生成一个关系,posts不是一个数据库的字段,仅是一个视图关系
    #在一对多的模式下,第一个参数是代表多方的类(表)名称
    #第二个参数backref,将向post类中添加一个author属性,从而定义反向关系。这一属性可替代user_id 访问user模型,此时获取的是模型对象,而不是外键的值(类似表的一行或一个实例)。
    #u = User.query.get(1)
    #author使用的例子:p = Post(body='my first post!', author=u)
    posts = db.relationship('Post', backref='author', lazy='dynamic')  
     
    #__repr__主要是自定义一种输出格式,重组print的打印效果。另外一种__str__是更上一层的输出格式。
    #终端用户显示使用__str__,而程序员在开发期间则使用底层的__repr__来显示
    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)

九、玩乐时间

下边的实例是在python自带的shell中运行。

>>> from app import db
>>> from app.models import User, Post
>>> u = User(username='john', email='john@example.com')
>>> db.session.add(u)    #先建立回话后才可以进行数据库操作:新增实例
>>> db.session.commit()    #执行操作
>>> u = User(username='susan', email='susan@example.com')
>>> db.session.add(u)
>>> db.session.commit()
>>> users = User.query.all()    #查询user所有记录
>>> users
[<User john>, <User susan>]
>>> for u in users:
...     print(u.id, u.username)
...
1 john
2 susan

>>> u = User.query.get(1)      #查询主键是1的记录
>>> u
<User john>
>>> u = User.query.get(1)
>>> p = Post(body='my first post!', author=u)   #按照author赋值POST
>>> db.session.add(p)
>>> db.session.commit()
>>> # get all posts written by a user
>>> u = User.query.get(1)
>>> u
<User john>
>>> posts = u.posts.all()
>>> posts
[<Post my first post!>]

>>> # same, but with a user that has no posts
>>> u = User.query.get(2)
>>> u
<User susan>
>>> u.posts.all()
[]

>>> # print post author and body for all posts 
>>> posts = Post.query.all()
>>> for p in posts:
...     print(p.id, p.author.username, p.body)
...
1 john my first post!

# get all users in reverse alphabetical order
>>> User.query.order_by(User.username.desc()).all()
[<User susan>, <User john>]
>>> 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()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值