数据模型关系
一对多
如上所示,一个作者关联多个文章,暂时认定,一篇文章只能有一个作者。
作者以及文章的类定义如下所示:
class Author(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(128), unique=True)email = db.Column(db.String(128))
class Article(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(50), index=True)description = db.Column(db.Text)
一对多的建立步骤
现在需要在数据库中,将作者和文章的关系关联成一对多的关系,具体操作如下:
-
定义外键
外键(
Foreign key
) 用来在B
表存储A
表的主键值,作为与A
表的关系字段。由于外键只能够存储单一数据,所以外键常在 “多” 的一侧定义,一个作者对应多个文章,因此需要在文章模型中添加作者的关系字段,记录作者的主键值,代码如下:
class Article(db.Model):...author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
-
定义关系属性
关系属性的定义主要是用来标记该类与那个类建立了关系,常常在 “一” 的一侧进行定义,关系属性能够返回多个记录,也称之为集合关系属性。
在作者和文章的关系中,就需要在作者一侧定义关系属性,代码如下:
class Author(db.Model):...articles = db.relationship('Article')
-
创建表
# 我在这里是通过python直接创建表,因此使用了app.app_context()这个方法,这个方法主要是用来引入flask的各种方法,否则操作会产生报错 with app.app_context():# 将所有的模型文件创建为表db.create_all()# 删除数据库中所有的表db.drop_all()
-
建立关系
建立关系这里指的是,将两张表的数据进行关系,主要有以下两种方式:外键字段赋值、关系属性赋值。
这里我们先准备几组数据,用来操作实现关系的建立。
from market import app,db from market.models import Author,Article author1 = Author(name='余华',email='yuhua@euansu.cn') author2 = Author(name='莫言',email='moyan@euansu.cn') author3 = Author(name='史铁生',email='shitiesheng@euansu.cn') article1 = Article(title='活着',description='活着') article2 = Article(title='许三观卖血记',description='许三观卖血记') article3 = Article(title='我与地坛',description='我与地坛') article4 = Article(title='红高粱',description='红高粱') article5 = Article(title='蛙',description='蛙') with app.app_context():db.session.add(author1)db.session.add(author2)db.session.add(author3)db.session.add(article1)db.session.add(article2)db.session.add(article3)db.session.add(article4)db.session.add(article5)db.session.commit()
执行如上操作后,查看数据库,正常插入了数据。
这里需要注意,关系属性虽然在作者模型中,但并未实际在表中创建字段,接下来通过如下代码对数据表中的数据建立一对多关系:
# 外键字段赋值 with app.app_context(): author = Author.query.filter_by(name='余华').first() article = Article.query.filter_by(title='活着').first()article.author_id = author.iddb.session.commit()
执行完成后,查看数据库,数据表
author_id
的值为关联作者的主键值。# 执行完外键关系赋值后,可以通过如下调用,查询作者余华关联的图书 with app.app_context(): author = Author.query.filter_by(name='余华').first() print(author.articles)
# 操作关系属性 with app.app_context(): author = Author.query.filter_by(name='余华').first()article = Article.query.filter_by(title='许三观卖血记').first()author.articles.append(article)# 提交事务,将变更写入到数据库db.session.commit()
执行完成后,查看数据库,数据表
author_id
的值为关联作者的主键值。# 执行完外键关系赋值后,可以通过如下调用,查询作者余华关联的图书 with app.app_context(): author = Author.query.filter_by(name='余华').first() print(author.articles)
因此,综上步骤,我们通过Flask建立两个表之间的一对多关系时,需要通过以下三个步骤:
-
定义外键,需要在 “多” 侧表的模型中增加外键字段。
-
定义关系属性,需要在 “一” 侧表的模型中定义关系属性,该属性并不体现在实际的表中。
-
建立关系,通过指定外键字段或操作关系属性,能够建立两个表之间的一对多关系属性。
建立双向关系
我们在 Author
类中定义了集合关系属性 articles
,用以获取某个作者的多个作品。在特殊场景下,也有可能希望在 Article
类中定义一个类似的 author
关系属性,当被调用时,返回关联的 Author
记录,这类返回单个值的关系属性被称为标量关系属性。而两侧都添加关系属性获取对方记录的称之为双向关系。
双向关系并不是必须的,只是满足于特殊的场景,可以按照如下方式建立双向关系:
class Author(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(128), unique=True)email = db.Column(db.String(128))articles = db.relationship('Article')
class Article(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(50), index=True)description = db.Column(db.Text)author_id = db.Column(db.Integer, db.ForeignKey('author.id'))author = db.relationship('Author')
使用如下 Python
代码查询:
with app.app_context():author = Author.query.filter_by(name='余华').first()article = Article.query.filter_by(title='许三观卖血记').first()print(author.articles)print(article.author)
使用 backref 简化关系定义
backef
参数用来自动为关系另一侧添加关系属性,作为反向引用,赋予的值会作为关系另一侧的关系属性名称。
class Author(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(128), unique=True)email = db.Column(db.String(128))articles = db.relationship('Article', backref='author')
class Article(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(50), index=True)description = db.Column(db.Text)author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
再次执行关系属性语句:
with app.app_context():author = Author.query.filter_by(name='余华').first()article = Article.query.filter_by(title='许三观卖血记').first()print(author.articles)print(article.author)
能够正常获取其关系的对象。
使用 backref
非常方便,但通常来说 “显式好过隐式”,所以我们应该尽量使用 back_populates
定义双向关系。
多对一
一对多的关系反过来就是多对一,这两种关系模型分别从不同的视角出发。
在一对多中,我们提交以下两点:
-
定义外键,需要在 “多” 侧表的模型中增加外键字段。
-
定义关系属性,需要在 “一” 侧表的模型中定义关系属性,该属性并不体现在实际的表中。
因此,多对一的关系代码定义如下:
class Author(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(128), unique=True)email = db.Column(db.String(128))# 定义外键author_id = db.Column(db.Integer, db.ForeignKey('author.id'))# 定义关系属性articles = db.relationship('Article')
class Article(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(50), index=True)description = db.Column(db.Text)
一对一
一对一关系是在一对多关系的基础上转化而来,只要确保两侧的关联关系唯一即可保证一对多关系转系转化为了一对一关系,在定义时,设置关系属性的 uselist
为 Flase
,此时的一对多关系转化为一对一关系。
class Person(db.Model):id = db.Column(db.Integer, primary_key = True)name = db.Column(db.String(30), unique = True)idcard = db.relationship('IDCard', uselist = False)
def __repr__(self):return '<Person %r>' % self.name
class IDCard(db.Model):id = db.Column(db.Integer, primary_key = True)idcard = db.Column(db.String(30), unique = True)person_id = db.Column(db.Integer, db.ForeignKey('person.id'))person = db.relationship('Person')
def __repr__(self):return '<IDCard %r>' % self.idcard
这里执行如下操作先写入数据:
with app.app_context(): idcard = IDCard(idcard='123456789') idcard2 = IDCard(idcard='123456798') person = Person(name='euansu')db.session.add(idcard) db.session.add(idcard2) db.session.add(person) db.session.commit()
建立关系:
# 指定外键id
with app.app_context(): idcard = IDCard.query.filter_by(idcard='123456789').first() person = Person.query.filter_by(name='euansu').first()idcard.person_id = person.iddb.session.commit()
查看数据库,正常写入:
# 操作关系属性
with app.app_context(): idcard = IDCard.query.filter_by(idcard='123456798').first() person = Person.query.filter_by(name='euansu').first()person.idcard.append(idcard)# 提交事务,将变更写入到数据库db.session.commit()
多对多
多对多关系中,需要建立一个关联表,关联表并不存在数据,只用来存储两侧模型外键的对应关系。
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(128), 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(128), unique=True) grade = db.Column(db.String(20))stutents = db.relationship('Student', secondary=association_table, back_populates='teachers')
创建完模型之后,执行迁移,在数据库中查看到新增的三张表:
flask db migrate
flask db upgrade