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.