Flask Web DEvelopment 翻译4

第五章 Flask 与数据库

“数据库(database)”是有序存储应用程序数据的方法。程序发起"查询(query)"来获取需要的那部分数据。Web应用程序最常用的数据库是基于"关系(relations)"规范的,也叫SQL(结构化查询语言Structured Query Language)的数据库。但最近几年,像文档型和键值性数据库,泛泛地被统称为NoSQL数据库,已经变得越来越广为人知起来。

SQL数据库

关系型数据库使用"表(table)"来存储数据,规范在程序范围内的不同实体。例如:一个订单管理程序的数据库可能会有客户表,产品表和订单表。
  一个表有固定数量的"列(column)"和不定数量的"行(row)"。列定义了实体的数据属性供表显示。例如:客户表可能有诸如名称,地址,电话等列。表中的每行都为每列定义了由值构成的实际数据元素。(一行被称为一条记录"record")。
  表有一个特殊的列,"主键(primary key)",它为表中的每一行数据定义了一个唯一识别符。表也可能有一个叫做"外键(foreign keys)"的列,它引用另外一个表或本表另外一行的主键。行之间的这些连接就被称为"关系(relationships)",这是关系型数据库的基础。
  图5-1显示了一个简单的数据库关系,只有用户和用户角色两个表。连接两个表的线则显示了两表之间的关系。
图5-1


Flask Web DEvelopment 翻译4_第1张图片
Paste_Image.png

在这个数据库关系中,role表保存着全部的用户角色列表,每个角色通过唯一的id值——这个表的主键来区分。users表则包含着全部的用户,同样每个用户都有唯一的id。除了主键id外,roles表有name列,users表有username列和password列。users表中的role_id是一个外键,引用了role表的id值,这样一来,就可以确定每个用户的角色了。
  就像例子所展示的,关系型数据库高校存储数据,且避免重复。在数据库里重命名一个用户角色非常简单,因为角色名字被保存在一个单独的地方。一旦更改了roles表的角色名称,users表中所有具有这一角色的用户立即就可以看到角色名称的变化。
  另外一方面,把数据分离存进多个表中也会带来复杂性。生成带有角色名称的用户列表时就有点小问题,因为用户名和角色名分别需要从两个表里面读取,然后再在显示之前把它们连接(join)到一起。关系型数据库引擎提供了必要时在表之间连接的操作支持。

NoSQL数据库

上节说过的不遵循关系模型描述的数据库一般被统称为NoSQL数据库。NoSQL数据库通常在数据组织方式上,用collections(集合)代替表,documents(文件)代替记录。NoSQL数据库设计思路导致它很难连接(join),所以绝大部分都不支持这一操作。图5-1显示了SQL数据库的用户表结构,这种形式要求程序自己完成一系列操作:通过users表中每个用户的role_id字段在roles表中搜索对应的role名称,并将结果连接(join)在一起。
  而图5-2则显示了适合NoSQL数据库的设计思路,它通过数据库非标准化,以允许数据重复为代价来减少表的数量,实现一个操作完成数据呈现。

Flask Web DEvelopment 翻译4_第2张图片
Paste_Image.png

这个结构的数据库为每个用户都存储角色name,这就导致重命名一个角色名称可能是会耗费巨大的操作——这回要求更新巨多的文件(document)
但,不是说NoSQL数据库带来的都是坏消息。数据重复允许更快的查询,没有join操作显示用户和角色更直接。

SQL 或 NoSQL

SQL数据库长处在于能以高校紧凑的方式存储结构化数据,这些数据库不断增长以保持一致性。NoSQL数据库放弃了一些一致性的要求,有时能得到性能方面的优势。
  关于数据库的完成分析和比较已经超出了本书的主题范围。对于中小型应用程序,这两种数据库无论性能还是易用性几乎没有什么差别。

Python 数据库框架

