PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

sqlalchemy symétrique plusieurs à un amitié

Entre autres choses, vous voudrez peut-être en savoir plus sur les proxys d'association . Un proxy d'association indique à SQLAlchemy que vous avez une relation plusieurs-à-plusieurs médiée par une table intermédiaire qui peut contenir des données supplémentaires. Dans votre cas, chaque User peut envoyer plusieurs demandes et également recevoir plusieurs demandes et Relationship est la table de médiation qui contient le status colonne en tant que données supplémentaires.

Voici une variante de votre code qui reste relativement proche de ce que vous avez écrit :

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.

(Notez comment j'ai commandé les lignes légèrement différemment et comment j'ai utilisé le _id suffixe pour les colonnes de clé étrangère tout en réservant le même nom sans le suffixe pour le db.relationship correspondant s. Je vous suggère également d'adopter ce style.)

Vous disposez désormais d'un moyen propre d'accéder aux demandes d'amitié entrantes et sortantes ainsi qu'aux utilisateurs correspondants directement depuis votre User maquette. Cependant, cela reste loin d'être idéal car vous devez écrire le code suivant afin d'obtenir tout confirmé amis d'un utilisateur :

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

(Je n'ai pas testé cela ; vous devrez peut-être également join avec User dans les deux requêtes dans l'ordre de l'union travailler.)

Pour aggraver les choses, le nom du modèle Relationship ainsi que les noms de plusieurs membres dans les modèles ne semblent pas très bien transmettre ce qu'ils signifient réellement.

Vous pouvez améliorer les choses en supprimant Relationship.status et renommer Relationship à FriendshipRequest . Ensuite, ajoutez un deuxième User -à-User modèle d'association appelé Friendship et ajoutez un deuxième ensemble correspondant de db.Relationship s avec backref s et association_proxy s à User . Lorsque quelqu'un envoie une demande d'amitié, vous déposez un enregistrement dans FriendshipRequest . Si la demande est acceptée, vous supprimez l'enregistrement et le remplacez par un nouvel enregistrement dans Friendship . De cette façon, au lieu d'utiliser un code de statut, le statut d'une amitié est encodé par la table dans laquelle vous stockez une paire d'utilisateurs. L'Friendship le modèle peut ressembler à ceci :

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

(Correspondant db.relationship s et association_proxy s dans User sont laissés en exercice au lecteur.)

Cette approche vous évite la moitié des opérations de filtrage lorsque vous avez besoin des amis confirmés d'un utilisateur. Pourtant, vous devez faire une union de deux requêtes car votre utilisateur peut être soit user1 ou user2 dans chaque instance de Friendship . Ceci est intrinsèquement difficile car nous avons affaire à une relation symétrique réflexive. Je pense qu'il est possible d'inventer des façons encore plus élégantes de le faire, mais je pense que ce serait suffisamment compliqué pour justifier une nouvelle question ici sur Stack Overflow.