sql >> Base de Datos >  >> RDS >> PostgreSQL

sqlalchemy simétrico muchos a uno amistad

Entre otras cosas, es posible que desee obtener información sobre proxies de asociación . Un proxy de asociación le dice a SQLAlchemy que tiene una relación de muchos a muchos mediada por una tabla intermedia que puede contener datos adicionales. En su caso, cada User puede enviar múltiples solicitudes y también recibir múltiples solicitudes y Relationship es la tabla mediadora que contiene el status columna como datos adicionales.

Aquí hay una variante de su código que se mantiene relativamente cerca de lo que escribió:

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.

(Observe cómo ordené las líneas de forma ligeramente diferente y cómo usé el _id sufijo para columnas de clave externa mientras se reserva el mismo nombre sin el sufijo para la correspondiente db.relationship s. Te sugiero que adoptes este estilo también).

Ahora tiene una forma limpia de acceder a las solicitudes de amistad entrantes y salientes, así como a los usuarios correspondientes directamente desde su User modelo. Sin embargo, esto sigue siendo menos que ideal porque necesita escribir el siguiente código para obtener todo confirmado amigos de un usuario:

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

(No probé esto; es posible que también deba join con User en ambas consultas para la union para trabajar.)

Para empeorar las cosas, el nombre del modelo Relationship así como los nombres de varios miembros dentro de los modelos no parecen transmitir muy bien lo que realmente significan.

Puede mejorar las cosas eliminando Relationship.status y renombrando Relationship a FriendshipRequest . Luego, agregue un segundo User -a-User modelo de asociación llamado Friendship y agregue un segundo conjunto correspondiente de db.Relationship s con backref s y association_proxy s para User . Cuando alguien envía una solicitud de amistad, usted presenta un registro a FriendshipRequest . Si se acepta la solicitud, elimina el registro y lo reemplaza con un nuevo registro en Friendship . De esta manera, en lugar de usar un código de estado, el estado de una amistad está codificado por la tabla en la que almacena un par de usuarios. La Friendship el modelo puede verse así:

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

(Correspondiente db.relationship s y association_proxy s en User se dejan como ejercicio para el lector).

Este enfoque le ahorra la mitad de las operaciones de filtrado cuando necesita los amigos confirmados de un usuario. Aún así, necesitas hacer una union de dos consultas porque su usuario puede ser user1 o user2 en cada instancia de Friendship . Esto es inherentemente difícil porque estamos tratando con una relación simétrica reflexiva. Creo que es posible inventar formas aún más elegantes de hacerlo, pero creo que sería lo suficientemente complicado como para justificar una nueva pregunta aquí en Stack Overflow.