Наред с други неща, може да искате да научите за проксита за асоцииране . Прокси за асоцииране казва на SQLAlchemy, че имате връзка много към много, медиирана от междинна таблица, която може да съдържа допълнителни данни. Във вашия случай всеки User
може да изпраща множество заявки и също така да получава множество заявки и Relationship
е посредническата таблица, която съдържа status
колона като допълнителни данни.
Ето вариант на вашия код, който остава относително близък до това, което сте написали:
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.
(Забележете как подредих редовете малко по-различно и как използвах _id
суфикс за колони с външен ключ, като запазва същото име без суфикс за съответния db.relationship
с. Предлагам ви също да приемете този стил.)
Вече имате чист начин за достъп до входящи и изходящи заявки за приятелство, както и до съответните потребители директно от вашия User
модел. Това обаче все още не е идеално, защото трябва да напишете следния код, за да получите всички потвърдени приятели на потребител:
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()
(Не съм тествал това; може да се наложи също да се join
с User
и в двете заявки за union
на работа.)
За да влоши нещата, името на модела Relationship
както и имената на няколко членове в рамките на моделите изглежда не предават много добре какво всъщност означават.
Можете да подобрите нещата, като премахнете Relationship.status
и преименуване на Relationship
до FriendshipRequest
. След това добавете втори User
-to-User
асоциационен модел, наречен Friendship
и добавете съответния втори набор от db.Relationship
s с backref
s и association_proxy
s към User
. Когато някой изпрати молба за приятелство, вие подавате запис в FriendshipRequest
. Ако заявката бъде приета, премахвате записа и го заменяте с нов запис в Friendship
. По този начин, вместо да се използва код за състояние, състоянието на приятелство се кодира от таблицата, в която съхранявате двойка потребители. Friendship
моделът може да изглежда така:
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.)
(Съответстващ db.relationship
s и association_proxy
s в User
се оставят като упражнение на читателя.)
Този подход ви спестява половината от операциите по филтриране, когато имате нужда от потвърдените приятели на даден потребител. Все пак трябва да направите union
от две заявки, защото вашият потребител може да бъде или user1
или user2
във всеки случай на Friendship
. Това по своята същност е трудно, защото имаме работа с рефлексивна симетрична връзка. Мисля, че е възможно да се измислят още по-елегантни начини да се направи това, но мисля, че това би било достатъчно сложно, за да оправдае нов въпрос тук в Stack Overflow.