如何在 Peewee 中控制相关模型提取的字段名称



我有两个模型:UserMessage。每个Message都有两个对User的引用(如senderreceiver)。此外,我还为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%确定)。

最新更新