Bland annat kanske du vill lära dig mer om associationsproxy . En associationsproxy talar om för SQLAlchemy att du har en många-till-många-relation som förmedlas av en mellantabell som kan innehålla ytterligare data. I ditt fall, varje User
kan skicka flera förfrågningar och även ta emot flera förfrågningar och Relationship
är förmedlingstabellen som innehåller status
kolumnen som ytterligare data.
Här är en variant av din kod som ligger relativt nära 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.
(Observera hur jag ordnade raderna lite annorlunda och hur jag använde _id
suffix för främmande nyckelkolumner samtidigt som samma namn reserveras utan suffixet för motsvarande db.relationship
s. Jag skulle föreslå att du också anammar den här stilen.)
Nu har du ett rent sätt att komma åt inkommande och utgående vänskapsförfrågningar samt motsvarande användare direkt från din User
modell. Detta är dock fortfarande mindre än idealiskt eftersom du måste skriva följande kod för att få alla bekräftade vänner till en användare:
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()
(Jag testade inte detta, du kanske också måste join
med User
i båda frågorna för att union
ska användas att arbeta.)
För att göra saken värre, modellnamnet Relationship
liksom namnen på flera medlemmar i modellerna verkar inte förmedla vad de egentligen betyder.
Du kan förbättra saker genom att ta bort Relationship.status
och byter namn på Relationship
till FriendshipRequest
. Lägg sedan till en andra User
-till-User
föreningsmodell som heter Friendship
och lägg till en motsvarande andra uppsättning db.Relationship
s med backref
s och association_proxy
s till User
. När någon skickar en vänskapsförfrågan arkiverar du en post till FriendshipRequest
. Om begäran accepteras tar du bort posten och ersätter den med en ny post i Friendship
. På så sätt, istället för att använda en statuskod, kodas statusen för en vänskap av tabellen där du lagrar ett par användare. Friendship
modell kan se ut så här:
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.)
(Motsvarande db.relationship
s och association_proxy
s i User
lämnas som en övning till läsaren.)
Detta tillvägagångssätt sparar hälften av filtreringsoperationerna när du behöver bekräftade vänner till en användare. Ändå måste du skapa en union
av två frågor eftersom din användare kan vara antingen user1
eller user2
i varje instans av Friendship
. Detta är till sin natur svårt eftersom vi har att göra med ett reflexivt symmetriskt förhållande. Jag tror att det är möjligt att uppfinna ännu mer eleganta sätt att göra det på, men jag tror att det skulle vara komplicerat nog för att motivera en ny fråga här på Stack Overflow.