赵走x博客
网站访问量:151511
首页
书籍
软件
工具
古诗词
搜索
登录
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 实战:38、数据库进阶实践
资源编号:75889
Python Web
Flask Web开发实战:入门、进阶与原理解析
热度:98
本节将介绍一些使用SQLAlchemy的进阶技巧,用于简化操作数据库的过程。
本节将介绍一些使用SQLAlchemy的进阶技巧,用于简化操作数据库的过程。 # 1、级联操作 Cascade意为“级联操作”,就是在操作一个对象的同时,对相关的对象也执行某些操作。我们通过一个Post模型和Comment模型来演示级联操作,分别表示文章(帖子)和评论,两者为一对多关系: ``` class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(50), unique=True) body = db.Column(db.Text) comments = db.relationship('Comment', back_populates='post') class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) post_id = db.Column(db.Integer, db.ForeignKey('post.id')) post = db.relationship('Post', back_populates='comments') ``` 级联行为通过关系函数relationship()的cascade参数设置。我们希望在操作Post对象时,处于附属地位的Comment对象也被相应执行某些操作,这时应该在Post类的关系函数中定义级联参数。设置了cascade参数的一侧将被视为父对象,相关的对象则被视为子对象。 cascade通常使用多个组合值,级联值之间使用逗号分隔,比如: ``` class Post(db.Model): ... comments = relationship('Comment', cascade='save-update, merge, delete') ``` 常用的配置组合如下所示: * save-update、merge(默认值) * save-update、merge、delete * all * all、delete-orphan 当没有设置cascade参数时,会使用默认值save-update、merge。上面的all等同于除了delete-orphan以外所有可用值的组合,即save-update、merge、refresh-expire、expunge、delete。下面我们会介绍常用的几个级联值: ### 1.save-update save-update是默认的级联行为,当cascade参数设为save-update时,如果使用db.session.add()方法将Post对象添加到数据库会话时,那么与Post相关联的Comment对象也将被添加到数据库会话。我们首先创建一个Post对象和两个Comment对象: ``` >>> post1 = Post() >>> comment1 =Comment() >>> comment2 =Comment() ``` 将post1添加到数据库会话后,只有post1在数据库会话中: ``` >>> db.session.add(post1) >>> post1 in db.session True >>> comment1 in db.session False >>> comment2 in db.session False ``` 如果我们让post1与这两个Comment对象建立关系,那么这两个Comment对象也会自动被添加到数据库会话中: ``` >>> post1.comments.append(comment1) >>> post1.comments.append(comment2) >>> comment1 in db.session True >>> comment2 in db.session True ``` 当调用db.session.commit()提交数据库会话时,这三个对象都会被提交到数据库中。 ### 2.delete 如果某个Post对象被删除,那么按照默认的行为,该Post对象相关联的所有Comment对象都将与这个Post对象取消关联,外键字段的值会被清空。如果Post类的关系函数中cascade参数设为delete时,这些相关的Comment会在关联的Post对象删除时被一并删除。当需要设置delete级联时,我们会将级联值设为all或save-update、merge、delete,比如: ``` class Post(db.Model): ... comments = relationship('Comment', cascade='all') ``` 我们先创建一个文章对象post2和两个评论对象comment3和comment4,并将这两个评论对象与文章对象建立关系,将它们添加到数据库会话并提交: ``` >>> post2 = Post() >>> comment3 = Comment() >>> comment4 = Comment() >>> post2.comments.append(comment3) >>> post2.comments.append(comment4) >>> db.session.add(post2) >>> db.session.commit() ``` 现在共有两条Post记录和四条Comment记录: ``` >>> Post.query.all() [
,
] >>> Comment.query.all() [
,
,
,
] ``` 如果删除文章对象post2,那么对应的两个评论对象也会一并被删除: ``` >>> post2 = Post.quer2y.get(2) >>> db.session.delete(post2) >>> db.session.commit() >>> Post.query.all() [
] >>> Comment.query.all() [
,
] ``` ### 3.delete-orphan 这个模式是基于delete级联的,必须和delete级联一起使用,通常会设为all、delete-orphan,因为all包含delete。因此当cascade参数设为delete-orphan时,它首先包含delete级联的行为:当某个Post对象被删除时,所有相关的Comment对象都将被删除(delete级联)。除此之外,当某个Post对象(父对象)与某个Comment对象(子对象)解除关系时,也会删除该Comment对象,这个解除关系的对象被称为孤立对象(orphan object)。现在comments属性中的级联值为all、delete-orphan,如下所示: ``` class Post(db.Model): ... comments = relationship('Comment', cascade='all, delete-orphan') ``` 我们先创建一个文章对象post3和两个评论对象comment5和comment6,并将这两个评论对象与文章对象建立关系,将它们添加到数据库会话并提交: ``` >>> post3 = Post() >>> comment5 = Comment() >>> comment6 = Comment() >>> post3.comments.append(comment5) >>> post3.comments.append(comment6) >>> db.session.add(post3) >>> db.session.commit() ``` 现在数据库中共有两条文章记录和四条评论记录: ``` >>> Post.query.all() [
,
] >>> Comment.query.all() [
,
,
,
] ``` 下面我们将comment5和comment6与post3解除关系并提交数据库会话: ``` >>> post3.comments.remove(comment5) >>> post3.comments.remove(comment6) >>> db.session.commit() ``` 默认情况下,相关评论对象的外键会被设为空值。因为我们设置了delete-orphan级联,所以现在你会发现解除关系的两条评论记录都被删除了: ``` >>> Comment.query.all() [
,
] ``` delete和delete-orphan通常会在一对多关系模式中,而且“多”这一侧的对象附属于“一”这一侧的对象时使用。尤其是如果“一”这一侧的“父”对象不存在了,那么“多”这一侧的“子”对象不再有意义的情况。比如,文章和评论的关系就是一个典型的示例。当文章被删除了,那么评论也就没必要再留存。在这种情况下,如果不使用级联操作,那么我们就需要手动迭代关系另一侧的所有评论对象,然后一一进行删除操作。 >提示 对于这两个级联选项,如果你不会通过列表语义对集合关系属性调用remove()方法等方式来操作关系,那么使用delete级联即可。 虽然级联操作方便,但是容易带来安全隐患,因此要谨慎使用。默认值能够满足大部分情况,所以最好仅在需要的时候才修改它。 在SQLAlchemy中,级联的行为和配置选项等最初衍生自另一个ORM——Hibernate ORM。如果你对这部分内容感到困惑,那么我将在这里引用SQLAlchemy文档中关于Hibernate文档的结论:“The sections we have just covered can be a bit confusing.However,in practice,it all works out nicely.(我们刚刚介绍的这部分内容可能会有一些让人困惑,不过,在实际使用中,它们都会工作得很顺利。)” 你可以访问SQLAlchemy文档相关部分([http://docs.sqlalchemy.org/en/latest/orm/cascades.html](http://docs.sqlalchemy.org/en/latest/orm/cascades.html) 查看所有可用的级联值及具体细节。 # 2、事件监听 在Flask中,我们可以使用Flask提供的多个装饰器注册请求回调函数,它们会在特定的请求处理环节被执行。类似的,SQLAlchemy也提供了一个listen_for()装饰器,它可以用来注册事件回调函数。 listen_for()装饰器主要接收两个参数,target表示监听的对象,这个对象可以是模型类、类实例或类属性等。identifier参数表示被监听事件的标识符,比如,用于监听属性的事件标识符有set、append、remove、init_scalar、init_collection等。 为了演示事件监听,我们创建了一个Draft模型类表示草稿,其中包含body字段和edit_time字段,分别存储草稿正文和被修改的次数,其中edit_time字段的默认值为0,如下所示: ``` class Draft(db.Model): id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) edit_time = db.Column(db.Integer, default=0) ``` 通过注册事件监听函数,我们可以实现在body列修改时,自动叠加表示被修改次数的edit_time字段。在SQLAlchemy中,每个事件都会有一个对应的事件方法,不同的事件方法支持不同的参数。被注册的监听函数需要接收对应事件方法的所有参数,所以具体的监听函数用法因使用的事件而异。设置某个字段值将触发set事件,代码清单是我们为set事件编写的事件监听函数。 ``` @db.event.listens_for(Draft.body, 'set') def increment_edit_time(target, value, oldvalue, initiator): if target.edit_time is not None: target.edit_time += 1 ``` 我们在listen_for()装饰器中分别传入Draft.body和set作为targe和identifier参数的值。监听函数接收所有set()事件方法接收的参数,其中的target参数表示触发事件的模型类实例,使用target.edit_time即可获取我们需要叠加的字段。其他的参数也需要照常写出,虽然这里没有用到。value表示被设置的值,oldvalue表示被取代的旧值。 当set事件发生在目标对象Draft.body上时,这个监听函数就会被执行,从而自动叠加Draft.edit_time列的值,如下所示: ``` >>> draft = Draft(body='init') >>> db.session.add(draft) >>> db.session.commit() >>> draft.edit_time 0 >>> draft.body = 'edited' >>> draft.edit_time 1 >>> draft.body = 'edited again' >>> draft.edit_time 2 >>> draft.body = 'edited again again' >>> draft.edit_time 3 >>> db.session.commit() ``` 除了这种传统的参数接收方式,即接收所有事件方法接收的参数,还有一种更简单的方法。通过在listen_for()装饰器中将关键字参数name设为True,可以在监听函数中接收**kwargs作为参数(可变长关键字参数),即“named argument”。然后在函数中可以使用参数名作为键来从**kwargs字典获取对应的参数值: ``` @db.event.listens_for(Draft.body, 'set', named=True) def increment_edit_time(**kwargs): if kwargs['target'].edit_time is not None: kwargs['target'].edit_time += 1 ``` SQLAlchemy作为SQL工具集本身包含两大主要组件:SQLAlchemy ORM和SQLAlchemy Core。前者实现了我们前面介绍的ORM功能,后者实现了数据库接口等核心功能,这两类组件都提供了大量的监听事件,几乎覆盖整个SQLAlchemy使用的生命周期。请访问下面的链接查看可用的事件列表以及具体的事件方法使用介绍: ·SQLAlchemy Core事件:[http://docs.sqlalchemy.org/en/latest/core/events.html](http://docs.sqlalchemy.org/en/latest/core/events.html) 。 ·SQLAlchemy ORM事件:[http://docs.sqlalchemy.org/en/latest/orm/events.html](http://docs.sqlalchemy.org/en/latest/orm/events.html) 。  提示 除了使用listen_for装饰器,我们还可以直接使用它内部调用的listen()函数注册事件监听函数时,第三个参数传入被注册的函数对象,比如db.event.listen(SomeClass,'load',my_load_listener)。