如何控制 Peewee 中相关模型获取的字段名称?
How to control field name for related model fetching in Peewee?
我有两个模型:User
和 Message
。每个 Message
都有两个对 User
的引用(如 sender
和 receiver
)。我还为 Message
定义了一个 other_user
混合方法,其中 returns "user other than specific one" - 见下文:
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)))
现在我想创建一个复合查询,它将检索当前用户的所有消息,还检索有关 "other" 个用户的信息。这是我的做法:
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))))
此查询运行良好 - 即它检索到我需要的准确信息。
但问题是:"other 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 在哪个变量中存储连接的 table。我将它们直接发送到现有的属性 (sender/receiver)。此外,我在模型中使用其他用户的值创建了一个额外的 属性,您可以像往常一样使用 self.other_user
.
访问它
该 switch 方法将当前上下文切换到 Message,因此您可以将新的 table 加入 Message 而不是在第一次加入两个之后结束的 SenderUser/ReceiverUser 上下文。
如果由于某种原因你要加入一些可能未定义的东西(这里似乎不是这种情况,因为两个用户都可能是强制性的),你可能想要添加你想要一个左外连接,像这样:
.join(ReceiverUser, JOIN.LEFT_OUTER, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
别忘了from peewee import JOIN
我刚刚注意到的另一件事是,您可能想要更改 other_user 方法,您必须比较 ID 而不是模型变量。如果你访问的时候self.sender没有填写,peewee会触发数据库select去获取,所以你的other_user方法可能会触发2次select查询。我会这样做:
@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 你可能会触发那个 select 无论如何,然后访问 id 属性 (虽然我在这里不是 100% 确定)。
我有两个模型:User
和 Message
。每个 Message
都有两个对 User
的引用(如 sender
和 receiver
)。我还为 Message
定义了一个 other_user
混合方法,其中 returns "user other than specific one" - 见下文:
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)))
现在我想创建一个复合查询,它将检索当前用户的所有消息,还检索有关 "other" 个用户的信息。这是我的做法:
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))))
此查询运行良好 - 即它检索到我需要的准确信息。
但问题是:"other 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 在哪个变量中存储连接的 table。我将它们直接发送到现有的属性 (sender/receiver)。此外,我在模型中使用其他用户的值创建了一个额外的 属性,您可以像往常一样使用 self.other_user
.
该 switch 方法将当前上下文切换到 Message,因此您可以将新的 table 加入 Message 而不是在第一次加入两个之后结束的 SenderUser/ReceiverUser 上下文。
如果由于某种原因你要加入一些可能未定义的东西(这里似乎不是这种情况,因为两个用户都可能是强制性的),你可能想要添加你想要一个左外连接,像这样:
.join(ReceiverUser, JOIN.LEFT_OUTER, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
别忘了from peewee import JOIN
我刚刚注意到的另一件事是,您可能想要更改 other_user 方法,您必须比较 ID 而不是模型变量。如果你访问的时候self.sender没有填写,peewee会触发数据库select去获取,所以你的other_user方法可能会触发2次select查询。我会这样做:
@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 你可能会触发那个 select 无论如何,然后访问 id 属性 (虽然我在这里不是 100% 确定)。