PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

sqlalchemy symmetrisch viele zu einer Freundschaft

Unter anderem möchten Sie vielleicht mehr über Assoziations-Proxys . Ein Assoziations-Proxy teilt SQLAlchemy mit, dass Sie eine Viele-zu-Viele-Beziehung haben, die durch eine Zwischentabelle vermittelt wird, die zusätzliche Daten enthalten kann. In Ihrem Fall jeder User kann mehrere Anfragen senden und auch mehrere Anfragen empfangen und Relationship ist die vermittelnde Tabelle, die den status enthält Spalte als Zusatzdaten.

Hier ist eine Variante Ihres Codes, die dem, was Sie geschrieben haben, relativ nahe kommt:

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.

(Beachten Sie, wie ich die Zeilen etwas anders angeordnet habe und wie ich die _id verwendet habe Suffix für Fremdschlüsselspalten, während derselbe Name ohne das Suffix für die entsprechende db.relationship reserviert wird s. Ich würde vorschlagen, dass Sie auch diesen Stil übernehmen.)

Jetzt haben Sie eine saubere Möglichkeit, direkt von Ihrem User aus auf ein- und ausgehende Freundschaftsanfragen sowie die entsprechenden Benutzer zuzugreifen Modell. Dies ist jedoch immer noch nicht ideal, da Sie den folgenden Code schreiben müssen, um alles bestätigt zu bekommen Freunde eines Benutzers:

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

(Ich habe dies nicht getestet; Sie müssen möglicherweise auch join mit User in beiden Abfragen in der Reihenfolge für die union zu arbeiten.)

Erschwerend kommt hinzu, dass der Modellname Relationship sowie die Namen mehrerer Mitglieder in den Modellen scheinen nicht sehr gut zu vermitteln, was sie eigentlich bedeuten.

Sie können die Angelegenheit verbessern, indem Sie Relationship.status entfernen und Umbenennen von Relationship zu FriendshipRequest . Fügen Sie dann einen zweiten User hinzu -an-User Assoziationsmodell namens Friendship und fügen Sie einen entsprechenden zweiten Satz von db.Relationship hinzu s mit backref s und association_proxy s an User . Wenn jemand eine Freundschaftsanfrage sendet, legen Sie einen Datensatz an FriendshipRequest ab . Wenn die Anfrage akzeptiert wird, entfernen Sie den Datensatz und ersetzen ihn durch einen neuen Datensatz in Friendship . Anstatt einen Statuscode zu verwenden, wird der Status einer Freundschaft auf diese Weise durch die Tabelle codiert, in der Sie ein Benutzerpaar speichern. Die Friendship Modell könnte so aussehen:

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

(Entsprechende db.relationship s und association_proxy s in User werden dem Leser als Übung überlassen.)

Dieser Ansatz erspart Ihnen die Hälfte der Filtervorgänge, wenn Sie die bestätigten Freunde eines Benutzers benötigen. Trotzdem müssen Sie eine union machen von zwei Abfragen, da Ihr Benutzer entweder user1 sein kann oder user2 in jeder Instanz von Friendship . Dies ist von Natur aus schwierig, da wir es mit einer reflexiven symmetrischen Beziehung zu tun haben. Ich denke, es ist möglich, noch elegantere Möglichkeiten zu erfinden, aber ich denke, das wäre kompliziert genug, um eine neue Frage hier auf Stack Overflow zu rechtfertigen.