2016-06-22 19 views
0

SQLAlchemy ORMを使用して友だちをモデル化しようとしています。モデル化しようとしている関係は対称です。 Facebookと同様、ユーザーaがユーザーbを追加する場合、ユーザーbはその友情要求を承認する必要があります。私の現在のモデルは以下の通りです。sqlalchemy対称多対1の友情

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

「Relationship」テーブルはこの場合の関連テーブルです。 「友人関係」の対称性は、「友情関係」関係の非対称性と直接の不一致である。私はあなたが2つを分けるべきだと思います。 – univerio

答えて

1

特に、association proxiesについて知りたいことがあります。アソシエーションプロキシはSQLAlchemyに、追加データを含む可能性のある中間テーブルによって仲介された多対多の関係を持つことを通知します。あなたのケースでは、それぞれUserは複数のリクエストを送信することができ、また複数のリクエストを受け取ることができ、Relationshipstatus列を追加データとして含む仲介テーブルです。ここで

は、あなたが書いたものに比較的近いままあなたのコードの変種です:

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から、フレンドリーな着信要求と対応するユーザーに直接アクセスできます。モデル。あなたは、にもjoinでなければならない場合があります。(私はこれをテストしていない

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() 

:あなたは、ユーザーのすべての確認の友人を得るために以下のコードを記述する必要があるためしかし、これはまだ理想的な未満でありますunionが動作するためには、両方のクエリでUser。)

さらに悪いことに、モデル名Relationshipだけでなく、モデル内のいくつかのメンバーの名前は、彼らが実際に何を意味するのか、非常によく伝えていないようです。

Relationship.statusを削除し、Relationshipの名前をFriendshipRequestに変更して、問題を改善することができます。次いで、Friendshipと呼ばれる第二UserUser -to-アソシエーションモデルを追加し、backref SおよびUserassociation_proxy Sとdb.Relationship Sの対応する第2のセットを追加します。誰かが友情要求を送ると、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.) 

Userdb.relationship sおよびassociation_proxyの対応読者への課題として残されています。)

この方法では、ユーザーの確認済みの友達が必要なときに、フィルタリング操作の半分を節約できます。ただし、Friendshipの各インスタンスでuser1またはuser2のいずれかを使用できるため、2つのクエリのうちunionを作成する必要があります。反射的な対称関係を扱っているため、これは本質的に困難です。私はそれを行うよりエレガントな方法を発明することは可能だと思うが、スタックオーバーフローに関する新たな質問を保証するのに十分複雑であると思う。

関連する問題