Python的数据库包有很多,既有开源的也有商业的。Flask 限制对数据库包的使用,所以你可以选用诸如MySQL,Postgrres,SQLite,Redis,MongoDB或者CouchDB等等,甚至其他你自己喜欢的DB。
  要是没有足够的选择,还有诸如SQLAlchemy或者MongoEngine这样的数据库抽象层的包,它们允许你使用常规的python对象来操作数据,而不需要跟底层的数据实体:表,文件或者查询语言打交道。

如何选择数据库框架,这里有一些评价因素:
易于使用
  如果直接比较数据库引擎和数据库抽象层,那么后者的优势十分明显。抽象层,又称为ORM(对象关系映射)或者ODM(对象文件映射),你呢个把高层的对象操作转换成底层的数据库操作。
性能
  ORM或者ODM必须把对象转换成数据库的这一操作是有开销的。虽然大部分情况下,性能损耗可以忽略不计,但也并不总是如此。通常,使用ORM·ODM获得的生产效率提升要超过轻微的性能退步,所以完全抛弃ORM|ODM的论断是不对的。更好的做法是,选择一个需要时能访问到底层的数据库抽象层。这样需要的话可以可以把一些特殊指令直接优化成本地数据库指令。
可移植性
  在你开发阶段可以选择数据库但生产环境下必须慎重考虑。如果你计划把程序拖到到云平台,你应该了解服务环境提供哪种数据库选择。
另外,可移植性也影响到ORM|ODM。其中一些框架只提供对单一数据库引擎的抽象层支持,另外的却可以更高层次的抽象,并可可以选择数据库引擎——在统一的面向对象的接口下。最好的例子就是SQLAlchemy ORM,它支持包括流行的MySQL,Postgres和SQLite在内的很多关系型数据库引擎。
Flask扩展
  选择一个继承在Flask的框架不是必须的,但这样做可以把你从不得不自己编写集成代码当中拯救出来。Flask集成可以简单配置和操作,所以使用为Flask扩展的特定包是很合算的。
  基于以上目标,我们这本书的数据库框架选择就是Flask-SQLAlchemy,包装了SQLAlchemy的一个Flask扩展。

使用Flask-SQLAlchemy管理数据库

Flask-SQLAlchemy 简化了在Flask程序中使用SQLAlchemy的一个扩展。SQLAlchemy是一个强大的数据库框架,支持很多后台数据库。他提供一个高水平的ORM并可实现对数据库底层原生SQL功能的访问。
  跟其他大多数扩展一样,Flask-SQLAlchemy可以通过pip安装:
(venv)$pip install flask-slqalchemy
  在Flask-SQLAlchemy中数据库被当作一个特定的URL。表5-1列出了三种最流行的数据库引擎的URL格式。

表5-1Flask-SQLAlchemy database URLs
Database engine       URL
MySQL                 mysql://username:password@hostname/database
Postgres              postgresql://username:password@hostname/database
SQLite (Unix)       sqlite:////absolute/path/to/database
SQLite (Windows)      sqlite:///c:/absolute/path/to/database

在这些URL中,hostname指的是mysql数据库服务器,有可能是localhost或者一个远程服务器。数据库服务器能承载多个数据库,所以database明确使用的数据库名字。如果数据库需要认证登陆,就使用username和password来认证用户。

提醒
SQLite数据库没有服务器,所以hostname,username和password都被省略了,database就是磁盘上的文件名。

程序的数据库URL必须在Flask的配置对象中以SQLALCHEMY_DATABAE_URI键的值配置好。另外一个有用的选项是配置键SQLALCHEMY_COMMIT_ON_TEARDOWN,他应该被设置为True来允许数据库在每次请求结束之后自动提交。请参考Flask-SQLAlchemy的文档获得更多的配置信息。
例5-1,展示了如何初始化并配置一个简单的SQLite数据库。

Example 5-1. hello.py: Database configuration
from flask.ext.sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

db = SQLAlchemy(app)

DB对象是SQLAlchemy类的实例化,他提供了对Flask=SQLAlchemy全部功能的访问。

定义模型(Model)

