1 定义模型
在 ORM 中,模型一般是一个 Python 类, 代表数据库中的一张表, 类中的属性对应数据库表中的列。Flask-SQLAlchemy 创建的数据库实例为模型提供了一个基类db.Model以及一系列辅助类和辅助函数,可用于定义 模型/表 的结构。下面的例子定义了两个表,一个是用户角色,一个是用户信息
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 '<Role %r>' % 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 '<User %r>' % self.username
db.Column 类构造函数的第一个参数是数据库表列/模型属性 的类型
db.Column 中其余的参数指定属性的配置选项
。
选项名 | 说 明 |
---|---|
primary_key | 如果设为 True,这列就是表的主键 |
unique | 如果设为 True,这列不允许出现重复的值 |
index | 如果设为 True,为这列创建索引,提升查询效率 |
nullable | 如果设为 True,这列允许使用空值;如果设为 False,这列不允许使用空值 |
default |
为这列定义默认值 |
2 数据库操作
2.1 创建表
SQLAlchemy 的实现,可以看到 Model 是一个元类,在继承这个类来声明子类的时候,会把表模型注册到 sqlalchemy 里,所以在 create_all 之前必须把模型的声明导入进来, 比如你可能需要这样做from model.account import User,User的定义是def User(db.Model)这样的,比如
(venv) $ python hello.py shell # 进入 Python shell
>>> from hello import db # 从`hello.py`导入创建的数据库实例
>>> db.create_all()
查看程序目录,会发现新建了一个名为app.db的文件。这个 SQLite 数据库文件 的名字就是在配置中指定的。如果数据库表已经存在于数据库中,那么 db.create_all() 不会重新创建或者更新这个表
更新现有数据库表的粗暴方式是先删除旧表
再重新创建:
>>> db.drop_all()
>>> db.create_all()
这个方法有个我们不想看到的副作用,它把数据库中原有的数据都销毁
了。末尾将会介绍一种称为数据库迁移
的方式用于更新数据库
2.1 插入记录
>>> 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 尚未赋值
通过数据库会话管理对数据库所做的改动,在 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()
2.1 修改记录
下面这个例子把 "Admin" 角色重命名为 "Administrator":
>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
2.1 删除记录
数据库会话还有个 delete() 方法。下面这个例子把 "Moderator" 角色从数据库中删除
>>> db.session.delete(mod_role)
>>> db.session.commit()
2.1 查询记录
Flask-SQLAlchemy 为每个模型类都提供了 query 对象。最基本的模型查询是取回对应表中的所有记录:
>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
>>> User.query.all()
[<User u'john'>, <User u'susan'>, <User u'david'>]
使用过滤器可以配置 query 对象进行更精确的数据库查询。下面这个例子查找角色为 "User" 的所有用户:
>>> User.query.filter_by(role=user_role).all() # user_role = Role(name='User'), role=user_role
[<User u'susan'>, <User u'david'>]
若要查看 SQLAlchemy 为查询生成的原生 SQL 查询语句,只需把 query 对象转换成字符串 :
>>> 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()
可在 query 对象上调用的常用过滤器
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit() | 使用指定的值限制原查询返回的结果数量,返回一个新查询 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
在查询上应用指定的过滤器后
,通过调用 all() 执行查询,以列表的形式返回结果。除了 all() 之外,还有其他方法能触发查询执行
。
常用查询执行函数
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果没有结果,则返回 None |
first_or_404() | 返回查询的第一个结果,如果没有结果,则终止请求,返回 404 错误响应 |
get() | 返回指定主键对应的行,如果没有对应的行,则返回 None |
get_or_404() | 返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回 404 错误响应 |
count() | 返回查询结果的数量 |
paginate() | 返回一个 Paginate 对象,它包含指定范围内的结果 |
下面这个例子分别从关系的两端查询角色和用户之间的一对 多关系:
>>> users = user_role.users
>>> users
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>
这个例子中的 user_role.users 查询有个小问题。执行 user_role.users 表达式时,隐含的查询会调用 all() 返回一个用户列表。query 对象是隐藏的,因此无法指定更精确的查询 过滤器。就这个特定示例而言,返回一个按照字母顺序排序的用户列表可能更好。
在示例 5-4中,我们修改了关系的设置,加入了lazy = 'dynamic'参数,从而禁止自动执行查询。
class Role(db.Model):
# ...
users = db.relationship('User', backref='role', lazy='dynamic')
# ...
这样配置关系之后,user_role.users 会返回一个尚未执行的查询,因此可以在其上添加过 滤器:
>>> user_role.users.order_by(User.username).all()
[<User u'david'>, <User u'susan'>]
>>> user_role.users.count()
2