Du vil måske blandt andet lære om associationsproxies . En tilknytningsproxy fortæller SQLAlchemy, at du har en mange-til-mange-relation medieret af en mellemtabel, som kan indeholde yderligere data. I dit tilfælde hver User
kan sende flere anmodninger og også modtage flere anmodninger og Relationship
er formidlingstabellen, som indeholder status
kolonne som yderligere data.
Her er en variant af din kode, som forbliver relativt tæt på det, du skrev:
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.
(Bemærk, hvordan jeg bestilte linjerne lidt anderledes, og hvordan jeg brugte _id
suffiks for fremmednøglekolonner, mens det samme navn reserveres uden suffikset for den tilsvarende db.relationship
s. Jeg vil foreslå, at du også tager denne stil.)
Nu har du en ren måde at få adgang til indgående og udgående venskabsanmodninger samt de tilsvarende brugere direkte fra din User
model. Dette er dog stadig mindre end ideelt, fordi du skal skrive følgende kode for at få alt bekræftet venner af en bruger:
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()
(Jeg testede ikke dette; du skal muligvis også join
med User
i begge forespørgsler i rækkefølge for union
at arbejde.)
For at gøre tingene værre, modelnavnet Relationship
samt navnene på adskillige medlemmer i modellerne lader ikke til at formidle særlig godt, hvad de rent faktisk betyder.
Du kan forbedre tingene ved at fjerne Relationship.status
og omdøb Relationship
til FriendshipRequest
. Tilføj derefter en anden User
-til-User
foreningsmodel kaldet Friendship
og tilføje et tilsvarende andet sæt db.Relationship
s med backref
s og association_proxy
s til User
. Når nogen sender en venskabsanmodning, indsender du en registrering til FriendshipRequest
. Hvis anmodningen accepteres, fjerner du posten og erstatter den med en ny post i Friendship
. På denne måde, i stedet for at bruge en statuskode, bliver status for et venskab kodet af den tabel, hvor du gemmer et par brugere. Friendship
modellen kan se sådan ud:
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.)
(Tilsvarende db.relationship
s og association_proxy
s i User
overlades som en øvelse til læseren.)
Denne tilgang sparer dig for halvdelen af filtreringsoperationerne, når du har brug for en brugers bekræftede venner. Alligevel skal du lave en union
af to forespørgsler, fordi din bruger kan være enten user1
eller user2
i hver forekomst af Friendship
. Dette er i sagens natur svært, fordi vi har at gøre med et refleksivt symmetrisk forhold. Jeg tror, det er muligt at opfinde endnu mere elegante måder at gøre det på, men jeg tror, det ville være kompliceret nok til at berettige et nyt spørgsmål her på Stack Overflow.