术语模型是指由应用程序使用的持久化实体。在一个ORM环境中,模型就是指一个Python类,他的属性都与数据库相应表中各列匹配。
  Flask-SQLAchemy的实例提供一个模型的基础类,该类具有一套辅助类和函数来定义自身结构。图5-1的roles表和users表可以被定义为一个Role模型和User模型,如例子5-2所示:

Example 5-2. hello.py: Role and User model definition
class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    def __repr__(self):
        return '' % self.name

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    def __repr__(self):
        return '' % self.username

'tablename'类变量定义了数据库中标的名字。如果不指定表名,Flask-SQLAlchemy会分配一个默认的表名,但默认表名并不遵守使用物理表名字的复数形式这个规则,所以最好还是明确指定下面的泪变量就是模型的属性,被定义为db.Column类的实例。
  传给db.Column构造函数的第一个参数是数据库列类型和模型的属性。表5-2列出了有效的列类型的变量,和在模型中使用的Python类型对应。
Table 5-2. Most common SQLAlchemy column types

类型名称        Python类型       说明
Integer        int              常规整数, 一般是32位
SmallInteger   int            短形整数, 一般16位
BigInteger  int or  long     不限精度整数
Float          float            浮点数
Numeric     decimal.Decimal  定点数
String       str            变长字符串
Text           str            变长字符串,为大字符串或不限长字符串优化
Unicode     unicode         变长 Unicode 字符串
UnicodeText unicode       变长 Unicode字符串,为大字符串或不限长字符串优化
Boolean     bool             布尔值
Date           datetime.date    日期值
Time        datetime.timedelta  时间值
DateTime       datetime.datetime   日期时间值
Interval       datetime.timedelta  时间间隔
Enum           str               字符串列表  
PickleType     Any Python object   自动Pickle序列化
LargeBinary str                 二进制数据

db.Column的其他参数为 每一个属性指定配置选项。表5-3列出了一些有效的选项:
Table 5-3. Most common SQLAlchemy column options

选项名称        说明
primary_key    设置为True , 指定该列为主键
unique       设置为 True , 该列不允许出现重复值.
index          设置为 True , 为该列创建索引来提高查询效率
nullable       设置为 True , 允许该列出现空值. False , 则不允许空值.
default     指定一个默认值.

提醒
Flask-SQLAlchemy要求所有模型定义一个主键列,一般被命名为id。

虽然不是严格意义上需要,这两个模型都包含了一个__repr__()方法来提供一个可读的字符串表达,用于调试和测试。

关系

关系型数据库使用关系在各表间创建行级的连接。图5-1的关系图展示了用户和角色之间的一种简单关系。这是一种从角色到用户一对多的关系,一个角色被多个用户所有,而一个用户只有一个角色。
例5-3展示了在模型类中如何实现图5-1中的一对多关系:

Example 5-3. hello.py: Relationships
class Role(db.Model):
    # ...
    users = db.relationship('User', backref='role')
class User(db.Model):
    # ...
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

就如同图5-1所示,连接关系通过用户表中的外键连接两行。User模型中添加的role_id列,定义为一个外键这就创建了关系。db.ForeignKey()的‘role_id’参数指明这一列应该被解释为拥有来自于roles表中对应行的id的值。
  添加在Role模型的users属性显示了关系面向对象的观点。给出role类的实例,users属性就会返回与该角色相关的用户列表。db.relationship()的第一个参数指明了另外一边是哪个模型。如果类尚未定义,这个模型将被返回为一个字符串。
  db.relationship()的backref参数,通过给用户模型添加role属性来定义反方向的关系。这个属性可以替代role_id访问role模型——一个对象替代了一个外键。
  大部分情况下,db.relationship()能自动定位关系的外键,但有时候它判断不了到底哪个列被用作外键。例如:如果用户模型有两个或多个列定义为角色表的 外键,那么SQLAlchemy就无法判定到底哪个才是起作用的外键。不论何时,只要外键模糊不清,db.relationship()的附加参数就必须指定。表5-4列出了可以用来定义关系的常见配置项

