在关系型数据库中,我们可以通过关系让不同表之间的字段建立联系。一般来说,定义关系需要两步,分别是创建外键和定义关系属性。在更复杂的多对多关系中,我们还需要定义关联表来管理关系。下面我们学习用SQLAlchemy在模型之间建立几种基础的关系模式。
在上面的操作中,每一次使用flask shell命令启动python shell后都要从app模块里导入db对象和相应的模型类。为什么不能把他们自动集成到python shell上下文里呢?就像flask内置的app对象一样。这当然可以实现,我们可以使用app.shell_context_processor装饰器注册一个shell上下文处理函数。它和模板上下文处理函数一样,也需要返回包含变量和变量值字典。
app.py: 注册shell上下文处理函数
@app.shell_context_processor def make_shell_context(): return dict(db=db, Note=Note) # 等同于{‘db‘: db, ‘Note‘: Note}
当你使用flask shell命令启动python shell时,所有使用app.shell_context_processor装饰器注册的shell上下文处理函数都会被自动执行,这会将db和Note对象推送到python shell上下文里:
>>> db <SQLAlchemy engine=sqlite:///D:\flask\FLASK_PRACTICE\DataBase\data.db> >>> Note <class ‘app.Note‘>
在下面演示各种数据库关系时,将编写更多的模型类。在示例程序中,都使用shell上下文处理函数添加到shell上下文中,因此你可以直接在python shell使用,不用手动导入。
我们将以作者和文章来演示一对多关系:一个作者可以写多篇文章。一对多关系如下图:
在示例程序中,Author类用来表示作者,Article类用来表示文章。
app.py: 一对多关系示例
class Author(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=True) 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类中添加一个关系属性articles,作为集合(collection)属性,当我们对特定的Author对象调用articles属性会返回所有的Article对象。下面来介绍如何一步步定义这个一对多关系。
定义关系的第一步是创建外键。外键是(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(‘authod.id‘)) #指定显示内容,否则默认显示<表名 主键id> def __repr__(self): return ‘<Article %r>‘ % self.title
这个字段使用db.ForeignKey类定义为外键,传入关系另一侧的表名和主键字段名,即author.id。实际的效果是将article表的authod_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) articles = db.relationShip(‘Article‘) #指定显示内容 def __repr__(self): return ‘<Author %r>‘ %self.name
关系属性的名称没有限制,你可以自由修改。它相当于一个快捷查询,不会作为字段写入数据库中。
这个属性并没有使用column类声明为列,而是使用了db.relationship()关系函数定义为关系属性,因为这个关系属性返回多个记录,我们称之为集合关系属性。relationship()函数的第一个参数为关系另一侧的模型名称,它会告诉SQLAlchemy将Author类与Article类建立关系。当这个关系属性被调用时,SQLAlchemy会找到关系的另一侧(即article表)的外键字段(即author_id),然后反向查询article表中所有author_id值为当前表主键值(即author.id)的记录,返回包含这些记录的列表,也就是返回某个作者对应的多篇文章记录。
>>> from app import Author, Article >>> from app import db >>> 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对象建立关系。提交数据库改动后,如果我们队id为1的foo对象调用articles关系属性,会看到spam对象包括在返回的Article对象列表中:
>>> foo=Author.query.first() >>> foo <Author u‘F00‘> >>> foo.articles [<Article u‘Spam‘>, <Article u‘Ham‘>]
另一种方式是通过操作关系属性,将关系属性付给实际的对象即可建立关系。集合关系属性可以像列表一样操作,调用append()方法来与一个Article对象建立关系:
>>> foo.articles [<Article u‘Spam‘>, <Article u‘Ham‘>] >>> A1 = Article.query.get(3) >>> A1 <Article u‘A1‘> >>> foo.articles.append(A1) >>> db.session.commit() >>> foo.articles [<Article u‘Spam‘>, <Article u‘Ham‘>, <Article u‘A1‘>]
我们也可以直接将关系属性赋值给一个包含Article对象的列表。
>>> foo.articles [<Article u‘Spam‘>, <Article u‘Ham‘>, <Article u‘A1‘>] >>> foo.articles = foo.articles[1:2] >>> foo.articles [<Article u‘Ham‘>]
和前面的第一种方式类似,为了让改动生效,我们需要调用db.session.commit()方法提交数据库会话。
建立关系后,存储外键的author_id字段会自动获得正确的值,而调用Author实例的关系属性articles时,会获得所有建立关系的Article对象:
>>> foo.id 2 >>> A1.author_id 2 >>> foo.articles [<Article u‘Spam‘>, <Article u‘Ham‘>, <Article u‘A1‘>]
和主键类似,外键字段有SQLAlchemy管理,我们不需要手动设置。当通过关系属性建立关系后,外键字段会自动获得正确的值。
和append()相对,对关系属性调用remove()方法可以与对应的Article对象接触关系:
>>> soo.articles [<Article u‘Ham‘>, <Article u‘A1‘>] >>> soo.articles.remove(A1) >>> soo.articles [<Article u‘Ham‘>] >>> db.session.commit() >>> soo.articles [<Article u‘Ham‘>]
你也可以使用pop()方法操作关系属性,它会与关系属性对应的列表的最后一个Article对象接触关系并返回改对象。
不要忘记在操作结束后需要调用commit()方法提交数据库会话,这样才可以把数据写入数据库。
是用关系函数定义的属性不是数据库字段,而是类似于特定的查询函数。当某个Article对象被删除时,在对应Author对象的articles属性调用时返回的列表也不会包含该对象。
原文:https://www.cnblogs.com/xiaxiaoxu/p/10597561.html