赵走x博客
网站访问量:151482
首页
书籍
软件
工具
古诗词
搜索
登录
Flask 实战:41、电子邮件进阶实践
Flask 实战:40、使用事务邮件服务SendGird
Flask 实战:39、使用Flask-Mail发送电子邮件
Flask 实战:38、数据库进阶实践
Flask 实战:37、更新数据库表
Flask 实战:36、定义关系
Flask 实战:35、在视图函数里操作数据库
Flask 实战:34、数据库操作:CRUD
Flask 实战:33、使用Flask-SQLAlchemy管理数据库
Flask 实战:32、ORM魔法
Flask 实战:31、数据库的分类
Flask 实战:30、单个页面多个表单
Flask 实战:29、单个表单多个提交按钮
Flask 实战:28、使用Flask-CKEditor集成富文本编辑器
Flask 实战:27、多文件上传
Flask 实战:26、文件上传
Flask 实战:25、自定义验证器
Flask 实战:24、使用宏渲染表单
Flask 实战:23、设置错误消息语言
Flask 实战:22、处理表单数据
Flask 实战:21、使用Flask-WTF处理表单
Flask 实战:20、HTML表单
Flask 实战:19、模板进阶实践
Flask 实战:18、模板结构组织
Flask 实战:17、模板辅助工具
Flask 实战:16、模板基本用法
Flask 实战:15、HTTP进阶实践
Flask 实战:14、Flask上下文
Flask 实战:13、HTTP响应
Flask 实战:12、HTTP请求
Flask 实战:11、请求响应循环
Flask 实战:10、Flask与MVC架构
Flask 实战:9、模板与静态文件
Flask 实战:8、Flask命令
Flask 实战:7、URL与端点
Flask 实战:6、项目配置
Flask 实战:5、Flask扩展
Flask 实战:4、Python Shell
Flask 实战:3、启动开发服务器
Flask 实战:2、Hello,Flask!
Flask 实战:1、初识Flask
Flask 实战:37、更新数据库表
资源编号:75888
Python Web
Flask Web开发实战:入门、进阶与原理解析
热度:135
模型类(表)不是一成不变的,当你添加了新的模型类,或是在模型类中添加了新的字段,甚至是修改了字段的名称或类型,都需要更新表。在前面我们把数据库表类比成盛放货物的货架,这些货架是固定生成的。当我们在操控程序(DBMS/ORM)上变更了货架的结构时,仓库的货架也要根据变化相应进行调整。而且,当货架的结构产生变动时,我们还需要考虑如何处理货架上的货物(数据)。
模型类(表)不是一成不变的,当你添加了新的模型类,或是在模型类中添加了新的字段,甚至是修改了字段的名称或类型,都需要更新表。在前面我们把数据库表类比成盛放货物的货架,这些货架是固定生成的。当我们在操控程序(DBMS/ORM)上变更了货架的结构时,仓库的货架也要根据变化相应进行调整。而且,当货架的结构产生变动时,我们还需要考虑如何处理货架上的货物(数据)。 当你在数据库的模型中添加了一个新的字段后,比如在Note模型里添加了一个存储笔记创建时间的timestamp字段。这时你可能想要立刻启动程序看看效果,遗憾的是,你看到了下面的报错信息: ``` OperationalError: (sqlite3.OperationalError) no such column: note.timestamp [...] ``` 这段错误消息指出note表中没有timestamp列,并在中括号里给出了查询所对应的SQL原语。之所以会出现这个错误,是因为数据库表并不会随着模型的修改而自动更新。想想我们之前关于仓库的比喻,仓库里来了一批新类型的货物,可我们还没为它们安排相应的货架,这当然要出错了。下面我们会学习如何更新数据库。 # 1、重新生成表 重新调用create_all()方法并不会起到更新表或重新创建表的作用。如果你并不在意表中的数据,最简单的方法是使用drop_all()方法删除表以及其中的数据,然后再使用create_all()方法重新创建: ``` >>> db.drop_all() >>> db.create_all() ``` 这会清除数据库里的原有数据,请勿在生产环境下使用。 为了方便开发,我们修改initdb命令函数的内容,为其增加一个--drop选项来支持删除表和数据库后进行重建,如代码清单所示: ``` @app.cli.command() @click.option('--drop', is_flag=True, help='Create after drop.') def initdb(drop): """Initialize the database.""" if drop: click.confirm('This operation will delete the database, do you want to continue?', abort=True) db.drop_all() click.echo('Drop tables.') db.create_all() click.echo('Initialized database.') ``` 在这个命令函数前,我们使用click提供的option装饰器为命令添加了一个--drop选项,将is_flag参数设为True可以将这个选项声明为布尔值标志(boolean flag)。--drop选项的值作为drop参数传入命令函数,如果提供了这个选项,那么drop的值将是True,否则为False。因为添加--drop选项会直接清空数据库内容,如果需要,也可以通过click.confirm()函数添加一个确认提示,这样只有输入y或yes才会继续执行操作。 现在,执行下面的命令会重建数据库和表: ``` $ flask initdb --drop ``` 当使用SQLite时,直接删除data.db文件和调用drop_all()方法效果相同,而且更直接,不容易出错。 # 2、使用Flask-Migrate迁移数据库 在开发时,以删除表再重建的方式更新数据库简单直接,但明显的缺陷是会丢掉数据库中的所有数据。在生产环境下,你绝对不会想让数据库里的数据都被删除掉,这时你需要使用数据库迁移工具来完成这个工作。SQLAlchemy的开发者Michael Bayer写了一个数据库迁移工具——Alembic来帮助我们实现数据库的迁移,数据库迁移工具可以在不破坏数据的情况下更新数据库表的结构。蒸馏器(Alembic)是炼金术士最重要的工具,要学习SQL炼金术(SQLAlchemy),我们当然要掌握蒸馏器的使用。 扩展Flask-Migrate集成了Alembic,提供了一些flask命令来简化迁移工作,我们将使用它来迁移数据库。Flask-Migrate及其依赖(主要是Alembic)可以使用Pipenv安装: ``` $ pipenv install flask-migrate ``` 在程序中,我们实例化Flask-Migrate提供的Migrate类,进行初始化操作: ``` from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate app = Flask(__name__) ... db = SQLAlchemy(app) migrate = Migrate(app, db) # 在db对象创建后调用 ``` 实例化Migrate类时,除了传入程序实例app,还需要传入实例化Flask-SQLAlchemy提供的SQLAlchemy类创建的db对象作为第二个参数。 ### 1.创建迁移环境 在开始迁移数据之前,需要先使用下面的命令创建一个迁移环境: ``` $ flask db init ``` Flask-Migrate提供了一个命令集,使用db作为命名集名称,它提供的命令都以flask db开头。你可以在命令行中输入flask--help查看所有可用的命令和说明。 迁移环境只需要创建一次。这会在你的项目根目录下创建一个migrations文件夹,其中包含了自动生成的配置文件和迁移版本文件夹。 ### 2.生成迁移脚本 使用migrate子命令可以自动生成迁移脚本: ``` $ flask db migrate -m "add note timestamp" ... INFO [alembic.autogenerate.compare] Detected added column 'message.timestamp Generating /Path/to/your/database/migrations/versions/c52a02014635_add note_timestamp.py ... done ``` 这条命令可以简单理解为在flask里对数据库(db)进行迁移(migrate)。-m选项用来添加迁移备注信息。从上面的输出信息我们可以看到,Alembic检测出了模型的变化:表note新添加了一个timestamp列,并且相应生成了一个迁移脚本c52a02014635_add_note_timestamp.py,脚本的内容如代码清单: ``` """add note timastamp Revision ID: c52a02014635 """ from alembic import op import sqlalchemy as sa # ... def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.add_column('note', sa.Column('timestamp', sa.DateTime(), nullable=True)) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_column('note', 'timestamp') # ### end Alembic commands ### ``` 从上面的代码可以看出,迁移脚本主要包含了两个函数:upgrade()函数用来将改动应用到数据库,函数中包含了向表中添加timestamp字段的命令;而downgrade()函数用来撤销改动,包含了删除timestamp字段的命令。 就像这两个函数中的注释所说的,迁移命令是由Alembic自动生成的,其中可能包含错误,所以有必要在生成后检查一下。 因为每一次迁移都会生成新的迁移脚本,而且Alembic为每一次迁移都生成了修订版本(revision)ID,所以数据库可以恢复到修改历史中的任一点。正因为如此,迁移环境中的文件也要纳入版本控制。 有些复杂的操作无法实现自动迁移,这时可以使用revision命令手动创建迁移脚本。这同样会生成一个迁移脚本,不过脚本中的upgrade()和downgrade()函数都是空的。你需要使用Alembic提供的Operations对象指令在这两个函数中实现具体操作,具体可以访问Alembic官方文档查看。 ### 3.更新数据库 生成了迁移脚本后,使用upgrade子命令即可更新数据库: ``` >>> $ flask db upgrade ... INFO [alembic.runtime.migration] Running upgrade -> c52a02014635, add note timestamp ``` 如果还没有创建数据库和表,这个命令会自动创建;如果已经创建,则会在不损坏数据的前提下执行更新。 如果你想回滚迁移,那么可以使用downgrade命令(降级),它会撤销最后一次迁移在数据库中的改动,这在开发时非常有用。比如,当你执行upgrade命令后发现某些地方出错了,这时就可以执行flask db downgrade命令进行回滚,删除对应的迁移脚本,重新生成迁移脚本后再进行更新(upgrade)。 虽然我们更新了数据库,但是之前创建的记录中并没有timestamp字段,所以这些记录的timestamp字段的值将为空。如果你需要为旧的数据添加默认的timestamp字段值,可以手动操作。 本节只是对数据库迁移做一个简单的介绍,你可以阅读Alembic的文档了解更多用法和自定义选项,其中的入门教程([http://alembic.zzzcomputing.com/en/latest/tutorial.html](http://alembic.zzzcomputing.com/en/latest/tutorial.html) )值得一读。 # 3、开发时是否需要迁移? 在生产环境下,当对数据库结构进行修改后,进行数据库迁移是必要的。因为你不想损坏任何数据,毕竟数据是无价的。在生成自动迁移脚本后,执行更新之前,对迁移脚本进行检查,甚至是使用备份的数据库进行迁移测试,都是有必要的。 而在开发环境中,你可以按需要选择是否进行数据迁移。对于大多数程序来说,我们可以在开发时使用虚拟数据生成工具来生成虚拟数据,从而避免手动创建记录进行测试。这样每次更改表结构时,可以直接清除后重新生成,然后生成测试数据,这要比执行一次迁移简单很多(在后面我们甚至会学习通过一条命令完成所有工作),除非生成虚拟数据耗费的时间过长。 另外,在本地开发时通常使用SQLite作为数据库引擎。SQLite不支持ALTER语句,而这正是迁移工具依赖的工作机制。也就是说,当SQLite数据库表的字段删除或修改后,我们没法直接使用迁移工具进行更新,你需要手动添加迁移代码来进行迁移。在开发中,修改和删除列是很常见的行为,手动操作迁移会花费太多的时间。 > 提示 对于SQLite,迁移工具一般使用“move and copy”的工作流(创建新表、转移数据、删除旧表)达到类似的效果,具体可访问[http://alembic.zzzcomputing.com/en/latest/batch.html](http://alembic.zzzcomputing.com/en/latest/batch.html) 了解。 当然,这些仅仅是从方便的角度考虑,如果你希望让生产环境的部署更加高效,则应该尽可能让开发环境和生产环境保持一致。这时你应该考虑直接在本地使用MySQL或PostgreSQL等性能更高的DBMS,然后设置迁移环境。 >附注 你可以参考12-Factor程序第10条([https://www.12factor.net/dev-prod-parity](https://www.12factor.net/dev-prod-parity) )了解更多相关信息。