我正在尝试使用SQLAlchemy ORM建模友谊。我试图建模的关系是对称的。与Facebook类似,如果用户a要添加用户b,则用户b必须批准该友谊请求。我目前的模型如下。
class User(db.Model):
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(35), unique=False)
username = db.Column(db.String(25), index=True, unique=True)
password = db.Column(db.String(35), unique=False)
email = db.Column(db.String(35), unique=True)
phone_number = db.Column(db.String(22))
# define relationships
requester = db.relationship('Relationship', foreign_keys='Relationship.requesting_user', backref='requester')
receiver = db.relationship('Relationship', foreign_keys='Relationship.receiving_user', backref='received')
def __repr__(self):
return '<User %r>' % (self.username)
class Relationship(db.Model):
__tablename__ = 'Relationship'
id = db.Column(db.Integer, primary_key=True)
requesting_user = db.Column(db.Integer, db.ForeignKey('User.id'))
receiving_user = db.Column(db.Integer, db.ForeignKey("User.id"))
status = db.Column(db.Integer)
__table_args__ = (db.UniqueConstraint('receiving_user', 'requesting_user', name='_receiving_user_uc'), )
该模型有效,但我认为它没有正确建模。甚至要求我使用状态吗?我假设它可以建模,以便每个朋友关系都有自己的条目。目前,用户可以与另一个用户发起朋友请求。当其他用户批准该请求时,状态将更改为已接受。我已经看了一些关联表,但我不太确定他们将如何发挥这样的模型。关于我当前模型的任何建议以及如何改进它将不胜感激。
答案 0 :(得分:1)
除其他外,您可能想了解association proxies。关联代理告诉SQLAlchemy,您有一个由中间表调解的多对多关系,该中间表可能包含其他数据。在您的情况下,每个User
可以发送多个请求,也会收到多个请求,Relationship
是包含status
列作为附加数据的中介表。
以下是您的代码的变体,它与您所写的内容保持相对接近:
from sqlalchemy.ext.associationproxy import association_proxy
class User(db.Model):
__tablename__ = 'User'
# The above is not necessary. If omitted, __tablename__ will be
# automatically inferred to be 'user', which is fine.
# (It is necessary if you have a __table_args__, though.)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(35), unique=False)
# and so forth
requested_rels = db.relationship(
'Relationship',
foreign_keys='Relationship.requesting_user_id',
backref='requesting_user'
)
received_rels = db.relationship(
'Relationship',
foreign_keys='Relationship.receiving_user_id',
backref='receiving_user'
)
aspiring_friends = association_proxy('received_rels', 'requesting_user')
desired_friends = association_proxy('requested_rels', 'receiving_user')
def __repr__(self):
# and so forth
class Relationship(db.Model):
# __tablename__ removed, becomes 'relationship'
# __table_args__ removed, see below
requesting_user_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
receiving_user_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
# Marking both columns above as primary_key creates a compound primary
# key, which at the same time saves you the effort of defining the
# UNIQUE constraint in __table_args__
status = db.Column(db.Integer)
# Implicit one-to-many relations: requesting_user, receiving_user.
# Normally it would be more convenient to define those relations on
# this side, but since you have two outgoing relationships with the
# same table (User), you chose wisely to define them there.
(请注意我对行的排序方式略有不同以及我如何使用_id
后缀作为外键列,同时保留相同的名称而没有相应db.relationship
的后缀。我建议你也采用这种风格。)
现在,您可以直接从User
模型访问传入和传出的友情请求以及相应的用户。但是,这仍然不太理想,因为您需要编写以下代码才能获得用户的所有已确认的朋友:
def get_friends(user):
requested_friends = (
db.session.query(Relationship.receiving_user)
.filter(Relationship.requesting_user == user)
.filter(Relationship.status == CONFIRMED)
)
received_friends = (
db.session.query(Relationship.requesting_user)
.filter(Relationship.receiving_user == user)
.filter(Relationship.status == CONFIRMED)
)
return requested_friends.union(received_friends).all()
(我没有对此进行测试;在两个查询中,您可能还需要join
User
,以便union
能够正常运行。)
更糟糕的是,模型名称Relationship
以及模型中几个成员的名称似乎并没有很好地传达它们实际意味着什么。
您可以删除Relationship.status
并将Relationship
重命名为FriendshipRequest
来改善问题。然后,添加名为User
的第二个User
到 - Friendship
关联模型,并添加相应的第二组db.Relationship
s backref
和{{1}转到association_proxy
。当某人发送友情请求时,您将记录提交给User
。如果请求被接受,则删除记录并将其替换为FriendshipRequest
中的新记录。这样,友谊的状态不是使用状态代码,而是由存储一对用户的表编码。 Friendship
模型可能如下所示:
Friendship
(class Friendship(db.Model):
user1_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
user2_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
# Implicit one-to-many relations: user1, user2
# (defined as backrefs in User.)
中对应的db.relationship
和association_proxy
s留给读者。)
当您需要确认的用户朋友时,此方法可以节省一半的过滤操作。不过,您需要提出User
两个查询,因为您的用户在union
的每个实例中可以是user1
或user2
。这本身就很困难,因为我们正在处理一种反身对称的关系。我认为有可能发明更优雅的方法来做到这一点,但我认为这将足够复杂以保证Stack Overflow上的新问题。