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

How to control field name for related model fetching in Peewee?

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