Table 5-4. Common SQLAlchemy relationship options
选项名称        说明
backref        在其他模型中添加回溯引用关系.
primaryjoin    在两个模型中显示指定join条件,在关系模糊时必须使用之.
lazy           指定如何加载相关项. 可能的值是select(在第一次被访问时就加载相关项目), immediate(在源对象被加载时就加载之), joined (立即加载相关项,但是以join方式), subquery (以子查询方式立即加载), noload (从不加载), 和dynamic (通过指定的查询来加载相关项).
uselist        如设为 False, 使用标量代替列表
order_by       为关系中的列表指定排序.
secondary      在多对多关系中指定关联表的名字.
secondaryjoin  SQLAlchemy无法自行判断的情况下,为多对多关系指定join条件

除了 一对多关系外,还有其他一些关系类型。一对一关系就像一对多的简化版描述,但应该把在db.relationship()的uselist项设置为false,这样"多"的那一端就变成了"一"。多对一关系也可以认为是一对多,只是把表的指示定义方向反转,或者把外键的位置放在多的这一侧。。最复杂的关系类型就是多对多,它要求定义一个被称之为关联表的附加表。你将在第12章了解多对多关系。

数据库操作

现在符合图5-1关系的模型已经配置完成,可以使用了。了解模型工作方式最好的办法就是在Python shell里先试试。下面小节将带领你学习常见的数据库操作。

创建表

第一件事就是让Flask-SQLAlchemy基于模型类来帮助我们创建数据库。db.crate_all()函数就是干这个的:

(venv) $ python hello.py shell
>>> from hello import db
>>> db.create_all()

检查一下程序文件夹,你会发现新增了一个名为data.sqlite的文件,这个名字是根据配置信息来的。如果表已经存在于数据库中,db.create_all()函数不会重复创建或者更新它。如果你修改了模型需要更新已有的数据库结构的时候,这一点就不是很方便了——简单粗暴的解决办法就是先删除已有的表,再重建之:

>>> db.drop_all()
>>> db.create_all()

不幸的是,这个方法有一个你绝对不希望的副作用——它同时清除了表中所有的数据。结决办法在本章结束时讲到——升级数据库。

插入行

下列代码创建了几个角色和用户:

>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=user_role)

模型的构造函数接收键值对参数来初始化模型属性值。注意,甚至role属性都可以被使用,即使它不过是一对多关系的一种高级表现,而非真实的数据库列。新对象的id属性没有被明确指定:主键是由Flask-SQLAlchemy来管理的。现在这些对象仅仅存在于python内存中,还没有真正被写入到数据库。因此,你可以看到他们的id都没有赋值:

>>> print(admin_role.id)
None
>>> print(mod_role.id)
None
>>> print(user_role.id)
None

“数据库会话”管理着对数据库的更改,这是由Flask-SQLAlchemy提供的类似于db.session的一个对象。要把上述更改写入数据库,必须把它们都加入会话:

>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)

或者,更简洁些:

