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

时间:2016-03-13 20:08:35

标签: python sql peewee

我有两个模型:UserMessage。每个Message都有两个User引用(senderreceiver)。此外,我为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不同)并不允许指定名称。

1 个答案:

答案 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%肯定)。