Flask-SQLAlchemy扩展简介
Flask-SQLAlchemy扩展集成了SQLAlchemy,它简化了连接数据库服务器、管理数据库、操作会话等各类工作, 使得我们在Flask中使用SQLAlchemy更加的简单。
Flask-SQLAlchemy扩展安装
Flask-SQLAlchemy的安装非常简单,直接使用pip安装即可,如果在安装Flask-SQLAlchemy之前没有安装SQLAlchemy,Python会自动帮我们安装Flask-SQLAlchemy的依赖库SQLAlchemy。
# 安装flask-SQLAlchemy,python会自动帮我们安装其依赖库SQLAlchemy
pip install flask-SQLAlchemy
Flask-SQLAlchemy的基本使用
常见情况下对于只有一个Flask应用,所以您需要做的事情就是创建Flask应用,选择加载配置接着创建 SQLAlchemy对象时候把Flask应用传递给它作为参数。
一旦创建,这个对象就包含SQLAlchemy和SQLAlchemy.orm中的所有函数和助手。此外它还提供一个名为 Model的类,用于作为声明模型时的delarative基类:
from flask import Flask
# 从flask_SQLAlchemy模块中导入SQLAlchemy类
from flask_SQLAlchemy import SQLAlchemy
app = Flask(__name__)
# 配置连接数据库的相关参数
app.config['SQLAlchemy_DATABASE_URI'] = 'sqlite:////tmp/test.db'
# 实例化一个SQLAlchemy对象,并给它绑定到Flask应用上(app)
db = SQLAlchemy(app)
# 实例化后的SQLAlchemy对象(db)有一个名为 Model的类,定义模型类需要继承db.Model
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return '<User %r>' % self.username
Flask-SQLAlchemy的相关配置
连接配置
要连接数据库服务器,首先要为Flask程序指定数据库URI(Uniform Resource Identifier,统一资源标识符)。数据库URI是一串包含各种属性的字符串,其中包含了各种用于连接数据库的信息。
SQLAlchemy把一个引擎的源表示为一个连同设定引擎选项的可选字符串参数的URI。URI的形式是:
dialect+driver://username:password@host:port/database
该字符串中的许多部分是可选的。如果没有指定驱动器,会选择默认的(确保在这种情况下不 包含 + )。
在Flask-SQLAlchemy中,数据库的URI通过配置变量SQLAlchemy_DATABASE_URI设置,默认为SQLite内存型数据库(sqlite:///:memory:) 。SQLite 是基于文件的DBMS ,不需要设置数据库服务器,只需要指定数据库文件的绝对路径。注意:
- SQLite的数据库URI 在Linux或macOS系统下的斜线数量是4个;在Windows系统下的URI中的斜线数量为3个。内存型数据库的斜线固定为3个。
- SQLite数据库文件名不限定后缀,常用的命名方式有foo.sqlite, foo.db,或是注明SQLite版本的foo.sqlite3
其他配置
安装并初始化Flask-SQLAlchemy后,启动程序时会看到命令行下有一行警告信息。这是因为Flask-SQLAlchemy建议你设置SQLAlchemy_TRACK_MODIFICATIONS配置变量,这个配置变量决定是否追踪对象的修改,这用于Flask-SQLAlchemy的事件通知系统。这个配置键的默认值为None,如果没有特殊需要,我们可以把它设为False 来关闭警告信息:
app.config ['SQLAlchemy_TRACK_MODIFICATIONS'] = False
定义数据库模型
用来映射到数据库表的Python类通常被称为数据库模型(model),一个数据库模型类对应
数据库中的一个表。定义模型即使用Python类定义表模式, 并声明映射关系。
需要牢记的事情:
- 所有模型的基类都叫做db.Model。它存储在您必须创建的SQLAlchemy实例上。
- 有一些部分在SQLAlchemy上是必选的,但是在Flask-SQLAlchemy上是可选的。 比如表名是自动地为您设置好的,除非您想要覆盖它。它是从转成小写的类名派生出来的,即 “CamelCase” 转换为 “camel_case”。
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
def __repr__(self):
return '<User %r>' % self.username
用db.Column类的实例来定义一个字段。字段名就是赋值给这个变量的名称。如果想要在表中使用不同的名称, 可以提供一个想要的列名的字符串作为可选第一个参数或者提供一个名为name的关键字参数。主键用 primary_key=True标记。可以把多个键标记为主键,此时它们作为复合主键。
定义模型类时如果没有定义id自增长主键,SQLAlchemy会自动生成一个id主键,主键由SQLAlchemy 管理。
字段的类型是 db.Column的第一个参数。可以直接提供它们或进一步规定(比如提供一个长度)。下面的类型是最常用的:- Integer:整形,映射到数据库中是int类型。
- Float:浮点类型,映射到数据库中是float类型。他占据的32位。
- Double:双精度浮点类型,映射到数据库中是double类型,占据64位。
- String:可变字符类型,映射到数据库中是varchar类型.
- Boolean:布尔类型,映射到数据库中的是tinyint类型。
- DECIMAL:定点类型。是专门为了解决浮点类型精度丢失的问题的。在存储钱相关的字段的时候建议大家都使用这个数据类型。并且这个类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数字,第二个参数表示小数点后有多少位。
- Enum:枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值。在ORM模型中,使用Enum来作为枚举,示例代码如下:
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
tag = db.Column(db.Enum("python",'flask','django'))
在Python3中,已经内置了enum这个枚举的模块,我们也可以使用这个模块去定义相关的字段。示例代码如下:
class TagEnum(enum.Enum):
python = "python"
flask = "flask"
django = "django"
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
tag = db.Column(db.Enum(TagEnum))
article = Article(tag=TagEnum.flask)
- Date:存储时间,只能存储年月日。映射到数据库中是date类型。在Python代码中,可以使用datetime.date来指定。示例代码如下:
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
create_time = db.Column(db.Date)
article = Article(create_time=date(2017,10,10))
- DateTime:存储时间,可以存储年月日时分秒毫秒等。映射到数据库中也是datetime类型。在Python代码中,可以使用datetime.datetime来指定。示例代码如下:
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
create_time = db.Column(db.DateTime)
article = Article(create_time=datetime(2011,11,11,11,11,11))
- Time:存储时间,可以存储时分秒。映射到数据库中也是time类型。在Python代码中,可以使用datetime.time来至此那个。示例代码如下:
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
create_time = db.Column(db.Time)
article = Article(create_time=time(hour=11,minute=11,second=11))
- Text:存储长字符串。一般可以存储6W多个字符。如果超出了这个范围,可以使用LONGTEXT类型。映射到数据库中就是text类型。
- LONGTEXT:长文本类型,映射到数据库中是longtext类型。
默认情况下, Flask-SQLAlchemy会根据模型类的名称生成一个表名称,生成规则如下:
Message --> message #单个单词转换为小写
FooBar --> foo_bar #多个单词转换为小写并使用下划线分隔
如果你想自己指定表名称,可以通过定义__tablename__属性来实现。
以下是实例化字段类时常用的字段参数:
- default:默认值。
- nullable:是否可空。
- primary_key:是否为主键。
- unique:是否唯一。
- index:是否设置为索引
- autoincremen:是否自动增长。
- onupdate:更新的时候执行的函数。
- name:该属性在数据库中的字段映射。
创建数据库和删除数据库
创建数据库
创建模型类后,我们需要手动创建数据库和对应的表,也就是我们常说的建库和建表。这通过对我们的db 对象调用create_all()方法实现。
from flask import Flask
# 从flask_SQLAlchemy模块中导入SQLAlchemy类
from flask_SQLAlchemy import SQLAlchemy
app = Flask(__name__)
# 配置连接数据库的相关参数
app.config['SQLAlchemy_DATABASE_URI'] = 'sqlite:////tmp/test.db'
# 实例化一个SQLAlchemy对象,并给它绑定到Flask应用上(app)
db = SQLAlchemy(app)
# 实例化后的SQLAlchemy对象(db)有一个名为 Model的类,定义模型类需要继承db.Model
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
db.create_all()
删除数据库
如果想删除已经创建的所有表可以通过调用db对象的drop_all()方法实现:
from flask import Flask
# 从flask_SQLAlchemy模块中导入SQLAlchemy类
from flask_SQLAlchemy import SQLAlchemy
app = Flask(__name__)
# 配置连接数据库的相关参数
app.config['SQLAlchemy_DATABASE_URI'] = 'sqlite:////tmp/test.db'
# 实例化一个SQLAlchemy对象,并给它绑定到Flask应用上(app)
db = SQLAlchemy(app)
# 实例化后的SQLAlchemy对象(db)有一个名为 Model的类,定义模型类需要继承db.Model
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
db.create_all()
db.drop_all()
注意:
数据库和表一旦创建后,之后对模型的改动不会自动作用到实际的表中。在模型中添加或删除字段,修改字段的名称和类型,这时再次调用create_all()也不会更新表结构。如果要使改动生效,最简单的方式是调用db.drop_all()方法删除数据库和表,然后再调用db.create_all()方法创建表。
数据库操作
数据库操作主要是CRUD,即Create(创建)、Read(读取/查询)、Update(更新) 和Delete(删除)。SQLAlchemy使用数据库会话来管理数据库操作,这里的数据库会话也称为事务(transaction)。Flask-SQLAlchemy会自动帮我们创建会话,可以通过db.session属性获取。
数据库中的会话代表一个临时存储区,你对数据库做出的改动都会存放在这里。你可以调用add()方法将新创建的对象添加到数据库会话中,或是对会话中的对象进行更新。只有当你对数据库会话对象调用commit()方法时,改动才被提交到数据库,这确保了数据提交的一致性。另外,数据库会话也支持回滚操作。当你对会话调用rollback()方法时,添加到会话中且未提交的改动都将被撤销。
添加数据
- 创建Python对象(实例化模型类)作为一条记录。
- 添加新创建的记录到数据库会话。
- 提交数据库会话。
from flask import Flask
# 从flask_SQLAlchemy模块中导入SQLAlchemy类
from flask_SQLAlchemy import SQLAlchemy
app = Flask(__name__)
# 配置连接数据库的相关参数
app.config['SQLAlchemy_DATABASE_URI'] = 'sqlite:////tmp/test.db'
# 实例化一个SQLAlchemy对象,并给它绑定到Flask应用上(app)
db = SQLAlchemy(app)
class Note(db.Model) :
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
# 向数据库中添加3条书签信息
notel = Note(body = '记得记录学习心得!')
note2 = Note(body = '明天和小明一起去海边玩!')
note3 = Note(body = '把报告做完,明天需要汇报!' )
db.session.add(notel)
db.session.add(note2)
db.session.add(note3)
db.session.commit()
模型类对象创建后作为临时对象(transient),当你提交数据库会话后,模型类对象才会转换为数据库记录写入到数据库中。
查询数据
那么我们怎么从数据库中查询数据?为此,Flask-SQLAlchemy在Model类上提供了query属性。当访问它时,会得到一个新的所有记录的查询对象。在使用all()或者first()发起查询之前可以使用方法 filter()来过滤记录。如果您想要用主键查询的话,也可以使用get()。
一般来说,一个完整的查询遵循下面的模式:
<模型类>.query.<过滤方法>.<查询方法>
<SQLAlchemy对象(db)>.session.query(模型类).<过滤方法>.<查询方法>
从某个模型类出发,通过在query属性对应的Query对象上附加的过滤方法和查询函数对模型类对应的表中的记录进行各种筛选和调整, 最终返回包含对应数据库记录数据的模型类实例,对返回的实例调用属性即可获取对应的字段数据。
first_or_404()、get_or_404()以及paginate()方法是Flask-SQLAlchemy 附加的查询方法,而非SQLAlchemy的查询方法。
all()返回所有记录:
note = Note.query.all()
print(note)
[<Note 1> , <Note 2> , <Note 3>]
first()返回第一条记录:
note1 =Note.query.first()
print(note1)
print(note1.body)
<Note 1>
记得记录学习心得!
get()返回指定主键值(id字段)的记录:
note2 = Note .query.get(2)
print(note2)
<Note 2>
count()返回记录的数量:
count = Note.query.count()
print(count)
3
SQLALchermy还提供了许多过滤方法,使用这些过滤方法可以获取更精确的查询,比如获取指定字段值的记录。对模型类的query属性存储的Query对象调用过滤方法将返回一个更精确的Query对象(后面我们简称为查询对象)。因为每个过滤方法都会返回新的查询对象,所以过滤器可以叠加使用。在查询对象上调用前面介绍的查询方法,即可获得一个包含过滤后的记录的列表。
和filter()方法相比,filter_by()方法更易于使用。在filter_by()方法中,你可以使用关键字表达式来指定过滤规则。更方便的是,你可以在这个过滤器中直接使用字段名称。
query可用参数:
- 模型对象。指定查找这个模型中所有的对象。
- 模型中的属性。可以指定只查找某个模型的其中几个属性。
- 聚合函数。
- func.count:统计行的数量。
- func.avg:求平均值。
- func.max:求最大值。
- func.min:求最小值。
- func.sum:求和。
过滤条件:
过滤是数据提取的一个很重要的功能,以下对一些常用的过滤条件进行解释,并且这些过滤条件都是只能通过filter方法实现的:
过滤是数据提取的一个很重要的功能,以下对一些常用的过滤条件进行解释,并且这些过滤条件都是只能通过filter方法实现的:
- equals:
query.filter(User.name == 'ed')
- not equals:
query.filter(User.name != 'ed')
- like:
query.filter(User.name.like('%ed%'))
- in:
query.filter(User.name.in_(['ed','wendy','jack']))
# 同时,in也可以作用于一个Query
query.filter(User.name.in_(session.query(User.name).filter(User.name.like('%ed%'))))
- not in:
query.filter(~User.name.in_(['ed','wendy','jack']))
- is null:
query.filter(User.name==None)
# 或者是
query.filter(User.name.is_(None))
- is not null:
query.filter(User.name != None)
# 或者是
query.filter(User.name.isnot(None))
- and:
from sqlalchemy import and_
query.filter(and_(User.name=='ed',User.fullname=='Ed Jones'))
# 或者是传递多个参数
query.filter(User.name=='ed',User.fullname=='Ed Jones')
# 或者是通过多次filter操作
query.filter(User.name=='ed').filter(User.fullname=='Ed Jones')
- or:
from sqlalchemy import or_
query.filter(or_(User.name=='ed',User.name=='wendy'))
查找方法:
介绍完过滤条件后,有一些经常用到的查找数据的方法也需要解释一下:
- all():返回一个Python列表(list):
query = session.query(User).filter(User.name.like('%ed%').order_by(User.id)
# 输出query的类型
print type(query)
<type 'list'>
# 调用all方法
query = query.all()
# 输出query的类型
print type(query)
<class 'sqlalchemy.orm.query.Query'>
- first():返回Query中的第一个值:
user = session.query(User).first()
print(user)
><User(name='ed', fullname='Ed Jones', password='f8s7ccs')>
- one():查找所有行作为一个结果集,如果结果集中只有一条数据,则会把这条数据提取出来,如果这个结果集少于或者多于一条数据,则会抛出异常。总结一句话:有且只有一条数据的时候才会正常的返回,否则抛出异常:
# 多于一条数据
user = query.one()
> Traceback (most recent call last):
> ...
> MultipleResultsFound: Multiple rows were found for one()
# 少于一条数据
user = query.filter(User.id == 99).one()
> Traceback (most recent call last):
> ...
> NoResultFound: No row was found for one()
# 只有一条数据
> query(User).filter_by(name='ed').one()
- one_or_none():跟one()方法类似,但是在结果集中没有数据的时候也不会抛出异常,会返回None。
- scalar():底层调用one()方法,并且如果one()方法没有抛出异常,会返回查询到的第一列的数据,在结果集中没有数据的时候也不会抛出异常,会返回None:
User.query.filter_by(name='ed').scalar()
文本SQL:
SQLAlchemy还提供了使用文本SQL的方式来进行查询,这种方式更加的灵活。而文本SQL要装在一个text()方法中,看以下例子:
from SQLAlchemy import text
for user in User.query.filter(text("id<244")).order_by(text("id")).all():
print user.name
如果过滤条件比如上例中的244存储在变量中,这时候就可以通过传递参数的形式进行构造:
User.query.filter(text("id<:value and name=:name")).params(value=224,name='ed').order_by(User.id)
在文本SQL中的变量前面使用了:来区分,然后使用params方法,指定需要传入进去的参数。另外,使用from_statement方法可以把过滤的函数和条件函数都给去掉,使用纯文本的SQL:
User.query.from_statement(text("select * from users where name=:name")).params(name='ed').all()
使用from_statement方法一定要注意,from_statement返回的是一个text里面的查询语句,一定要记得调用all()方法来获取所有的值。
计数(Count):
Query对象有一个非常方便的方法来计算里面装了多少数据:
User.query.filter(User.name.like('%ed%')).count()
当然,有时候你想明确的计数,比如要统计users表中有多少个不同的姓名,那么简单粗暴的采用以上count是不行的,因为姓名有可能会重复,但是处于两条不同的数据上,如果在原生数据库中,可以使用distinct关键字,那么在SQLAlchemy中,可以通过func.count()方法来实现:
from SQLAlchemy import func
User.query.group_by(User.name).all()
# 输出的结果
> [(1, u'ed'), (1, u'fred'), (1, u'mary'), (1, u'wendy')]
另外,如果想实现select count(*) from users,可以通过以下方式来实现:
db.session.query(func.count('*')).select_from(User).scalar()
当然,如果指定了要查找的表的字段,可以省略select_from()方法:
db.session.query(func.count(User.id)).scalar()
排序:
-
order_by:可以指定根据这个表中的某个字段进行排序,如果在前面加了一个-,代表的是降序排序。
-
在模型定义的时候指定默认排序:有些时候,不想每次在查询的时候都指定排序的方式,可以在定义模型的时候就指定排序的方式。有以下两种方式:
-
relationship的order_by参数:在指定relationship的时候,传递order_by参数来指定排序的字段。
-
在模型定义中,添加以下代码:
__mapper_args__ = { "order_by": title }
即可让文章使用标题来进行排序。
-
-
正向排序和反向排序:默认情况是从小到大,从前到后排序的,如果想要反向排序,可以调用排序的字段的desc方法。
limit、offset和切片:
-
limit:可以限制每次查询的时候只查询几条数据。
Message.query.limit(2).all()
-
offset:可以限制查找数据的时候过滤掉前面多少条。limit和offset可以同时使用。
Message.query.offset(2).all()
-
切片:可以对Query对象使用切片操作,来获取想要的数据。 可以使用slice(start,stop)方法来做切片操作。也可以使用[start:stop]的方式来进行切片操作。一般在实际开发中,中括号的形式是用得比较多的。 返回的是list对象
Message.query.order_by(Message.timestamp.desc())[0:10]
懒加载:
在一对多,或者多对多的时候,如果想要获取多的这一部分的数据的时候,往往能通过一个属性就可以全部获取了。比如有一个作者,想要或者这个作者的所有文章,那么可以通过user.articles就可以获取所有的。但有时候我们不想获取所有的数据,比如只想获取这个作者今天发表的文章,那么这时候我们可以给relationship传递一个lazy='dynamic',以后通过user.articles获取到的就不是一个列表,而是一个AppendQuery对象了。这样就可以对这个对象再进行一层过滤和排序等操作。
lazy可用的选项:
- select:这个是默认选项。还是拿user.articles的例子来讲。如果你没有访问user.articles这个属性,那么SQLAlchemy就不会从数据库中查找文章。一旦你访问了这个属性,那么SQLAlchemy就会立马从数据库中查找所有的文章,并把查找出来的数据组装成一个列表返回。这也是懒加载。
- dynamic:这个就是我们刚刚讲的。就是在访问user.articles的时候返回回来的不是一个列表,而是AppenderQuery对象。
group_by:
根据某个字段进行分组。比如想要根据性别进行分组,来统计每个分组分别有多少人,那么可以使用以下代码来完成:
db.session.query(Message.name,func.count(Message.id)).group_by(Message.name).all()
having:
having是对查找结果进一步过滤。比如只想要看未成年人的数量,那么可以首先对年龄进行分组统计人数,然后再对分组进行having过滤。示例代码如下:
result = db.session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age >= 18).all()
join方法:
join查询分为两种,一种是inner join,另一种是outer join。默认的是inner join,如果指定left join或者是right join则为outer join。如果想要查询User及其对应的Address,则可以通过以下方式来实现:
for u,a in db.session.query(User,Address).filter(User.id==Address.user_id).all():
print(u)
print(a)
# 输出结果:
> <User (id=1,name='ed',fullname='Ed Jason',password='123456')>
> <Address id=4,email=ed@google.com,user_id=1>
这是通过普通方式的实现,也可以通过join的方式实现,更加简单:
for u,a in db.session.query(User,Address).join(Address).all():
print(u)
print(a)
# 输出结果:
> <User (id=1,name='ed',fullname='Ed Jason',password='123456')>
> <Address id=4,email=ed@google.com,user_id=1>
当然,如果采用outerjoin,可以获取所有user,而不用在乎这个user是否有address对象,并且outerjoin默认为左外查询:
for instance in db.session.query(User,Address).outerjoin(Address).all():
print(instance)
# 输出结果:
(<User (id=1,name='ed',fullname='Ed Jason',password='123456')>, <Address id=4,email=ed@google.com,user_id=1>)
(<User (id=2,name='xt',fullname='xiaotuo',password='123')>, None)
别名:
当多表查询的时候,有时候同一个表要用到多次,这时候用别名就可以方便的解决命名冲突的问题了:
from SQLAlchemy.orm import aliased
adalias1 = aliased(Address)
adalias2 = aliased(Address)
for username,email1,email2 in db.session.query(User.name,adalias1.email_address,adalias2.email_address).join(adalias1).join(adalias2).all():
print(username,email1,email2)
子查询:
SQLAlchemy也支持子查询,比如现在要查找一个用户的用户名以及该用户的邮箱地址数量。要满足这个需求,可以在子查询中找到所有用户的邮箱数(通过group by合并同一用户),然后再将结果放在父查询中进行使用:
from SQLAlchemy.sql import func
# 构造子查询
stmt = db.session.query(Address.user_id.label('user_id'),func.count(*).label('address_count')).group_by(Address.user_id).subquery()
# 将子查询放到父查询中
for u,count in db.session.query(User,stmt.c.address_count).outerjoin(stmt,User.id==stmt.c.user_id).order_by(User.id):
print(u,count)
从上面我们可以看到,一个查询如果想要变为子查询,则是通过subquery()方法实现,变成子查询后,通过子查询.c属性来访问查询出来的列。以上方式只能查询某个对象的具体字段,如果要查找整个实体,则需要通过aliased方法,示例如下:
stmt = db.session.query(Address)
adalias = aliased(Address,stmt)
for user,address in session.query(User,stmt).join(stmt,User.addresses):
print(user,address)
子查询可以让多个查询变成一个查询,只要查找一次数据库,性能相对来讲更加高效一点。不用写多个sql语句就可以实现一些复杂的查询。那么在SQLAlchemy中,要实现一个子查询,应该使用以下几个步骤:
- 将子查询按照传统的方式写好查询代码,然后在query对象后面执行subquery方法,将这个查询变成一个子查询。
- 在子查询中,将以后需要用到的字段通过label方法,取个别名。
- 在父查询中,如果想要使用子查询的字段,那么可以通过子查询的返回值上的c属性拿到。
在视图中查询
当编写Flask视图函数,对于不存在的条目返回一个404错误是非常方便的。因为这是一个很常见的问题,Flask-SQLAlchemy为了解决这个问题提供了一个帮助函数。可以使用get_or_404()来代替get(),使用 first_or_404()来代替 first()。这样会抛出一个404错误,而不是返回None:
@app.route('/user/<username>')
def show_user(username):
user = User.query.filter_by(username=username).first_or_404()
return render_template('show_user.html', user=user)
更新数据
更新一条记录非常简单,先按照条件查询出要更新的数据,然后直接赋值给模型类的字段属性就可以改变字段值,然后调用commit()方法提交会话即可。
note = Note.query.get(2)
note.body = '下周公司团建!'
db.session.commit()
删除数据
删除记录和添加记录很相似, 不过要把add()方法换成delete()方法,最后都需要调用commit()方法提交修改。删除数据和更新数据一样都需要先按照条件查询出要操作的数据(要删除的数据)。
note = Note.query.get(2)
db.session.delete(note)
db.session.commit()
定义关系
外键
外键可以让表之间的关系更加紧密。而SQLAlchemy同样也支持外键。通过ForeignKey类来实现,并且可以指定表的外键约束。 使用Flask-SQLAlchemy创建外键非常简单。在从表中增加一个字段,指定这个字段外键的是哪个表的哪个字段就可以了。从表中外键的字段,必须和父表的主键字段类型保持一致。
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
username = db.Column(db.String(50),nullable=False)
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
title = db.Column(db.String(50),nullable=False)
content = db.Column(db.Text,nullable=False)
uid = db.Column(db.Integer,db.ForeignKey("user.id"))
外键约束有以下几项:
- RESTRICT:父表数据被删除,会阻止删除。默认就是这一项。
- NO ACTION:在MySQL中,同RESTRICT。
- CASCADE:级联删除。
- SET NULL:父表数据被删除,子表数据会设置为NULL
表之间的关系存在三种:一对一、一对多、多对多。而SQLAlchemy中的ORM也可以模拟这三种关系 。
一对多
我们将以作者和文章来演示一对多关系:一个作者可以写作多篇文章。
Author 类用来表示作者, Article类用来表示文章,示例代码所下:
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True)
name= db.Column(db.String(70), unique=True)
phone = db.Column(db.String(20))
class Article(db.Model):
id= db.Column(db.Integer, primary_key=True)
title= db.Column(db.String(50), index=True)
body= db.Column(db.Text)
-
定义外键
定义关系的第一步是创建外键。外键是( foreign key )用来在A表存储B表的主键值以便和B表建立联系的关系字段。因为外键只能存储单一数据(标量)所以外键总是在“多”这一侧定义,多篇文章属于同一个作者,所以我们需要为每篇文章添加外键存储作者的主键值以指向对应的作者。在Article 模型中,我们定义一个author_id 字段作为外键:
class Article(db.Model): id= db.Column(db.Integer, primary_key=True) title= db.Column(db.String(50), index=True) body= db.Column(db.Text) # 定义外键 author_id = db.Column(db.Integer, db.ForeignKey('author.id')
这个字段使用db.ForeignKey类定义为外键,传入关系另一侧的表名和主键字段名,即author. id。实际的效果是将article 表的author_id 的值限制为author表的id列的值。它将用来存储author表中记录的主键值。
外键字段的命名没有限制,因为要连接的目标概是author表的id列,所以为了便于区分而将这个外键字段的名称命名为author_id 。
传入ForeignKey 类的参数author.id ,其中author指的是Author模型对应的表名称,而id指的是字段名,即“表 名.字段名” 。模型类对应的表名由Flask-SQLAlchemy 生成,默认为类名称的小写形式,多个单词通过下划线分 隔,你也可以显式地通过__tablename__属性自己指定。
-
定义关系属性
定义关系的第二步是使用关系函数定义关系属性。关系属性在关系的出发侧定义,即一对多关系的“ 一”这一侧。一个作者拥有多篇文章,在Author模型中,我们定义了一个articles属性来表示对应的多篇文章:
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True)
name= db.Column(db.String(70), unique=True)
phone = db.Column(db.String(20))
# 定义关系
articles = db.relationship('Article')
关系属性的名称没有限制,你可以自由修改。它相当于一个快捷查询,不会作为字段写入数据库中。
这个属性并没有使用Column类声明为列,而是使用了db.relationship()关系函数定义为关系属性,因为这个关系属性返回多个记录,我们称之为集合关系属性。relationship()函数的第一个参数为关系另一侧的模型名称,它会告诉SQLAlchemy将Author类与Article类建立关系。当这个关系属性被调用时,SQLAlchemy会找 到关系另一侧(即article 表)的外键字段(即author_id), 然后反向查询article表中所有author_id值为当前表主键值(即author.id)的记录,返回包含这些记录的列表,也就是返回某个作者对应的多篇文章记 录。
下面在Python Shell中演示如何对实际的对象建立关系。先创建一个作者记录和两个文章记录,并添加到数据 库会话中:
>> foo = Author(name='Foo')
>> spam = Article(title='Spam')
>> ham = Article(title='Ham')
>> db.session.add(foo)
>> db.session.add(spam)
>> db.session.add(ham)
-
建立关系
建立关系有两种方式, 第一种方式是为外键字段赋值, 比如:>> spam.author_id = 1 >> db.session.commit()
我们将spam对象的author_id字段的值设为1,这会和id值为1的Author对象建立关系。提交数据库改动后,如果我们对i d 为1的foo对象调用articles关系属性,会看到spam对象包括在返回的Article对象列表中:
>> foo.articles [<Article u' Spam'>, <Article u'Ham'>]
另一种方式是通过操作关系属性,将关系属性赋给实际的对象即可建立关系。集合关系属性可以像列表一样操作,调用append()方法来与一个Article对象建立关系:
>> foo.articles.append(spam) >> foo.articles.append(ham) >> db.session.commit()
我也可以直接将关系属性赋值给一个包含Article对象的列表。
和前面的第一种方式类似,为了让改动生效,我们需要调用db. session.commit()方法提交数据库会话。建立关系后,存储外键的autbor_id字段会自动获得正确的值, 而调用Author实例的关系属性articles时,会获得所有建立关系的Article对象:
>> spam.author_id 1 >> foo.articles [<Article u'Spam'> , <Article u'Ham'>]
和主键类似,外键字段由SQLAlchemy管理, 我们不需要手动设直。当通过关系属性建立关系后, 外键字段会自动获得正确的值。
和append()相对,对关系属性调用remove()方法可以与对应的Aritcle对象解除关系:
>> foo.articles.remove(spam) >> db.session.commit() >> foo.articles [<Article u'Ha'>]
你也可以使用pop()方法操作关系属性,它会与关系属性对应的列表的最后一个Aritcle对象解除关系并返回该对象。
不要忘记在操作结束后需要调用commit()方法提交数据库会话,这样才可以把改动写入数据库。
在上面我们提到过,使用关系函数定义的属性不是数据库字段,而是类似于特定的查询函数。当某个Aritcle对象被删除时,在对应Author对象的aritcles属性调用时返回的列表也不会包含该对象。
在关系函数中,有很多参数可以用来设置调用关系属性进行查询时的具体行为。
dynamic选项仅用于集合关系属性,不可用于多对一、一对一或是在关系函数中将uselist参数设为False的情况。
许多教程和示例使用dynamic来动态加载所有集合关系属性对应的记录,这是应该避免的行为。使用dynamic加载方式意味着每次操作关系都会执行一次SQL查询,这会造成潜在的性能问题。大多数情况下我们只需妥使用默认值(select),只有在调用关系属性会返回大量记录,并且总是需要对关系属性返回的结果附加额外的查询时才需要使用动态加载(lazy='dynamic’) 。
-
建立双向关系
我们在Author类中定义了集合关系属性articles,用来获取某个作者拥有的多篇文章记录。在某些情况下,你也许希望能在Arti cle类中定义一个类似的author关系属性,当被调用时返回对应的作者记录,这类返回单个值的关系属性被称为标量关系属性。而这种两侧都添加关系属性获取对方记录的关系我们称之为双向关系(bidirectional relationship) 。双向关系并不是必须的,但在某些情况下会非常方便。双向关系的建立很简单,通过在关系的另一侧也创建一个relationship()函数,我们就可以在两个表之间建立双向关系。我们使用作家(Writer)和书(Book)的一对多关系来进行演示,建立双向关系后的Writer和Book类,示例代码如下:class Writer(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=True) books = db.relationship('Book', back_populates='writer') class Book(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(50), index=True) writer_id = db.Column(db.Integer, db.ForeignKey('writer.id') writer = db.relationship('Writer', back_populates='books')
在“多”这一侧的Book(书)类中,我们新创建了一个writer关系属性,这是一个标量关系属性,调用它会获取对应的Writer(作者)记录;而在Writer(作者)类中的books属性则用来获取对应的多个Book (书)记录。在关系函数中,我们使用back_populates参数来连接对方,back _populates参数的值需要设为关系另一侧的关系属性名。
为了方便演示,我们先创建l1个Wri ter和2个Book记录,井添加到数据库中:
>> king = Writer(name='Stephen king') >> carrie = Book(name='Carrie') >> it = Book(name='IT') >> db.session.add(king) >> db.session.add(carrie) >> db.session.add(it) >> db.session.commit()
设置双向关系后,除了通过集合属性books来操作关系,我们也可以使用标量属性writer来进行关系操作。比如,将一个Writer对象赋值给某个Book对象的writer属性,就会和这个Book对象建立关系:
>> carrie.writer = king >> carrie.writer <Writer u'Stephen King'> >> king.books [<Book u'Carrie'>] >> it.writer = writer >> king.books [<Book u'Carrie'>, <Book u'IT'>]
相对的,将某个Book的writer 属性设为None, 就会解除与对应Writer对象的关系:
>> carrie.writer = None >> king.books [<Book u'IT'>] >> db.session.commit()
需要注意的是,我们只需要在关系的一侧操作关系。当为Book对象的writer属性赋值后,对应Writer对象的books属性的返回值也会自动包含这个Book对象。反之,当某个Writer对象被删除时,对应的Book对象的writer属性被调用时的返回值也会被置为空( 即NULL , 会返回None)。
-
使用backref简化关系定义
在介绍关系函数的参数时,我们曾提到过,使用关系函数中的backref参数可以简化双向关系的定义。以一对多关系为例, backref参数用来自动为关系另一侧添加关系属性,作为反向引用(back reference) ,赋予的值会作为关系另一侧的关系属性名称。比如,我们在Author一侧的关系函数中将backref 参数设为author , SQLAlchemy会自动为Article类添加一个author属性。为了避免和前面的示例命名冲突,我们使用歌手(Singer)和歌曲(Song)的一对多关系作为演示,分别创建Singer和Song类,示例代码如下:class Singer(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=True) songs = db.relationship('Song', backref='singer') class Song(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), index=True) singer_id = db.Column(db.Integer, db.ForeignKey('singer.id')
在定义集合属性songs的关系函数中,我们将backref参数设为singer,这会同时在Song类中添加了一个singe r标量属性。这时我们仅需要定义一个关系函数,虽然singer是一个“看不见的关系属性” ,但在使用上和定义两个关系函数并使用back_populates参数的效果完全相同。
需要注意的是, 使用backref允许我们仅在关系一侧定义另一侧的关系属性, 但是在某些情况下,我们希望可以对在关系另一侧的关系属性进行设置,这时就需要使用backref()函数。backref()函数接收第一个参数作为在关系另一侧添加的关系属性名,其他关键字参数会作为关系另一侧关系函数的参数传入。比如, 我们要在关系另一侧“看不见的relationship()函数”中将uselist 参数设为False,可以这样实现:
class Singer(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=True) songs = db.relationship('Song', backref='singer') songs = db.relationship('Song', backref=backref('singer', uselist=False))
尽管使用backref非常方便, 但通常来说“显式好过隐式” ,所以我们应该尽量使用back_populates定义双向关系。
多对一
一对多关系反过来就是多对一关系,这两种关系模式分别从不同的视角出发。我们使用居民和城市来演示多对一关系: 多个居民居住在同一个城市。
在示例程序中, Citizen类表示居民, City类表示城市。建立多对一关系后,我们将在Citizen类中创建一个标量关系属性city,调用它可以获取单个City对象。
我们在前面介绍过,关系属性在关系模式的出发侧定义。当出发点在“多”这一侧时, 我们希望在Citizen类中添加一个关系属性city来获取对应的城市对象, 因为这个关系属性返回单个值,我们称之为标量关系属性。在定义关系时,外键总是在“多” 这一侧定义,所以在多对一关系中外键和关系属性都定义在“多”这一侧,即City类中,示例代码如下:
class Citizen(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
city_id = db.Column(db.Integer, db.ForeignKey('city.id')
city= db.relationship ('City')
class City(db.Model):
id= db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True)
这时定义的city关系属性是一个标量属性(返回单一数据) 。当Citizen.city被调用时,SQLAlchemy会根据外键字段city_id 存储的值查找对应的City对象并返回,即居民记录对应的城市记录。
当建立双向关系时,如果不使用backref,那么一对多和多对一关系模式在定义上完全相同,这时可以将一对多和多对一视为同一种关系模式。
一对一
我们将使用国家和首都来演示一对一关系: 每个国家只有一个首都;反过来说, 一个城市也只能作为一个国家的首都。
在示例程序中, Country类表示国家, Capital类表示首都。建立一对一关系后,我们将在Country类中创建一个标量关系属性capital , 调用它会获取单个Capital对象;我们还将在Capital类中创建一个标量关系属性country,调用它会获取单个的Country对象。
一对一关系实际上是通过建立双向关系的一对多关系的基础上转化而来。我们要确保关系两侧的关系属性都是标量属性,都只返回单个值,所以要在定义集合属性的关系函数中将uselist参数设为False ,这时一对多关系将被转换为一对一关系。示例代码如下:
class Country(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True)
capital = db.relationship('Capital', uselist=False)
class Capital(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True )
country_id = db.Column(db.Integer, db.ForeignKey('country.id')
country = db.relationshiP('Country')
“多”这一侧本身就是标量关系属性,不用做任何改动。而“一”这一侧的集合关系属性,通过将uselist 设为False后,将仅返回对应的单个记录,而且无法再使用列表语义操作:
china = Country(name = 'china')
beijing = Capital(name= 'Beijing' )
db.session.add(china)
db.session.add(beijing)
db.session.commit()
china.capital = beijing
>> china.capital
<Capital 1>
>> beijing.country
u 'China'
>> tokyo = Capital(name'Tokyo')
>> china.capital.append(tokyo)
Traceback (most recent call last) :
File "<console>", line 1 , in <module>
AttributeError :'Capital' object has no attribute 'append'
多对多
我们将使用学生和老师来演示多对多关系:每个学生有多个老师,而每个老师有多个学生。
在示例程序中, Student类表示学生, Teacher类表示老师。在这两个模型之间建立多对多关系后, 我们需要在Student类中添加一个集合关系属性teachers ,调用它可以获取某个学生的多个老师,而不同的学生可以和同一个老师建立关系。
在一对多关系中,我们可以在“多”这一侧添加外键指向“一”这一侧,外键只能存储一个记录,但是在多对多关系中,每一个记录都可以与关系另一侧的多个记录建立关系,关系两侧的模型都需要存储一组外键。在SQLAlchemy中, 要想表示多对多关系, 除了关系两侧的模型外,我们还需要创建一个关联表( association tab le ) 。关联表不存储数据,只用来存储关系两侧模型的外键对应关系,示例代码如下:
association_table = db.Table('association', db.Column('student_id', db.Integer,
db.Foreignkey('student.id')), db.Column('teacher_id', db.Integer,
db.ForeignKey('teacher.id'))
)
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
grade = db.Column(db.String(20))
teachers = db.relationship('Teacher',
secondary=association_table,
back_populates='students')
class Teacher(db.Model) :
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70) , unique=True)
office = db.Column(db.String(20))
关联表使用db.Table类定义,传人的第一个参数是关联表的名称。我们在关联表中定义了两个外键字段:teacher_id 字段存储Teacher类的主键, student_id 存储Student类的主键。借助关联表这个中间人存储的外键对,我们可以把多对多关系分化成两个一对多关系。
当我们需要查询某个学生记录的多个老师时,我们先通过学生和关联表的一对多关系查找所有包含该学生的关联表记录,然后就可以从这些记录中再进一步获取每个关联表记录包含的老师记录。以图5-8中的随机数据为例,假设学生记录的id为1,那么通过查找关联表中student_id 字段为1的记录,就可以获取到对应的teacher_id值(分别为3和4),通过外键值就可以在teacher表里获取id为3和4的记录,最终,我们就获取到id为1的学生记录相关联的所有老师记录。
我们在Student类中定义一个teachers关系属性用来获取老师集合。在多对多关系中定义关系函数,除了第一个参数是关系另一侧的模型名称外,我们还需要添加一个secondary参数,把这个值设为关联表的名称。
为了便于实现真正的多对多关系,我们需要建立双向关系。建立双向关系后,多对多关系会变得更加直观。在Student类上的teachers集合属性会返回所有关联的老师记录,而在Teacher类上的students集合属性会返回所有相关的学生记录:
association_table = db.Table('association', db.Column('student_id', db.Integer,
db.Foreignkey('student.id')), db.Column('teacher_id', db.Integer,
db.ForeignKey('teacher.id'))
)
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
grade = db.Column(db.String(20))
teachers = db.relationship('Teacher',
secondary=association_table,
back_populates='students')
class Teacher(db.Model) :
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70) , unique=True)
office = db.Column(db.String(20))
students = db.relationship('Student',
secondary=association_table,
back_populates='teachers')
除了在声明关系时有所不同,多对多关系模式在操作关系时和其他关系模式基本相同。调用关系属性student.teachers时, SQLAlchemy会直接返回关系另一侧的Teacher对象,而不是关联表记录,反之亦同。和其他关系模式中的集合关系属性一样,我们可以将关系属性teachers和students像列表一样操作。比如,当你需要为某一个学生添加老师时,对关系属性使用append()方法即可。如果你想要解除关系,那么可以使用remove()方法。
同样的,在多对多关系中我们也只需要在关系的一侧操作关系。当为学生A的teachers添加了老师B后,调用老师B 的students属性时返回的学生记录也会包含学生A,反之亦同。
关联表由SQLAlchemy接管,它会帮我们管理这个表:我们只需像往常一样通过操作关系属性来建立或解除关系, SQLAlchemy会自动在关联表中创建或删除对应的关联表记录,而不用手动操作关联表。
绑定多个数据库
从0.12开始,Flask-SQLAlchemy可以容易地连接到多个数据库。为了实现这个功能,预配置了SQLAlchemy来支持多个 “binds”。
什么是绑定(binds)?
在 SQLAlchemy 中一个绑定(bind)是能执行 SQL 语句并且通常是一个连接或者引擎类的东东。在 Flask-SQLAlchemy中,绑定(bind)总是背后自动为您创建好的引擎。这些引擎中的每个之后都会关联一个短键(bind key)。这个键会在模型声明时使用来把一个模型关联到一个特定引擎。
如果模型没有关联一个特定的引擎的话,就会使用默认的连接(SQLAlchemy_DATABASE_URI配置值)。
示例配置
下面的配置声明了三个数据库连接。特殊的默认值和另外两个分别名为 users(用于用户)和 appmeta连接到一个提供只读访问应用内部数据的sqlite数据库):
SQLAlchemy_DATABASE_URI = 'postgres://localhost/main'
SQLAlchemy_BINDS = {
'users': 'mysqldb://localhost/users',
'appmeta': 'sqlite:////path/to/appmeta.db'
}
创建和删除表
默认情况下,create_all()和drop_all()方法对所有声明的绑定(包括默认绑定)进行操作。可以通过提供bind参数来自定义此行为。 它可以是单个绑定(bind)名, '__all__'
指向所有绑定(binds)或一个绑定(bind)名的列表。默认的绑定(bind)(SQLAlchemy_DATABASE_URI
) 名为 None
:
db.create_all()
db.create_all(bind=['users'])
db.create_all(bind='appmeta')
db.drop_all(bind=None)
引用绑定(Binds)
当您声明模型时,您可以用 __bind_key__ 属性指定绑定(bind):
class User(db.Model):
__bind_key__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
bind key 存储在表中的 info字典中作为 'bind_key' 键值。了解这个很重要,因为当想要直接创建一个表对象时,需要把它放在那:
user_favorites = db.Table('user_favorites',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('message_id', db.Integer, db.ForeignKey('message.id')),
info={'bind_key': 'users'}
)
如果在模型上指定了 __bind_key__,可以用它们准确地做您想要的。模型会自行连接到指定的数据库连接。
信号支持
Flask-SQLAlchemy可以订阅如下这些信号以便在更新提交到数据库之前以及之后得到通知。
如果配置中SQLAlchemy_TRACK_MODIFICATIONS启用的话,这些更新变化才能被追踪。
存在以下两个信号:
-
models_committed
这个信号在修改的模型提交到数据库时发出。发送者是发送修改的应用,模型和操作描述符以 (model, operation) 形式作为元组,这样的元组列表传递给接受者的changes参数。该模型是发送到数据库的模型实例,当一个模型已经插入,操作是'insert',而已删除是 'delete',如果更新了任何列,会是'update' 。
-
before_models_committed
工作机制和models_committed完全一样,但是在提交之前发送。
定制
Flask-SQLAlchemy定义合理的默认值。但是,有时需要自定义。有多种方法可以自定义模型的定义方式和交互方式。
这些定制应用于SQLAlchemy对象的创建,并扩展到从其Model类派生的所有模型。
模型类
SQLAlchemy模型都从声明性基类继承。正如db.Model在Flask-SQLAlchemy中所公开的那样,所有模型都对其进行了扩展。可以通过将默认子类化并将自定义类传递给来进行自定义 model_class。
下面的示例为每个模型提供一个整数主键,或一个用于联接表继承的外键。
注意:
一切的整数主键不一定是最佳的数据库设计(取决于项目要求),这只是一个例子。
from flask_SQLAlchemy import Model, SQLAlchemy
import SQLAlchemy as sa
from SQLAlchemy.ext.declarative import declared_attr, has_inherited_table
class IdModel(Model):
@declared_attr
def id(cls):
for base in cls.__mro__[1:-1]:
if getattr(base, '__table__', None) is not None:
type = sa.ForeignKey(base.id)
break
else:
type = sa.Integer
return sa.Column(type, primary_key=True)
db = SQLAlchemy(model_class=IdModel)
class User(db.Model):
name = db.Column(db.String)python
class Employee(User):
title = db.Column(db.String)
混合模型
如果仅在某些模型而不是所有模型上需要行为,则使用mixin类仅自定义那些模型。例如,如果某些模型应跟踪它们的创建或更新时间:
from datetime import datetime
class TimestampMixin(object):
created = db.Column(
db.DateTime, nullable=False, default=datetime.utcnow)
updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
class Author(db.Model):
...
class Post(TimestampMixin, db.Model):
...python
查询类
也可以自定义可用于query
模型特殊属性的内容。例如,提供一种 get_or方法:
from flask_SQLAlchemy import BaseQuery, SQLAlchemy
class GetOrQuery(BaseQuery):
def get_or(self, ident, default=None):
return self.get(ident) or default
db = SQLAlchemy(query_class=GetOrQuery)
# 通过id获取用户,或返回匿名用户实例
user = User.query.get_or(user_id, anonymous_user)
现在,从Flask-SQLAlchemy模型的特殊查询属性执行的所有查询都可以将get_or方法用作其查询的一部分。使用db.relationship定义的所有关系(但不提供SQLAlchemy.orm.relationship())也将提供此功能。
通过query_class在定义中提供关键字,还可以为各个关系定义自定义查询类。这适用于db.relationship和SQLAlchemy.relationship:
class MyModel(db.Model):
cousin = db.relationship('OtherModel', query_class=GetOrQuery)
注意:
如果在关系上定义了查询类,则它将优先于附加到其相应模型的查询类。
通过覆盖模型的query_class 类属性,还可以为各个模型定义特定的查询类:
class MyModel(db.Model):
query_class = GetOrQuery
在这种情况下,该get_or方法仅适用于从开始的查询MyModel.query。
模型元类
警告:
元类是一个高级主题,您可能不需要自定义它们即可实现所需的功能。这里主要记录该文档,以显示如何禁用表名生成。
在定义模型子类时,模型元类负责设置SQLAlchemy内部。Flask-SQLAlchemy通过mixins添加了一些额外的行为。它的默认元类DefaultMeta继承了它们。
- BindMetaMixin:__bind_key__从类中提取并应用于表。
- NameMetaMixin:如果模型未指定__tablename__而是指定了主键,则会自动生成一个名称。
您可以通过定义自己的元类并自己创建声明性基础来添加自己的行为。确保仍然从您想要的mixin继承(或仅从默认元类继承)。
如上所示,传递一个声明性基类而不是简单的模型基类,base_class将导致Flask-SQLAlchemy使用该基类,而不是使用默认的元类构造一个基类。
from flask_SQLAlchemy import SQLAlchemy
from flask_SQLAlchemy.model import DefaultMeta, Model
class CustomMeta(DefaultMeta):
def __init__(cls, name, bases, d):
# 自定义类设置可以在这里
# 一定要回调父类
super(CustomMeta, cls).__init__(name, bases, d)
# custom class-only methods could go here
db = SQLAlchemy(model_class=declarative_base(cls=Model, metaclass=CustomMeta, name='Model'))
您还可以declarative_base()根据需要传递想要自定义基类的任何其他参数 。
禁用表名称生成
一些项目倾向于手动设置每个模型的__tablename__,而不是依赖于Flask-SQLAlchemy的检测和生成。可以通过定义自定义元类来禁用表名的生成。
from flask_SQLAlchemy.model import BindMetaMixin, Model
from SQLAlchemy.ext.declarative import DeclarativeMeta, declarative_base
class NoNameMeta(BindMetaMixin, DeclarativeMeta):
pass
db = SQLAlchemy(model_class=declarative_base(
cls=Model, metaclass=NoNameMeta, name='Model'))
这将创建仍支持__bind_key__功能但不生成表名称的库。
网友评论