PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

sqlalchemy simétrico muitos para um amizade


Entre outras coisas, você pode querer aprender sobre proxies de associação . Um proxy de associação informa ao SQLAlchemy que você tem um relacionamento muitos para muitos mediado por uma tabela intermediária que pode conter dados adicionais. No seu caso, cada User pode enviar várias solicitações e também receber várias solicitações e Relationship é a tabela de mediação que contém o status coluna como dados adicionais.

Aqui está uma variante do seu código que fica relativamente próxima do que você escreveu:
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 como ordenei as linhas de forma ligeiramente diferente e como usei o _id sufixo para colunas de chave estrangeira enquanto reserva o mesmo nome sem o sufixo para o db.relationship correspondente s. Eu sugiro que você adote esse estilo também.)

Agora você tem uma maneira limpa de acessar solicitações de amizade recebidas e enviadas, bem como os usuários correspondentes diretamente do seu User modelo. No entanto, isso ainda não é o ideal porque você precisa escrever o código a seguir para obter todos os confirmados amigos de um usuário:
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()

(Eu não testei isso; você pode precisar também join com User em ambas as consultas para que a union trabalhar.)

Para piorar as coisas, o nome do modelo Relationship assim como os nomes de vários membros dentro dos modelos não parecem transmitir muito bem o que eles realmente significam.

Você pode melhorar os assuntos removendo Relationship.status e renomeando Relationship para FriendshipRequest . Em seguida, adicione um segundo User -to-User modelo de associação chamado Friendship e adicione um segundo conjunto correspondente de db.Relationship s com backref se association_proxy s para User . Quando alguém envia uma solicitação de amizade, você registra um registro em FriendshipRequest . Se a solicitação for aceita, você remove o registro e o substitui por um novo registro em Friendship . Dessa forma, em vez de usar um código de status, o status de uma amizade é codificado pela tabela na qual você armazena um par de usuários. A Friendship modelo pode ficar assim:
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.)

(Correspondente db.relationship se association_proxy s em User são deixados como exercício para o leitor.)

Essa abordagem economiza metade das operações de filtragem quando você precisa dos amigos confirmados de um usuário. Ainda assim, você precisa fazer uma union de duas consultas porque seu usuário pode ser user1 ou user2 em cada instância de Friendship . Isso é inerentemente difícil porque estamos lidando com uma relação simétrica reflexiva. Acho que é possível inventar maneiras ainda mais elegantes de fazer isso, mas acho que seria complicado o suficiente para justificar uma nova pergunta aqui no Stack Overflow.