再次理解 Flask-SQLAlchemy 中数据模型之间的关联

再次理解 Flask-SQLAlchemy 中数据模型之间的关联

一对多

Post 表示博客的文章

1
2
3
4
5
6
7
8
9
10
11
class Post(db.Model):
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(255))
text = db.Column(db.Text())
publish_date = db.Column(db.Datetime())
# 外键的参数是'表名.主键'
# 也可以是类名.主键(不是字符串)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))

def __init__(self, title):
self.title = title

User 表示用户

1
2
3
4
5
6
7
8
9
10
11
12
class User(db.Model):
id = db.Column(db.Integer(), primary_key=True)
username = db.Column(db.String(255))
password = db.Column(db.String(255))
posts = db.relationship( # 是一个 list,内容是该作者的所有 post 的对象
'Post', # Post 此时可能还没定义,所以用字符串作为参数传递
backref='user',
# 查询方式,dynamic 表示动态加载
# 这样在加载完 Post 的时候并不立刻加载与其关联的 User
# 而是在被使用时才加载
lazy='dynamic'
)

在一个典型的博客系统中,一篇文章的作者只有一个,而一个作者可能有多篇文章,因此 Post 通过外键与 User 建立了联系,User.id 作为了 Post 表的一列存在了表中。而 User 则使用 db.relationship 来和 ForeignKey 来建立联系。注意 db.relationship 定义的内容并不真的存在于数据库表中,只是存在于 SQLAlchemy 中。

用法

通过在 Post 中指定 user_id 来影响 User.posts

1
2
3
4
5
6
7
>>> user = User.query.get(1)
>>> new_post = Post('Post Title')
>>> new_post.user_id = user.id
>>> db.session.add(new_post)
>>> db.session.commit()
>>> user.posts # 此时新的 post 会被自动添加到 User 表中
[<Post 'Post Title'>]

通过反向引用来影响 User.posts

1
2
3
4
5
6
>>> second_post = Post('Second Title')
>>> second_post.user = user
>>> db.session.add(second_post)
>>> db.session.commit()
>>> user.posts # 此时新的 post 也会被自动添加到 User 表中
[<Post 'Post Title'>, <Post 'Second Title'>]

在 relationship 中通过 backref 参数来指定了反向引用,因此在 Post 中只要通过 Post.user 就可以引用对应的 User 内容了,在这里就是通过反向引用来改变了 User.posts 的内容(新绑定了一个 post)

关于 User.posts

User 中的 posts 字段因为是 dynamic 方式,所以既可以看作列表,也可以看做查询对象(可以在后面使用 filter_by 等)

看作列表

1
2
>>> user.posts
[<Post 'Post Title'>, <Post 'Second Title'>]

看做查询对象

1
2
>>> user.posts.order_by(Post.publish_date.desc()).all()
[<Post 'Second Title'>, <Post 'Post Title'>]

一对多的最后

因为二者进行了绑定,因此对一个的内容(一般是‘多’的那边)进行更改,另一个就可以马上同步而不用自己手动修改

一对一

一对一和一对多类似,不过在声明 relationship 的时候要传入参数 uselist=False

Userinfo 表示没那么重要的数据,对 User 作为补充

1
2
3
4
5
class Userinfo(db.Model):
id = db.Column(db.Integer(), db.ForeignKey('user.id'), primary_key=True)
age = db.Column(db.String(255))
introduction = db.Column(db.Text())
update_date = db.Column(db.Datetime())

User 表示用户

1
2
3
4
5
6
7
8
9
class User(db.Model):
id = db.Column(db.Integer(), primary_key=True)
username = db.Column(db.String(255))
password = db.Column(db.String(255))
userinfo = db.relationship(
'Userinfo',
backref='user',
uselist=False
)

一对一的作用主要是副表对主表进行扩展,主表中存放的是相对重要的内容,而副表中存放的是相对没那么重要的内容。副表访问主表数据的方法和一对多相同,但是主表访问附表的方式就多种多样了

通过外键来查询

1
>>> userinfo = Userinfo.query.get(user.id)

二者建立双向绑定,通过主表反向调用副表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Userinfo(db.Model):
id = db.Column(db.Integer(), db.ForeignKey('user.id'), primary_key=True)
age = db.Column(db.String(255))
introduction = db.Column(db.Text())
update_date = db.Column(db.Datetime())
user = db.relationship(
'Use',
back_populates='userinfo',
uselist=False
)

class User(db.Model):
id = db.Column(db.Integer(), primary_key=True)
username = db.Column(db.String(255))
password = db.Column(db.String(255))
userinfo = db.relationship(
'Userinfo',
back_populates='user',
uselist=False
)

这里 back_populates 和 backref 意义是相似的,但是 back_populates 必须要在两处都进行说明,这样同时也可以进行双向的反向引用了

因此主表访问附表就可以直接访问了

1
>>> userinfo = user.userinfo

多对多

数据库模型定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 直接用 db.Table 来建立表
tags = db.Table( # 辅助表,用来建立多对多关系
'post_tags', # 表名
db.Column('post_id', db.Integer, db.ForeignKey('post.id')),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
)


class Post(db.Model):
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(255))
text = db.Column(db.Text())
publish_date = db.Column(db.Datetime())
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
tags = db.relationship( # 声明关系
'Tag', # 主要是给 Tag 引用
secondary=tags, # 通过 tags 作为中介
backref=backref('posts', lazy='dynamic')
)

def __init__(self, title):
self.title = title


class Tag(db.Model):
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(255))

def __init__(self, title):
self.title = title

用法

1
2
3
4
5
6
7
8
9
10
11
12
>>> post_one = Post.query.all()[1]
>>> post_two = Post.query.all()[2]
>>> tag_one = Tag('Python')
>>> tag_two = Tag('SQLAlchemy')
>>> tag_three = Tag('Flask')
>>> post_one.tags = [tag_two] # 手动创建对应关系
>>> post_two.tags = [tag_one, tag_two, tag_three] # 里面是对象
>>> tag_two.posts # 反向引用
[post_one, post_two] # 里面是对象
>>> db.session.add(post_one) # 似乎并没有 add tag 到 session
>>> db.session.add(post_two) # 因为通过回话自动保存了标签
>>> db.session.commit()

在所对多关系中,backref 也变成了 list,可以通过 list 的 append 方法来添加文章到标签

1
2
3
4
5
6
>>> tag_one.posts.append(post_one)
[<Post 'Post Title'>, <Post 'Second Title'>]
>> post_one.tags
[<Tag 'SQLAlchemy'>, <Tag 'Python'>]
>>> db.session.add(tag)
>>> db.session.commit()

最后

如果要重写 init 方法的话则要注意使用的变量是否已经初始化了,没有初始化的话可能变量值为 None,此时调用就会报错。