扩展Flask-SQLAlchemy
集成了SQLAlchemy
,它简化了连接数据库服务器、管理数据库操作会话等各类工作,让Flask中的数据处理体验变得更加轻松。
首先使用pipenv
安装Flask-SQLAlchemy
:
pipenv install flask-sqlalchemy
实例化Flask-SQLAlchemy,传入程序实例app,以完成初始化:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(64),unique=True)
password = db.Column(db.String(64))
SQLAlchemy常用的字段类型
字 段 | 说 明 |
---|---|
Integer | 整数 |
String | 字符串,可以通过参数length指定字符串长度 |
Text | 较长的Unicode文本 |
Date | 日期,存储Python的datetime.data对象 |
Time | 时间,存储Python的datetime.time对象 |
Datetime | 时间和日期,存储Python的datetime对象 |
Interval | 时间间隔,存储Python的datetime.timedalta对象 |
Float | 浮点数 |
Boolean | 布尔值 |
PickleType | 存储Pickle序列化的Python对象 |
LargeBinary | 存储任意二进制数据 |
默认情况下,Flask-SQLAlchemy会根据模型类的名称生成一个表名称,生成规则如下:
Message --> message # 单个单词转换为小写 FooBar --> foo_bar #多个单词转换为小写并使用下划线分隔
如果想自己指定表名称,可以通过定义__tablename__属性来实现
SQLAlchemy常用的字段参数
参数名 | 说 明 |
---|---|
primary_key | 如果设为True,该字段为主键 |
unique | 如果设为True,该字段不允许出现重复值 |
index | 如果设为True,为该字段创建索引,以提高查询效率 |
nullable | 确定字段值可否为空,值为True或False,默认值为True |
default | 为字段设置默认值 |
创建模型类后,我们需要手动创建数据库和对应的表,也就是我们常说的建库和建表。这通过对我们的db对象调用create_all()方法实现:
from app import db
db.drop_all()
db.create_all()
数据库和表一旦创建后,之后对模型的改动不会自动作用到实际的表中。
比如,在模型类中添加或删除字段,修改字段的名称和类型,这时再次调用create_all()也不会更新表结构。
如果要使改动生效,最简单的方式是调用db.drop_all()
方法删除数据库和表,然后再调用db.create_all()
方法创建。
数据库操作主要是CRUD,即Create(创建)、Read(读取/查询)、Update(更新)、Delete(删除)。
SQLAlchemy使用数据库会话来管理数据库操作,这里的数据库会话也称为事物(transaction)。Flask-SQLAlchemy自动帮我们创建会话,可以通过db.session属性获取。
数据库中的会话代表一个临时存储区,你对数据库做出的改动都会存放在这里。你可以调用add()
方法将新创建的对象添加到数据库会话中,或是对会话中的对象进行更新。
只有当你对数据库会话对象调用commit()
方法时,改动才会被提交到数据库,这确保了数据提交的一致性。另外,数据库会话也支持回滚操作。
当你对会话调用rollback()
方法时,添加到会话中且未提交的改动都将被撤销。
默认情况下,Flask-SQLAlchemy会自动为模型类生成一个__repr__()方法。当调用模型的对象时,repr()方法会返回一条类似“<模型类名 主键值>”的字符串,比如
class Student(db.Model):
...
def __repr__(self):
return '<Student %r>' % self.id
添加一条新记录到数据库主要分为三步:
* 1). 创建Python对象(实例化模型类)作为一条记录。
* 2). 添加新创建的记录到数据库会话。
* 3). 提交数据库会话。
from app import db
from models import Student
s1 = Student(name="August Rush", age=18, sex='male')
s2 = Student(name="Taylor Swift", age=22, sex='female')
s3 = Student(name="Grecia Gao", age=18, sex='female')
db.session.add(s1)
db.session.add(s2)
db.session.add(s3)
db.session.commit()
除了依次调用add()方法添加多个记录,也可以使用add_all()一次添加包含所有记录对象的列表。 db.session.add_all([s1,s2,s3])
使用模型类提供的query属性附加调用各种过滤方法及查询方法可以达到读取/查询数据的任务。
一般来说,一个完整的查询遵循下面的模式:
<模型类>.query.<过滤方法>.<查询方法>
SQLAlchemy常用的查询方法
查询方法 | 说 明 |
---|---|
all() | 返回包含所有查询记录的列表 |
first() | 返回查询的第一条记录,如果未找到,则返回None |
one() | 返回第一条记录,且仅允许有一条记录。如果记录数量大于1或小于1,则抛出错误 |
get(ident) | 传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回None |
count() | 返回查询结果的数量 |
one_or_none() | 类似one(),如果结果数量不为1,返回None |
first_or_404() | 返回查询的第一条记录,如果未找到,则返回404错误响应 |
get_or_404(ident) | 传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回404错误响应 |
paginate() | 返回一个Pagination对象,可以对记录进行分页处理 |
with_parent(instance) | 传入模型类实例作为参数,返回和这个实例相关联的对象 |
>>> Student.query.all()
[<Student 1>, <Student 2>, <Student 3>]
>>> s1 = Student.query.first()
>>> s1
<Student 1>
>>> s1.name
August Rush
>>> s2 = Student.query.get(2)
>>> s2
<Student 2>
>>> Student.query.count()
3
SQLAlchemy常用的过滤方法
查询过滤器名称 | 说 明 |
---|---|
filter() | 使用指定的规则过滤记录,返回新产生的查询对象 |
filter_by() | 使用指定规则过滤记录(以关键字表达式的形式),返回新产生的查询对象 |
order_by() | 根据指定条件对记录进行排序,返回新产生的查询对象 |
limit(limit) | 使用指定的值限制原查询返回的记录数量,返回新产生的查询对象 |
group_by() | 根据指定条件对记录进行分组,返回新产生的查询对象 |
offset(offset) | 使用指定的值偏移原查询的结果,返回新产生的查询对象 |
>>> Student.query.filter(Student.age=18)
[<Student 1>, <Studen 3>]
>>> Student.query.filter(Student.age=18).first()
<Student 1>
在filter()方法中传入表达式时,除了"=="以及表示不等于的"!=",其他常用的查询操作符以及使用示例如下所示:
# LIKE:
filter(Student.name.like('%ugu%'))
# IN:
filter(Student.name.in_(['august', 'taylor', 'grecia']))
# NOT:
from sqlalchemy import not_
filter(not_(Student.name == "August Rush"))
# AND:
# 使用and_()
from sqlalchemy import and_
filter(and_(Student.name!="August Rush", Student.age==18))
# 或在filter()中加入多个表达式,使用逗号分隔
filter(Student.name!="August Rush", Student.age==18)
# 或叠加调用多个filter()/filter_by()方法
filter(Student.name!="August Rush").filter(Student.age==18)
# OR:
from sqlalchemy import or_
filter(or_(Student.name=="August Rush", Student.age==18))
和filter方法相比,filter_by()方法更易于使用。在filter_by()方法中,你可以使用关键字表达式来指定过滤规则。甚至可以在这个过滤器中直接使用指定名称。
Student.query.filter_by(age=22).first()
更新一条记录非常简单,直接赋值给模型类的字段属性就可以改变字段值,然后调用commit()方法添加会话即可。
>>> s1 = Student.query.get(1)
>>> s1.name
August Rush
>>> s1.name = 'Rainbow August'
>>> db.session.commit()
删除记录和添加记录很相似,不过要把add()方法换成delete()方法,最后都需要调用commit()方法提交修改。
>>> s1 = Student.query.get(1)
>>> db.session.delete(s1)
>>> db.session.commit()
在关系型数据库中,我们可以通过关系让不同表之间的字段建立联系。一般来说,定义关系需要两步,分别是创建外键和定义关系属性。
在更复杂的多对多关系中,我们还需要定义关联表来管理关系。
将以作者和文章来演示一对多关系:一个作者可以写多篇文章。
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
phone = db.Column(db.String(20))
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(64))
content = db.Column(db.Text)
我们将在这两个模型之间建立一个简单的一对多关系
定义关系的第一步是创建外键。因为外键只能存储单一数据(标量),所以外键总是在“多”这一侧定义。
class Author(db.Model):
...
author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
这个字段使用db.ForeignKey
类定义为外键,传入关系另一侧的表名和主键名,即author.id。
实际的效果是将article
表的author_id
的值限制为author
表的id
列的值。它将用来存储author
表中记录的主键值。
传入ForeignKey
类的参数author.id
,其中author
指的是Author模型对应的表名称,而id指的是字段名,即“表名.字段名”。
定义关系的第二步是使用关系函数定义关系属性。关系属性在关系的出发侧定义,即一对多关系的“一”这一侧。
class Author(db.Model):
...
article = db.relationship('Article')
这个属性使用了db.relationship()关系函数定义为关系属性,因为这个关系属性返回多个记录,我们称之为集合关系属性。
relationship()函数的第一个参数为关系另一侧的模型名称,它会告诉SQLAlchemy将Author类与Article类建立关系。
当这个关系属性被调用时,SQLAlchemy会找到关系另一侧的外键字段,然后反向查询article表中所有author_id值为当前表主键值的记录, 返回包含这些记录的列表。
建立关系有两种方式,第一种方式是为外键字段赋值,比如:
>>> article1.author_id = 1
>>> db.session.commit()
>>> author.article
[<Article 1>]
另一种方式是通过操作关系属性,将关系属性赋给实际的对象即可建立关系。集合关系属性可以像列表一样操作,调用append()方法来与一个Article对象建立关系:
>>> author.article.append(article2)
>>> author.article.append(article3)
>>> db.session.commit()
>>> author.article
[<Article 1>, <Article 2>, <Article 3>]
和append()相对,对关系属性调用remove()方法可以与对应的Article对象解除关系:
>>> author.article.remove(article1)
>>> db.session.commit()
>>> author.article
[<Article 1>, <Article 2>]
还可以使用pop()方法操作关系属性,它会与关系属性对应的列表的最后一个Article对象解除关系并返回该对象:
>>> v = author.article.pop()
>>> v
<Aricle 2>
使用关系函数定义的属性不是数据库字段,而是类似于特定的查询函数。
当某个Article对象被删除时,在对应Author对象的article属性调用时返回的列表页不会包含该对象。
在关系函数中,有很多参数可以用来设置调用关系属性进行查询时的具体行为。常用的关系函数如:
参 数 名 | 说 明 |
---|---|
back_populates | 定义反向引用,用于建立双向关系,在关系的另一侧也必须显式定义关系属性 |
backref | 添加反向引用,自动在另一侧建立关系属性,是back_populates的简化版 |
lazy | 指定如何加载相关记录,具体选项见下表 |
uselist | 指定是否使用列表的形式加载记录,设为False则使用标量(scalar) |
caseade | 设置级联操作 |
order_by | 指定加载相关记录时的排序方式 |
secondary | 在多对多关系中指定关联表 |
primaryjoin | 指定多对多关系中的一级联结条件 |
secondaryjoin | 指定多对多关系中的二级联结条件 |
关系记录加载方式(lazy参数可选值)
关系加载方式 | 说 明 |
---|---|
select | 在必要时一次性加载记录,返回包含记录的列表(默认值),等同于lazy=True |
joined | 和父查询一样加载记录,但使用联结,等同于lazy=False |
immediate | 一旦父查询加载就加载 |
subquery | 类似于joined,不过将使用子查询 |
dynamic | 不直接加载记录,而是返回一个包含相关记录的query对象,以便再继续附加查询函数对结果进行过滤 |
我们在Author类中定义了集合关系属性articles,用来获取某个作者拥有的多篇文章记录。
在某些情况下,你也许希望能在Article类中定义一个类似的author关系属性,当被调用时返回对应的作者记录,这类返回单个值的关系属性被称为标量关系属性。
而这种两侧都添加关系属性获取对方记录的关系我们称之为双向关系。
双向关系并不是必须的,但在某些情况下会非常方便。
双向关系的建立很简单,通过在关系的另一侧也创建一个relationship()函数,我们就可以在两个表之间建立双向关系。
class Author(db.Model):
__tablename__ = 'author'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
phone = db.Column(db.String(20))
article = db.relationship('Article', back_populates='author')
def __repr__(self):
return "%s" % self.name
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(64))
content = db.Column(db.Text)
author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
author = db.relationship("Author", back_populates='article')
def __repr__(self):
return "%s" % self.title
在“多”这一侧的Article类中,我们新创建了一个author关系属性,这是一个标量关系属性,调用它会获取对应的Author记录;
而在Author类中的article属性则用来获取对应的多个Article记录。在关系函数中,我们使用back_populates参数来连接对方,
back_populates参数的值需要设为关系另一侧的关系属性名
。
设置双向关系后,除了通过集合属性article来操作关系,我们也可以使用标量属性author来进行关系操作。
比如,将一个Author对象赋值给某个Article对象的author属性,就会和这个Article对象建立关系。
相对的,将某个Article的author属性设为None,就会解除与对应Author对象的关系。
需要注意的是,我们只需要在关系的一侧操作关系即可。
当为Article对象的author属性赋值后,对应的Author对象的article属性的返回值也会自动包含这个Article对象。
反之,当某个Author对象被删除时,对应的Article对象的author属性被调用时的返回值也会被置为空。
backref参数用来自动为关系另一侧添加关系属性,作为反向引用,赋予的值会作为关系另一侧的关系属性名称。
比如,我们在Author一侧的关系函数中将backref参数设为author,SQLAlchemy会自动为Article类添加一个author属性。
class Singer(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
songs = db.relationship('Song', backref='singer')
class Song(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
singer_id = db.Column(db.Integer, db.ForeignKey('singer.id'))
在定义集合属性songs的关系函数中,我们将backref参数设为singer,这会同时在Song类中添加一个singer标量属性。
这时我们仅需要定义一个关系函数,虽然singer是一个“看不见的关系属性”,但在使用上和定义两个关系函数并使用back_populates参数的效果完全相同。
尽管使用backref非常方便,但通常来说“显示好过隐式”,所以我们应该尽量使用back_populates定义双向关系。
基于Nginx+Supervisord+uWSGI+Django1.11.1+Python3.6.5构建