我有两个模型:User
和Message
。每个Message
都有两个User
引用(sender
和receiver
)。此外,我为other_user
定义了一个Message
混合方法,该方法返回"除特定用户以外的用户" - 见下文:
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
不同)并不允许指定名称。
答案 0 :(得分:1)
我不完全确定你想要什么,所以我会提供一个可能适用于你特定场景的片段,并希望你也可以学习如何做你想要的,如果不是这样的话:
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方法将当前上下文切换为Message,因此您可以将新表连接到Message而不是SenderUser / ReceiverUser上下文,这些上下文是在两个第一次连接之后结束的。
如果由于某种原因你加入的东西可能是未定义的(这似乎不是这种情况,因为两个用户可能都是强制性的),你可能想要添加你想要一个左外连接,比如这样:
.join(ReceiverUser, JOIN.LEFT_OUTER, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
不要忘记from peewee import JOIN
我刚注意到的其他事情是,你可能想要更改那个必须比较id而不是模型变量的other_user方法。如果访问时没有填充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%肯定)。