sqlalchemy对称多对一友谊

时间:2016-06-22 15:45:23

标签: sql postgresql sqlalchemy flask-sqlalchemy

我正在尝试使用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'), )

该模型有效,但我认为它没有正确建模。甚至要求我使用状态吗?我假设它可以建模,以便每个朋友关系都有自己的条目。目前,用户可以与另一个用户发起朋友请求。当其他用户批准该请求时,状态将更改为已接受。我已经看了一些关联表,但我不太确定他们将如何发挥这样的模型。关于我当前模型的任何建议以及如何改进它将不胜感激。

1 个答案:

答案 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.relationshipassociation_proxy s留给读者。)

当您需要确认的用户朋友时,此方法可以节省一半的过滤操作。不过,您需要提出User两个查询,因为您的用户在union的每个实例中可以是user1user2。这本身就很困难,因为我们正在处理一种反身对称的关系。我认为有可能发明更优雅的方法来做到这一点,但我认为这将足够复杂以保证Stack Overflow上的新问题。