上一篇我们讲到多对多关系中使用一张关联表把两张表关联在一起,但在 web 开发中还有一种情况,就是同一张表通过关系列表实现自关联。常见例子入微博用户的 “关注者/粉丝”(followed/followers)。
实现代码如下:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///demo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
followers = db.Table('followers',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
followed = db.relationship(
# User 表示关系当中的右侧实体(将左侧实体看成是上级类)
# 由于这是自引用关系,所以两侧都是同一个实体。
'User',
# secondary 指定了用于该关系的关联表
# 就是使用我在上面定义的 followers
secondary=followers,
# primaryjoin 指明了右侧对象关联到左侧实体(关注者)的条件
# 也就是根据左侧实体查找出对应的右侧对象
# 执行 user.followed 时候就是这样的查找
primaryjoin=(followers.c.follower_id == id),
# secondaryjoin 指明了左侧对象关联到右侧实体(被关注者)的条件
# 也就是根据右侧实体找出左侧对象
# 执行 user.followers 时候就是这样的查找
secondaryjoin=(followers.c.followed_id == id),
# backref 定义了右侧实体如何访问该关系
# 也就是根据右侧实体查找对应的左侧对象
# 在左侧,关系被命名为 followed
# 在右侧使用 followers 来表示所有左侧用户的列表,即粉丝列表
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
)
@app.shell_context_processor
def make_shell_context():
return {'db': db, 'User': User}
假设 followers 表如下数据:
follower_id | followed_id |
---|---|
1 | 2 |
1 | 3 |
2 | 3 |
这表格表示 id 为 1 的用户(称为:user1)关注了(followed) id 为 2、3 的用户(称为:user2、user3),user2 关注了 user3。
反过来说,user1 是 user2、user3 的关注者(followers)。
当我们执行 user1.followed
的操作,是根据左侧实体查找出对应的右侧对象,查询条件为 followers.c.follower_id == 1
,得到 user2 和 user3,也就是 user1 关注的用户。
>>> u1 = User.query.get(1)
>>> u1.followed.all()
[<User 2>, <User 3>]
当我们执行 user3.followers
的操作,是根据右侧实体找出左侧对象,查询条件为 followers.c.followed_id == 3
,得到 user1、user2,也就是 use3 的关注者。
>>> u3 = User.query.get(3)
>>> u3.followers.all()
[<User 1>, <User 2>]
关注和取消关注
user1 关注 user2:
user1.followed.append(user2)
user1 取消关注 user2:
user1.followed.remove(user2)
更有效的方法是我们在 User 类的方法里集成关注和取消关注:
class User(UserMixin, db.Model):
#...
def is_following(self, user):
"""
检查是否已关注 user
"""
return self.followed.filter(
followers.c.followed_id == user.id).count() > 0
def follow(self, user):
if not self.is_following(user):
self.followed.append(user)
def unfollow(self, user):
if self.is_following(user):
self.followed.remove(user)