PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

sqlalchemy simmetrico molti a uno amicizia

Tra le altre cose, potresti voler conoscere i proxy di associazione . Un proxy di associazione indica a SQLAlchemy che hai una relazione molti-a-molti mediata da una tabella intermedia che potrebbe contenere dati aggiuntivi. Nel tuo caso, ogni User può inviare più richieste e anche ricevere più richieste e Relationship è la tabella di mediazione che contiene lo status colonna come dati aggiuntivi.

Ecco una variante del tuo codice che rimane relativamente vicino a ciò che hai scritto:

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.

(Nota come ho ordinato le righe in modo leggermente diverso e come ho usato il _id suffisso per le colonne chiave esterna riservando lo stesso nome senza il suffisso per il corrispondente db.relationship S. Suggerirei di adottare anche tu questo stile.)

Ora hai un modo pulito per accedere alle richieste di amicizia in entrata e in uscita, nonché agli utenti corrispondenti direttamente dal tuo User modello. Tuttavia, questo non è ancora l'ideale perché è necessario scrivere il codice seguente per ottenere tutto confermato amici di un utente:

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

(Non l'ho testato; potresti dover anche join con User in entrambe le query in ordine per l'union lavorare.)

A peggiorare le cose, il nome del modello Relationship così come i nomi di diversi membri all'interno dei modelli non sembrano trasmettere molto bene cosa significano effettivamente.

Puoi migliorare le cose rimuovendo Relationship.status e rinominando Relationship a FriendshipRequest . Quindi, aggiungi un secondo User -to-User modello di associazione chiamato Friendship e aggiungi un secondo set corrispondente di db.Relationship s con backref se association_proxy s a User . Quando qualcuno invia una richiesta di amicizia, archivi un record a FriendshipRequest . Se la richiesta viene accettata, rimuovi il record e lo sostituisci con un nuovo record in Friendship . In questo modo, invece di utilizzare un codice di stato, lo stato di un'amicizia è codificato dalla tabella in cui memorizzi una coppia di utenti. L'Friendship il modello potrebbe assomigliare a questo:

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

(corrispondente a db.relationship se association_proxy s in User sono lasciati come esercizio al lettore.)

Questo approccio consente di risparmiare metà delle operazioni di filtraggio quando sono necessari gli amici confermati di un utente. Tuttavia, devi fare un union di due query perché il tuo utente può essere user1 o user2 in ogni istanza di Friendship . Questo è intrinsecamente difficile perché abbiamo a che fare con una relazione simmetrica riflessiva. Penso che sia possibile inventare modi ancora più eleganti per farlo, ma penso che sarebbe abbastanza complicato da giustificare una nuova domanda qui su Stack Overflow.