>>> db.session.add_all([admin_role, mod_role, user_role,user_john, user_susan, user_david])`

要把这些对象写入数据库,你需要调用会话的提交(commit方法:

>>db.session.commit()

再检查一下id属性,现在就都已经有值了:

>>> print(admin_role.id)
1
>>> print(mod_role.id)
2
>>> print(user_role.id)
3

提醒:数据库会话也可以"回滚(roll back)"。如果调用db.session.rollback(),我们添加在数据库会话中的任何对象都会被恢复成实际数据库中的状态。

修改行

数据库会话的add()方法也可以用来更新模型。在原shell会话中执行以下代码把"administrator"更名为"Admin":

>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
删除行

数据库会话还有delete()方法。以下指令删除了"Moderator"角色:

>>> db.session.delete(mod_role)
>>> db.session.commit()
查询行

Flask-SQLAlchemy在每个模型类中创建了一个“query”的对象。大部分对模型的基础查询就是来自对应表的实体的内容。

>>> Role.query.all()
[, ]
>>> User.query.all()
[, , ]

查询(query)对象可以使用filters(过滤器)执行特定数据库的搜索。下例展示了如何找出所有符合"User"角色的用户:

>>> User.query.filter_by(role=user_role).all()
[, ]

且,我们还可以把查询对象转化成字符串来检查原始SQL查询语句。

>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username,users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'

如果你结束当前的shell会话,上例中创建的对象将恢复成Python对象,在对应的数据库表中仍旧以行形式存在。如果你重新建一个shell会话,你必须从对应的数据库行中重建Python对象。下列代码展示了展示了名为“User”的用户具备的角色:

>>> user_role = Role.query.filter_by(name='User').first()

查询对象以filter_by()来使用过滤器,返回一个新的精确查询。多过滤器可以以队列形式调用,以根据需要完成查询。表5-5显示了一些常用过滤器。完整的过滤器列表请参照SQLAlchemy文档。

Table 5-5. Common SQLAlchemy query filters
选项            说明
filter()       返回一个在原有查询基础上添加条件过滤的新查询
filter_by()    返回一个在原有查询基础上增加了等于过滤的新查询
limit()     基于原有查询返回限制结果数量的新查询
offset()       基于对原有查询结果应用偏移量的新查询
order_by()   对原有查询结果进行指定标准排序的新查询
group_by()  对原有查询结果进行指定标准分组的新查询

在给查询配置好过滤器后,调用all()方法就可以执行查询,并以列表形式返回结果。当然,还有其他方法可以触发查询执行。表5-6显示了其他的查询执行方法

Table 5-6. Most common SQLAlchemy query executors
选项              说明
all()             以列表形式返回所有结果
first()        返回查询的第一个项,如果没有结果则返回None
first_or_404()  返回结果的第一项,如果没有结果则中断查询并发送404错误作为响应。
get()            返回匹配指定主键值的这一行,如无则返回None
get_or_404()      返回匹配指定主键值的这一行,如无则中断查询并发送404错误作为响应。
count()        返回查询结果的计数值
paginate()      返回指定范围的查询结果对象

关系就像查询一样工作。下例从两端查询用户和角色之间一对多的关系:

>>> users = user_role.users
>>> users
[, ]
>>> users[0].role

这的user_role.users查询有个小问题。当user_role.users表达式执行时,实际上是隐藏执行了一个查询,并内部调用了all()方法来返回用户list。因为查询对象是隐藏的,所以就无法再通过过滤器来进行精确筛选。在这个局部代码里,用来返回以字母排序的用户列表可能是有用的。在例子5-4中,关系配置被修改为lazy='dybnamic'这样查询就不会被自动执行。

Example 5-4. app/models.py: Dynamic relationships
class Role(db.Model):
# ...
users = db.relationship('User', backref='role', lazy='dynamic')
# ...

通过这样配置关系,user_role.users返回的查询还没有真正执行,我们就可以进一步添加过滤器筛选:

>>> user_role.users.order_by(User.username).all()
[, ]
>>> user_role.users.count()
2

在视图函数中使用数据库

上节描述的数据库操作也可以直接在视图函数中使用。例子5-5是新版本的首页路由将用户输入的名称存入数据库:

Example 5-5. hello.py: Database use in view functions
@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username = form.name.data)
            db.session.add(user)
            session['known'] = False
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('index.html',form = form,name = session.get('name'),known = session.get('known', False))

在这个修改后的版本中,每次提交名字时程序都会使用filter_by()查询过滤器从数据库中比对。变量known被写入用户会话,以便重定向后,把这个信息发送给模板,用以生成个性化的欢迎信息。注意为了让程序正常运行,你需要像前面所示的那样在Python shell中创建好数据库和表。
  新版的相关模板在例子5-6中。这个模板使用known参数给欢迎信息添加了第二行,它会针对新用户和已知用户显示不同信息。

Example 5-6. templates/index.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}

{{ wtf.quick_form(form) }}
{% endblock %}

集成Python Shell

每次启动一个shell会话都不得不导入数据库实例和模型,相当繁琐。为了解决每次的这些重复导入,Flask-Script shell命令行可以用来配置自动导入相关对象。
  为了把对象添加到导入列表,shell命令行需要被注册为make_context回调函数。请看例子5-7

Example 5-7. hello.py: Adding a shell context
from flask.ext.script import Shell
def make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role)
manager.add_command("shell", Shell(make_context=make_shell_context))

make_shell_context()函数注册了程序实例v、数据库实例以及模型,这样就可以自动将他们导入到shell:

$ python hello.py shell
>>> app

>>> db

>>> User

通过Flask-Migrate迁移数据库

在你程序开发流程中,你会发现数据模型可能需要变更,这时候就需要考虑数据库升级。
  Flask-SQLAlchemy是根据模型来创建表的——原本数据库中不存在该表,因此如果要升级表,唯一的办法就是删除旧表——当然,这样一来表里的数据也保不住了。
  更好的方案就是使用一个"数据库迁移"框架。就像源代码版本控制工具持续跟踪代码修改那样,数据库迁移框架也吃会持续追踪数据库结构的变化(model中的结构变化),然后变更实际数据库结构。
  Flask-SQLAlchemy的主要开发人员曾经设计过一个名为Alembic的迁移框架,但我们可以使用Flask-Migrate扩展——通过Flask-Scrit集成命令行包装并提供了alembic全部操作——来替代直接使用它。

创建迁移库

开始前,需要把Flask-Migrate安装到虚拟环境里:
  (venv) $ pip install flask-migrate
例子5-8说明了如何初始化这个扩展:

Example 5-8. hello.py: Flask-Migrate configuration
from flask.ext.migrate import Migrate, MigrateCommand
# ...
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)

为了使用数据库迁移命令行,Flask-Migrate将migrateCommand类附加到了Flask-Script的manager对象上。本例 中,命令行被附加在db上。
  在数据库迁移之前,还需要使用init子命令来创建一个迁移库:

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

这个命令创建了一个migrations文件夹,用来存放所有的迁移脚本。

迁移库中的文件必须随程序其他部分一起被加入到版本控制中去。

创建迁移脚本

在Alembic中,数据库迁移是通过一个"迁移脚本"完成的。这个脚本有两个函数:upgrade()和downgrade()。upgrade()更改数据库迁移/变更,而downgrade()则把迁移/变更部分从数据库中移除。由于具有了添加和移除更改的能力alembic能够把数据库配置到更改历史上的任何一点。
  Alembic迁移可以手动也可以适用revision和migrate命令自动进行。手动迁移使用两个空的upgrade()和downgrade()函数创建了一个迁移脚本,这个概要脚本需要开发者使用Albembic Operations对象提供的指令自己实现。而自动迁移,则根据当前数据库结构和模型定义之间的产别来生成upgrade()和downgrade()函数。

自动迁移并不十分可靠,有时候会丢失某些细节——你应该复查迁移脚本。

migrate子命令将创建一个自动迁移脚本:

(venv) $ python hello.py db migrate -m "initial migration"
INFO [alembic.migration] Context impl SQLiteImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate] Detected added table 'roles'
INFO [alembic.autogenerate] Detected added table 'users'
INFO [alembic.autogenerate.compare] Detected added index
'ix_users_username' on '['username']'
Generating /home/flask/flasky/migrations/versions/1bc
594146bb5_initial_migration.py...done

升级数据库

一旦迁移脚本检查无误,就可以适用upgrade命令来提交数据库更改。

(venv) $ python hello.py db upgrade
INFO [alembic.migration] Context impl SQLiteImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.migration] Running upgrade None -> 1bc594146bb5, initial migration

若是第一次迁移,基本等同于调用db.crate_all()。但在后续迁移/升级中,upgrade命令则可以无损升级数据表(更改结构而不影响其内容)。
  关于数据库设计和使用用这一主题非常重要,整本书也是基于这一主题。你应该通过本章有个基本了解,更多的高级主题将在后续章节中谈到。下一章是关于发送Email的。
<<第四章 Web表单 第六章 EMail>>

你可能感兴趣的:(Flask Web DEvelopment 翻译4)