我有两个模型:User
和Message
。每个Message
都有两个对User
的引用(如sender
和receiver
)。此外,我还为Message
定义了一个other_user
混合方法,该方法返回"特定用户以外的用户" - 见下文:
from peewee import *
from playhouse.hybrid import hybrid_method
from playhouse.shortcuts import case
class User(Model):
name = CharField()
class Message(Model):
sender = ForeignKeyField(User, related_name='messages_sent')
receiver = ForeignKeyField(User, related_name='messages_received')
text = TextField()
@hybrid_method
def other_user(self, user):
if user == self.sender:
return self.receiver
elif user == self.receiver:
return self.sender
else:
raise ValueError
@other_user.expression
def other_user(cls, user):
return case(user.id, (
(cls.sender, cls.receiver),
(cls.receiver, cls.sender)))
现在我想进行一个复合查询,它将检索当前用户的所有消息,并检索有关当前用户以外的"其他"用户的信息。这是我的做法:
current_user = request.user # don't matter how I retrieve it
query = (Message.select(Message, User)
.where(
(Message.sender == current_user) |
(Message.receiver == current_user))
.join(User, on=(User.id == Message.other_user(current_user))))
这个查询效果很好 - 即它检索我需要的确切信息。但问题是:"其他用户"信息始终保存为sender
字段。如果我将其用于没有直接ForeignKey
引用的模型,那么peewee
为其他请求的模型创建一个新字段(在这种情况下,它将命名为 user
)。但是,如果从主模型到次要请求模型至少有一个ForeignKey
关系,则它首先使用此类关系。
是否有可能以某种方式覆盖此行为?我尝试了Model.alias()
方法,但它(与Node.alias
不同)不允许指定名称。
我不完全确定你想要什么,所以我将提供一个可能适用于你的特定场景的代码片段,并希望也能让你学习如何做你想做的事情,如果不是这样的话:
SenderUser = User.alias()
ReceiverUser = User.alias()
OtherUser = User.alias()
query = (Message.select(Message, SenderUser, ReceiverUser)
.where(
(Message.sender == current_user) |
(Message.receiver == current_user))
.join(SenderUser, on = (Message.sender == SenderUser.id).alias('sender'))
.switch(Message)
.join(ReceiverUser, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
.switch(Message)
.join(OtherUser, on = (Message.other_user(current_user) == OtherUser.id).alias('other_user'))
笔记:
您实际上不需要创建所有这些别名(SenderUser/ReceiverUser/OtherUser),只需创建两个别名,并使用User作为另一个别名。我只是发现查询像这样变得更加可读。
当您在 on 子句中定义别名时,您基本上会告诉 peewee 将连接表存储在哪个变量中。我将它们直接发送到已经存在的属性(发送方/接收方)。此外,我正在使用其他用户的值在模型中创建一个额外的属性,您可以像往常一样使用 self.other_user
访问该属性。
该 switch 方法将当前上下文切换到消息,因此您可以将新表联接到消息,而不是在前两个联接后结束的 SenderUser/ReceiverUser 上下文。
如果出于某种原因您要加入可能未定义的内容(这里似乎不是这种情况,因为两个用户都可能是强制性的),您可能需要添加您想要左外部连接,如下所示:
.join(ReceiverUser, JOIN.LEFT_OUTER, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
别忘了from peewee import JOIN
我刚刚注意到的另一件事是,您可能想要更改other_user方法,您必须比较 id 而不是模型变量。如果在访问 self.sender 时未填充它,peewee 将触发数据库选择来获取它,因此您的other_user方法可能会触发 2 个选择查询。我会这样做:
@hybrid_method
def other_user_id(self, user):
if user.id == self.sender_id:
return self.receiver_id
elif user.id == self.receiver_id:
return self.sender_id
else:
raise ValueError
你可以看到我用sender_id而不是 sender.id。它使用已在消息模型中设置的每个外键的 id。如果您确实 self.receiver.id 则无论如何都可能会触发该选择,然后访问id属性(不过我在这里不是100%确定)。