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.